diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 23e8dda..b15eb63 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,7 +80,10 @@ jobs: df -h echo "Start CI build" docker run --privileged --rm tonistiigi/binfmt --install all - podman manifest create build - podman build --format docker --platform linux/amd64,linux/arm64,linux/arm/v7 --manifest localhost/build . - podman manifest push localhost/build docker://docker.io/bbernhard/signal-cli-rest-api:${EPOCHSECONDS}-ci + podman manifest create build-jre + podman build --format docker --target jre --platform linux/amd64,linux/arm64,linux/arm/v7 --manifest localhost/build-jre . + podman manifest push localhost/build-jre docker://docker.io/bbernhard/signal-cli-rest-api:${EPOCHSECONDS}-ci + podman manifest create build-native + podman build --format docker --target native --platform linux/amd64,linux/arm64 --manifest localhost/build-native . + podman manifest push localhost/build-native docker://docker.io/bbernhard/signal-cli-rest-api:${EPOCHSECONDS}-ci-native diff --git a/.github/workflows/release-dev-version.yml b/.github/workflows/release-dev-version.yml index 8581b3d..f127e11 100644 --- a/.github/workflows/release-dev-version.yml +++ b/.github/workflows/release-dev-version.yml @@ -83,7 +83,11 @@ jobs: run: | echo "Start dev build" docker run --privileged --rm tonistiigi/binfmt --install all - podman manifest create build - podman build --format docker --build-arg BUILD_VERSION_ARG=${VERSION} --manifest localhost/build --platform linux/amd64,linux/arm64,linux/arm/v7 . - podman manifest push localhost/build docker://docker.io/bbernhard/signal-cli-rest-api:${VERSION}-dev - podman manifest push localhost/build docker://docker.io/bbernhard/signal-cli-rest-api:latest-dev + podman manifest create build-jre + podman build --format docker --target jre --build-arg BUILD_VERSION_ARG=${VERSION} --platform linux/amd64,linux/arm64,linux/arm/v7 --manifest localhost/build-jre . + podman manifest push localhost/build-jre docker://docker.io/bbernhard/signal-cli-rest-api:${VERSION}-dev + podman manifest push localhost/build-jre docker://docker.io/bbernhard/signal-cli-rest-api:latest-dev + podman manifest create build-native + podman build --format docker --target native --build-arg BUILD_VERSION_ARG=${VERSION} --platform linux/amd64,linux/arm64 --manifest localhost/build-native . + podman manifest push localhost/build-native docker://docker.io/bbernhard/signal-cli-rest-api:${VERSION}-dev-native + podman manifest push localhost/build-native docker://docker.io/bbernhard/signal-cli-rest-api:latest-dev-native diff --git a/.github/workflows/release-productive-version.yml b/.github/workflows/release-productive-version.yml index 357818c..febce11 100644 --- a/.github/workflows/release-productive-version.yml +++ b/.github/workflows/release-productive-version.yml @@ -83,7 +83,11 @@ jobs: run: | echo "Start productive build" docker run --privileged --rm tonistiigi/binfmt --install all - podman manifest create build - podman build --format docker --build-arg BUILD_VERSION_ARG=${VERSION} --manifest localhost/build --platform linux/amd64,linux/arm64,linux/arm/v7 . - podman manifest push localhost/build docker://docker.io/bbernhard/signal-cli-rest-api:${VERSION} - podman manifest push localhost/build docker://docker.io/bbernhard/signal-cli-rest-api:latest + podman manifest create build-jre + podman build --format docker --target jre --build-arg BUILD_VERSION_ARG=${VERSION} --platform linux/amd64,linux/arm64,linux/arm/v7 --manifest localhost/build-jre . + podman manifest push localhost/build-jre docker://docker.io/bbernhard/signal-cli-rest-api:${VERSION} + podman manifest push localhost/build-jre docker://docker.io/bbernhard/signal-cli-rest-api:latest + podman manifest create build-native + podman build --format docker --target native --build-arg BUILD_VERSION_ARG=${VERSION} --platform linux/amd64,linux/arm64 --manifest localhost/build-native . + podman manifest push localhost/build-native docker://docker.io/bbernhard/signal-cli-rest-api:${VERSION}-native + podman manifest push localhost/build-native docker://docker.io/bbernhard/signal-cli-rest-api:latest-native diff --git a/AGENTS.md b/AGENTS.md index 66d04c2..75572d1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,8 +16,8 @@ Go-based REST API wrapping [signal-cli](https://github.com/AsamK/signal-cli) for - `scripts/jsonrpc2-helper.go` — daemon manager for json-rpc mode - `plugin_loader.go` — Go plugin system using Lua scripts (v1/v2 plugin API) - `plugins/` — example Lua plugins and persistence plugin -- `Dockerfile` — multi-stage build: Go compile → release image with signal-cli + Java runtime -- `entrypoint.sh` — container entrypoint; handles UID/GID, chown, supervisor for json-rpc mode +- `Dockerfile` — multi-stage build with two release targets: `jre` and `native` +- `entrypoint.sh` — container entrypoint; validates MODE against available binaries, handles UID/GID, chown, supervisor for json-rpc mode ## Build & Run @@ -34,6 +34,11 @@ Go-based REST API wrapping [signal-cli](https://github.com/AsamK/signal-cli) for docker compose build docker compose up ``` +- **Build specific variant (Docker):** + ```bash + docker build --target jre . # JRE variant (normal + json-rpc) + docker build --target native . # native variant (native + json-rpc-native) + ``` - **No linter or typecheck config exists** — standard Go tooling only (e.g., `go vet`). ## Swagger / API Docs @@ -47,6 +52,19 @@ go run github.com/swaggo/swag/cmd/swag@v1.16.6 init --requiredByDefault --output The receive V1 schemas require an extra step (`add_v1_receive_schemas.go`); see `src/docs/README.md`. CI checks that generated docs are up-to-date (workflow: `check-docs.yml`). +## Image Variants + +The Dockerfile produces two release targets from a shared `base` stage: + +| Target | Tag suffix | Contains | Modes | Platforms | +|---|---|---|---|---| +| `jre` | `latest`, `X.Y.Z` | headless JRE + signal-cli Java dist | `normal`, `json-rpc` | amd64, arm64, arm/v7 | +| `native` | `latest-native`, `X.Y.Z-native` | signal-cli-native only (no JRE) | `native`, `json-rpc-native` | amd64, arm64 | + +The `entrypoint.sh` validates that the requested `MODE` matches binaries available in the running image variant and exits with a clear error if mismatched. + +The JRE variant uses `openjdk-25-jre-headless` (not the full `openjdk-25-jre`) to avoid ~280 MB of GUI libraries that a CLI tool never uses. + ## Key Environment Variables | Variable | Default | Purpose | @@ -63,8 +81,8 @@ The receive V1 schemas require an extra step (`add_v1_receive_schemas.go`); see ## Gotchas - The Dockerfile pins `SIGNAL_CLI_VERSION` and `LIBSIGNAL_CLIENT_VERSION` as build args. After bumping these, verify the `libsignal-client-*.jar` filename still matches. -- Multi-arch build targets: `linux/amd64`, `linux/arm64`, `linux/arm/v7`. The `native` (GraalVM) binary is unavailable on armv7 — code falls back to `normal` mode. +- Multi-arch builds: the `jre` target covers `linux/amd64`, `linux/arm64`, `linux/arm/v7`; the `native` target covers `linux/amd64`, `linux/arm64` only (no armv7 native binary). - `AUTO_RECEIVE_SCHEDULE` and `SIGNAL_CLI_CMD_TIMEOUT` cause a fatal error in json-rpc mode. - `RECEIVE_WEBHOOK_URL` is only valid in json-rpc mode; fatal in other modes. -- CI uses Podman for multi-arch builds, not Docker Buildx. -- Release versions are published via `publish.sh` triggering GitHub Actions (dev vs stable tags). \ No newline at end of file +- CI uses Podman for multi-arch builds, not Docker Buildx. Each CI/release workflow builds both `jre` and `native` targets. +- Release versions are published via `publish.sh` triggering GitHub Actions (dev vs stable tags, with `-native` suffix for the native variant). \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 539f4e0..8d84e8a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -130,13 +130,9 @@ RUN cd /tmp/signal-cli-rest-api-src/scripts && go build -o jsonrpc2-helper # build plugin_loader RUN cd /tmp/signal-cli-rest-api-src && go build -buildmode=plugin -o signal-cli-rest-api_plugin_loader.so plugin_loader.go -# Start a fresh container for release container +# ---- Shared base for both image variants ---- -# eclipse-temurin doesn't provide a OpenJDK 21 image for armv7 (see https://github.com/adoptium/containers/issues/502). Until this -# is fixed we use the standard ubuntu image -#FROM eclipse-temurin:21-jre-jammy - -FROM ubuntu:noble +FROM ubuntu:noble AS base ENV GIN_MODE=release @@ -150,31 +146,20 @@ ENV SIGNAL_CLI_REST_API_PLUGIN_SHARED_OBJ_DIR=/usr/bin/ RUN dpkg-reconfigure debconf --frontend=noninteractive \ && apt-get update \ - && apt-get install -y --no-install-recommends util-linux supervisor openjdk-25-jre curl locales \ - && rm -rf /var/lib/apt/lists/* + && apt-get install -y --no-install-recommends util-linux curl locales \ + && rm -rf /var/lib/apt/lists/* COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/signal-cli-rest-api /usr/bin/signal-cli-rest-api -COPY --from=buildcontainer /opt/signal-cli-${SIGNAL_CLI_VERSION} /opt/signal-cli-${SIGNAL_CLI_VERSION} -COPY --from=buildcontainer /tmp/signal-cli-native /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli-native COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/scripts/jsonrpc2-helper /usr/bin/jsonrpc2-helper COPY --from=buildcontainer /tmp/signal-cli-rest-api-src/signal-cli-rest-api_plugin_loader.so /usr/bin/signal-cli-rest-api_plugin_loader.so COPY entrypoint.sh /entrypoint.sh - RUN userdel ubuntu -r \ && groupadd -g 1000 signal-api \ && useradd --no-log-init -M -d /home -s /bin/bash -u 1000 -g 1000 signal-api \ - && ln -s /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli /usr/bin/signal-cli \ - && ln -s /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli-native /usr/bin/signal-cli-native \ && mkdir -p /signal-cli-config/ \ && mkdir -p /home/.local/share/signal-cli -# remove the temporary created signal-cli-native on armv7, as GRAALVM doesn't support 32bit -RUN arch="$(uname -m)"; \ - case "$arch" in \ - armv7l) echo "GRAALVM doesn't support 32bit" && rm /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli-native /usr/bin/signal-cli-native ;; \ - esac; - RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \ dpkg-reconfigure --frontend=noninteractive locales && \ update-locale LANG=en_US.UTF-8 @@ -192,3 +177,36 @@ ENTRYPOINT ["/entrypoint.sh"] HEALTHCHECK --interval=20s --timeout=10s --retries=3 \ CMD curl -f http://localhost:${PORT}/v1/health || exit 1 + +# ---- JRE variant: MODE=normal, json-rpc ---- +# Includes headless JRE + signal-cli Java dist. No native binary. +# Supports: linux/amd64, linux/arm64, linux/arm/v7 + +FROM base AS jre + +RUN dpkg-reconfigure debconf --frontend=noninteractive \ + && apt-get update \ + && apt-get install -y --no-install-recommends openjdk-25-jre-headless supervisor \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=buildcontainer /opt/signal-cli-${SIGNAL_CLI_VERSION} /opt/signal-cli-${SIGNAL_CLI_VERSION} + +RUN ln -s /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli /usr/bin/signal-cli \ + && mkdir -p /home/.local/share/signal-cli + +# ---- Native variant: MODE=native, json-rpc-native ---- +# Includes signal-cli-native only. No JRE, no Java dist. +# Supports: linux/amd64, linux/arm64 (no arm/v7 — GraalVM doesn't produce 32-bit binaries) + +FROM base AS native + +RUN dpkg-reconfigure debconf --frontend=noninteractive \ + && apt-get update \ + && apt-get install -y --no-install-recommends supervisor \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=buildcontainer /tmp/signal-cli-native /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli-native + +RUN mkdir -p /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin \ + && ln -s /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli-native /usr/bin/signal-cli-native \ + && mkdir -p /home/.local/share/signal-cli diff --git a/docker-compose.yml b/docker-compose.yml index 958a194..1434881 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,22 @@ services: + # JRE variant (default) -- use with MODE=normal or MODE=json-rpc signal-cli-rest-api: image: bbernhard/signal-cli-rest-api:latest environment: - - MODE=normal #supported modes: json-rpc, native, normal + - MODE=normal #supported modes: normal, json-rpc #- AUTO_RECEIVE_SCHEDULE=0 22 * * * #enable this parameter on demand (see description below) ports: - "8080:8080" #map docker port 8080 to host port 8080. volumes: - "./signal-cli-config:/home/.local/share/signal-cli" #map "signal-cli-config" folder on host system into docker container. the folder contains the password and cryptographic keys when a new number is registered + # Native variant -- use with MODE=native or MODE=json-rpc-native + # Uncomment to use the native variant (smaller image, no Java runtime) + # signal-cli-rest-api-native: + # image: bbernhard/signal-cli-rest-api:latest-native + # environment: + # - MODE=native #supported modes: native, json-rpc-native + # ports: + # - "8080:8080" + # volumes: + # - "./signal-cli-config:/home/.local/share/signal-cli" \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh index 027254a..a528d2d 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -5,6 +5,23 @@ set -e [ -z "${SIGNAL_CLI_CONFIG_DIR}" ] && echo "SIGNAL_CLI_CONFIG_DIR environmental variable needs to be set! Aborting!" && exit 1; +# Validate MODE against the binaries available in this image variant +MODE=${MODE:-normal} +if [ "$MODE" = "native" ] || [ "$MODE" = "json-rpc-native" ]; then + if [ ! -x /usr/bin/signal-cli-native ]; then + echo "ERROR: MODE=$MODE requires signal-cli-native, but this image doesn't include it." + echo "Use the -native image tag (e.g., bbernhard/signal-cli-rest-api:latest-native)" + exit 1 + fi +fi +if [ "$MODE" = "normal" ] || [ "$MODE" = "json-rpc" ]; then + if ! command -v java >/dev/null 2>&1; then + echo "ERROR: MODE=$MODE requires Java (signal-cli), but this image doesn't include it." + echo "Use the standard image tag (e.g., bbernhard/signal-cli-rest-api:latest)" + exit 1 + fi +fi + usermod -u ${SIGNAL_CLI_UID} signal-api groupmod -o -g ${SIGNAL_CLI_GID} signal-api