4.8 KiB
AGENTS.md
Project Overview
Go-based REST API wrapping signal-cli for Signal Messenger. Runs exclusively inside Docker; there is no standalone go run dev server for the full API. The Go app orchestrates signal-cli (a Java binary) or signal-cli-native (GraalVM precompiled), communicating via CLI args in normal/native mode or JSON-RPC in daemon mode.
Architecture
src/— Go source (the REST API server)main.go— entrypoint; sets up Gin router, routes, cron, plugin loaderapi/api.go— all HTTP handlersclient/— signal-cli interaction layer; three modes:Normal,Native,JsonRpc(seeclient.goconstants)datastructs/datastructs.go— shared structsutils/— helpers, plugin config, text-style parserdocs/— Swagger docs (auto-generated byswag init)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 pluginDockerfile— multi-stage build with three release targets:all,jre, andnativeentrypoint.sh— container entrypoint; validates MODE against available binaries, handles UID/GID, chown, supervisor for json-rpc mode
Build & Run
- Build & test (local): All commands run from
src/cd src go test ./client -v go test ./utils -v go build -o signal-cli-rest-api main.go - Build & run (Docker, the primary workflow):
# Edit docker-compose.yml: change image to build: "." docker compose build docker compose up - Build specific variant (Docker):
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).
Swagger / API Docs
Docs are auto-generated from swag annotations in main.go and api/api.go.
cd src
go run github.com/swaggo/swag/cmd/swag@v1.16.6 init --requiredByDefault --outputTypes "go,json"
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 three release targets from a shared base stage:
| Target | Tag suffix | Contains | Modes | Platforms |
|---|---|---|---|---|
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.
Key Environment Variables
| Variable | Default | Purpose |
|---|---|---|
MODE |
normal |
normal, native, json-rpc, json-rpc-native |
PORT |
8080 |
Listen port |
ENABLE_PLUGINS |
false |
Enable Lua plugin system |
AUTO_RECEIVE_SCHEDULE |
— | Cron expression; incompatible with json-rpc mode |
SIGNAL_CLI_CMD_TIMEOUT |
— | CLI timeout; incompatible with json-rpc mode |
RECEIVE_WEBHOOK_URL |
— | Webhook on receive; json-rpc mode only |
LOG_LEVEL |
info |
debug, info, warn, error |
DEFAULT_SIGNAL_TEXT_MODE |
normal |
normal or styled |
Gotchas
- The Dockerfile pins
SIGNAL_CLI_VERSIONandLIBSIGNAL_CLIENT_VERSIONas build args. After bumping these, verify thelibsignal-client-*.jarfilename still matches. - Multi-arch builds: the
jretarget coverslinux/amd64,linux/arm64,linux/arm/v7; thenativetarget coverslinux/amd64,linux/arm64only (no armv7 native binary). AUTO_RECEIVE_SCHEDULEandSIGNAL_CLI_CMD_TIMEOUTcause a fatal error in json-rpc mode.RECEIVE_WEBHOOK_URLis 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 all three targets (
all,jre,native). - Release versions are published via
publish.shtriggering GitHub Actions (dev vs stable tags, with-jreand-nativesuffixes for those variants;allvariant gets the plain version/latest tags).