From ab60199ec99861351e899080af6c75a22b157392 Mon Sep 17 00:00:00 2001 From: Peter Zandbergen Date: Sun, 21 Jun 2026 11:18:36 +0200 Subject: [PATCH] Add all target and optimize Actions pipelines --- .env | 4 +- .github/workflows/ci.yml | 9 ++- .github/workflows/release-dev-version.yml | 12 ++-- .../workflows/release-productive-version.yml | 12 ++-- AGENTS.md | 18 +++-- Dockerfile | 13 ++++ IMAGE-OPTIMIZATION-PROPOSAL.md | 7 +- docker-compose.yml | 26 ++++--- test-builds.sh | 67 +++++++++++++++++++ 9 files changed, 138 insertions(+), 30 deletions(-) create mode 100644 test-builds.sh diff --git a/.env b/.env index 26a8ac9..6ace68a 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -BUILD_TARGET=jre # native, jre -RUN_MODE=normal # native, normal +BUILD_TARGET=native # all, jre, native +RUN_MODE=json-rpc-native # normal, json-rpc, native, json-rpc-native diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f948624..e73f257 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,10 +83,13 @@ jobs: df -h echo "Start CI build" docker run --privileged --rm tonistiigi/binfmt --install all + podman manifest create build-all + podman build --format docker --layers --target all --platform linux/amd64,linux/arm64,linux/arm/v7 --manifest localhost/build-all . + podman manifest push localhost/build-all docker://${IMAGE_REGISTRY}/${IMAGE_REPO}:${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://${IMAGE_REGISTRY}/${IMAGE_REPO}:${EPOCHSECONDS}-ci + podman build --format docker --layers --target jre --platform linux/amd64,linux/arm64,linux/arm/v7 --manifest localhost/build-jre . + podman manifest push localhost/build-jre docker://${IMAGE_REGISTRY}/${IMAGE_REPO}:${EPOCHSECONDS}-ci-jre podman manifest create build-native - podman build --format docker --target native --platform linux/amd64,linux/arm64 --manifest localhost/build-native . + podman build --format docker --layers --target native --platform linux/amd64,linux/arm64 --manifest localhost/build-native . podman manifest push localhost/build-native docker://${IMAGE_REGISTRY}/${IMAGE_REPO}:${EPOCHSECONDS}-ci-native diff --git a/.github/workflows/release-dev-version.yml b/.github/workflows/release-dev-version.yml index 6569a33..a0eaa89 100644 --- a/.github/workflows/release-dev-version.yml +++ b/.github/workflows/release-dev-version.yml @@ -86,11 +86,15 @@ jobs: run: | echo "Start dev build" docker run --privileged --rm tonistiigi/binfmt --install all + podman manifest create build-all + podman build --format docker --layers --target all --build-arg BUILD_VERSION_ARG=${VERSION} --platform linux/amd64,linux/arm64,linux/arm/v7 --manifest localhost/build-all . + podman manifest push localhost/build-all docker://${IMAGE_REGISTRY}/${IMAGE_REPO}:${VERSION}-dev + podman manifest push localhost/build-all docker://${IMAGE_REGISTRY}/${IMAGE_REPO}: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://${IMAGE_REGISTRY}/${IMAGE_REPO}:${VERSION}-dev - podman manifest push localhost/build-jre docker://${IMAGE_REGISTRY}/${IMAGE_REPO}:latest-dev + podman build --format docker --layers --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://${IMAGE_REGISTRY}/${IMAGE_REPO}:${VERSION}-dev-jre + podman manifest push localhost/build-jre docker://${IMAGE_REGISTRY}/${IMAGE_REPO}:latest-dev-jre 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 build --format docker --layers --target native --build-arg BUILD_VERSION_ARG=${VERSION} --platform linux/amd64,linux/arm64 --manifest localhost/build-native . podman manifest push localhost/build-native docker://${IMAGE_REGISTRY}/${IMAGE_REPO}:${VERSION}-dev-native podman manifest push localhost/build-native docker://${IMAGE_REGISTRY}/${IMAGE_REPO}:latest-dev-native diff --git a/.github/workflows/release-productive-version.yml b/.github/workflows/release-productive-version.yml index 10bcb78..f8fa761 100644 --- a/.github/workflows/release-productive-version.yml +++ b/.github/workflows/release-productive-version.yml @@ -86,11 +86,15 @@ jobs: run: | echo "Start productive build" docker run --privileged --rm tonistiigi/binfmt --install all + podman manifest create build-all + podman build --format docker --layers --target all --build-arg BUILD_VERSION_ARG=${VERSION} --platform linux/amd64,linux/arm64,linux/arm/v7 --manifest localhost/build-all . + podman manifest push localhost/build-all docker://${IMAGE_REGISTRY}/${IMAGE_REPO}:${VERSION} + podman manifest push localhost/build-all docker://${IMAGE_REGISTRY}/${IMAGE_REPO}: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://${IMAGE_REGISTRY}/${IMAGE_REPO}:${VERSION} - podman manifest push localhost/build-jre docker://${IMAGE_REGISTRY}/${IMAGE_REPO}:latest + podman build --format docker --layers --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://${IMAGE_REGISTRY}/${IMAGE_REPO}:${VERSION}-jre + podman manifest push localhost/build-jre docker://${IMAGE_REGISTRY}/${IMAGE_REPO}:latest-jre 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 build --format docker --layers --target native --build-arg BUILD_VERSION_ARG=${VERSION} --platform linux/amd64,linux/arm64 --manifest localhost/build-native . podman manifest push localhost/build-native docker://${IMAGE_REGISTRY}/${IMAGE_REPO}:${VERSION}-native podman manifest push localhost/build-native docker://${IMAGE_REGISTRY}/${IMAGE_REPO}:latest-native diff --git a/AGENTS.md b/AGENTS.md index 75572d1..9271065 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,7 +16,7 @@ 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 with two release targets: `jre` and `native` +- `Dockerfile` — multi-stage build with three release targets: `all`, `jre`, and `native` - `entrypoint.sh` — container entrypoint; validates MODE against available binaries, handles UID/GID, chown, supervisor for json-rpc mode ## Build & Run @@ -36,8 +36,9 @@ Go-based REST API wrapping [signal-cli](https://github.com/AsamK/signal-cli) for ``` - **Build specific variant (Docker):** ```bash - docker build --target jre . # JRE variant (normal + json-rpc) - docker build --target native . # native variant (native + json-rpc-native) + docker build --target all . # all-in-one (all four modes) + 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`). @@ -54,13 +55,16 @@ The receive V1 schemas require an extra step (`add_v1_receive_schemas.go`); see ## Image Variants -The Dockerfile produces two release targets from a shared `base` stage: +The Dockerfile produces three 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 | +| `all` | `latest`, `X.Y.Z` | headless JRE + signal-cli Java dist + signal-cli-native | `normal`, `json-rpc`, `native`, `json-rpc-native` | amd64, arm64, arm/v7 | +| `jre` | `latest-jre`, `X.Y.Z-jre` | 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 `all` target is the backwards-compatible default — it contains both runtimes so all four modes work, matching the original monolithic image. + 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. @@ -84,5 +88,5 @@ The JRE variant uses `openjdk-25-jre-headless` (not the full `openjdk-25-jre`) t - 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. 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 +- CI uses Podman for multi-arch builds, not Docker Buildx. Each CI/release workflow builds all three targets (`all`, `jre`, `native`). +- Release versions are published via `publish.sh` triggering GitHub Actions (dev vs stable tags, with `-jre` and `-native` suffixes for those variants; `all` variant gets the plain version/latest tags). \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index f4b4da8..ddd99ea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -196,6 +196,19 @@ COPY --from=buildcontainer /opt/signal-cli-${SIGNAL_CLI_VERSION} /opt/signal-cli RUN ln -s /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli /usr/bin/signal-cli \ && mkdir -p /home/.local/share/signal-cli +# ---- All-in-one variant: MODE=normal, json-rpc, native, json-rpc-native ---- +# Includes headless JRE + signal-cli Java dist + signal-cli-native. +# Supports: linux/amd64, linux/arm64, linux/arm/v7 +# (arm/v7 gets JRE modes only — signal-cli-native is a dummy on arm/v7) + +FROM jre AS all + +ARG SIGNAL_CLI_VERSION + +COPY --from=buildcontainer /tmp/signal-cli-native /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli-native + +RUN ln -s /opt/signal-cli-${SIGNAL_CLI_VERSION}/bin/signal-cli-native /usr/bin/signal-cli-native + # ---- 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) diff --git a/IMAGE-OPTIMIZATION-PROPOSAL.md b/IMAGE-OPTIMIZATION-PROPOSAL.md index e6a6bfe..8660483 100644 --- a/IMAGE-OPTIMIZATION-PROPOSAL.md +++ b/IMAGE-OPTIMIZATION-PROPOSAL.md @@ -19,13 +19,16 @@ No user benefits from both the JRE and the native binary at the same time — ea Additionally, the JRE pulls in ~280 MB of GUI libraries (LLVM, Mesa, GTK, icon themes) that signal-cli never uses. It is a headless CLI tool. -## Proposed Solution: Two Image Variants +## Proposed Solution: Three Image Variants | Variant | Tag pattern | Contains | Modes | Est. size | |---|---|---|---|---| -| **JRE** | `latest`, `X.Y.Z` | Headless JRE + signal-cli Java dist | `normal`, `json-rpc` | **~660 MB** | +| **all** | `latest`, `X.Y.Z` | Headles JRE and native | `normal`, `json-rpc`, `native`, `json-rpc-native` | **1.3 GB** | +| **JRE** | `latest-jre`, `X.Y.Z-jre` | Headless JRE + signal-cli Java dist | `normal`, `json-rpc` | **~660 MB** | | **Native** | `latest-native`, `X.Y.Z-native` | signal-cli-native only (no JRE) | `native`, `json-rpc-native` | **~615 MB** | +We keep the **all** variant in to avoid breaking changes. + For `arm/v7`, only the JRE variant is published (GraalVM doesn't produce a native binary for 32-bit ARM). ### Why the native variant (615 MB) isn't smaller than JRE (660 MB) diff --git a/docker-compose.yml b/docker-compose.yml index 383f166..ecb6b11 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,26 +1,36 @@ services: - # JRE variant (default) -- use with MODE=normal or MODE=json-rpc + # All-in-one variant -- supports all modes: normal, json-rpc, native, json-rpc-native + # This is the backwards-compatible default (same as the original image). signal-cli-rest-api: - # image: bbernhard/signal-cli-rest-api:latest + # image: bbernhard/signal-cli-rest-api:latest build: context: "." dockerfile: Dockerfile target: ${BUILD_TARGET} environment: - - MODE=${RUN_MODE} #supported modes: normal, json-rpc + - MODE=${RUN_MODE} #supported modes: normal, json-rpc, native, json-rpc-native #- 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 + # JRE-only variant -- lighter, use with MODE=normal or MODE=json-rpc + # signal-cli-rest-api-jre: + # image: bbernhard/signal-cli-rest-api:latest-jre # environment: - # - MODE=native #supported modes: native, json-rpc-native + # - MODE=normal # ports: # - "8080:8080" # volumes: # - "./signal-cli-config:/home/.local/share/signal-cli" + + # Native-only variant -- lightest, use with MODE=native or MODE=json-rpc-native + # signal-cli-rest-api-native: + # image: bbernhard/signal-cli-rest-api:latest-native + # environment: + # - MODE=native + # ports: + # - "8080:8080" + # volumes: + # - "./signal-cli-config:/home/.local/share/signal-cli" \ No newline at end of file diff --git a/test-builds.sh b/test-builds.sh new file mode 100644 index 0000000..8190fa1 --- /dev/null +++ b/test-builds.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash + +WDIR=$(dirname $(readlink -f $0)) + + +function test_run() { + local target=$1 + local run_mode=$2 + + # Create the .env file + # Build and run all + # Create the .env file + cat < $WDIR/.env +BUILD_TARGET=${target} # all, jre, native +RUN_MODE=${run_mode} # normal, json-rpc, native, json-rpc-native +EOF + + docker compose \ + --project-directory $WDIR \ + up \ + --build --abort-on-container-exit --exit-code-from signal-cli-rest-api & + compose_pid=$! +} + +function test_wait() { + local target=$1 + local run_mode=$2 + local timeout=$3 + local start_time=$(date +%s) + + for i in $(seq 1 30); do + if curl -sf http://localhost:8080/v1/health >/dev/null 2>&1; then + echo "Health check passed" + echo Killing $compose_pid + kill -9 $compose_pid 2>/dev/null + wait $compose_pid 2>/dev/null + docker compose down + if [ $? -ne 0 ]; then + echo "ERROR: docker compose down failed for ${target} ${run_mode}" + exit 1 + fi + return 0 + fi + sleep 2 + done + echo "ERROR: docker compose build failed for ${target} ${run_mode}" +} + +function test_run_and_wait() { + local target=$1 + local run_mode=$2 + test_run $target $run_mode + test_wait $target $run_mode +} + +# Try to run all three build targets and wait for them to finish +# normal, json-rpc, native, json-rpc-native +test_run_and_wait "all" "normal" +test_run_and_wait "all" "json-rpc" +test_run_and_wait "all" "native" +test_run_and_wait "all" "json-rpc-native" + +test_run_and_wait "jre" "normal" +test_run_and_wait "jre" "json-rpc" + +test_run_and_wait "native" "native" +test_run_and_wait "native" "json-rpc-native"