4.4 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 two release targets:jreandnativeentrypoint.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 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 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 |
|---|---|---|
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 both
jreandnativetargets. - Release versions are published via
publish.shtriggering GitHub Actions (dev vs stable tags, with-nativesuffix for the native variant).