Files
prompt/Server/README.md
T
TZGyn cbdf5538aa Add a Dockerfile so the sync server is one command to deploy
Self-hosting meant building the binary by hand and wiring Postgres up
yourself. The image is a static binary on distroless with a built-in
healthcheck flag, and docker-compose brings up Postgres alongside it.

The image itself is unbuilt here: no container runtime is available in
this environment, so only the static Linux build and the healthcheck
flag have been exercised.

💘 Generated with Crush

Assisted-by: Crush:kimi-k3
2026-10-08 10:23:54 +08:00

3.2 KiB

Prompt sync server

Small Go service that stores one JSON blob per sync group, keyed by an 8-digit code (1234-5678). There is no authentication: the code is the shared secret, so anyone who knows it can read and overwrite that group. It is meant to be self-hosted for personal use, not exposed as a public service.

Requirements

  • Postgres (any recent version; developed against 18)
  • A host both Macs can reach — a VPS, a home server, or a Tailscale node

Run it

createdb prompt_sync
cd Server
PROMPT_SYNC_DATABASE_URL="postgres://localhost:5432/prompt_sync?sslmode=disable" \
  go run .            # or: go build -o prompt-sync . && ./prompt-sync
Env var Default
PROMPT_SYNC_ADDR :8080
PROMPT_SYNC_DATABASE_URL postgres://localhost:5432/prompt_sync?sslmode=disable

The schema is created on startup. Every request is logged as METHOD PATH STATUS DURATION; SIGTERM/SIGINT shut down gracefully so an in-flight push is not cut off mid-write.

Run with Docker

docker-compose.yml starts Postgres and the server together:

cd Server
POSTGRES_PASSWORD=$(openssl rand -hex 16) docker compose up -d
docker compose logs -f server      # request log

Or build the image on its own and point it at an existing Postgres:

docker build -t prompt-sync Server/
docker run -d --name prompt-sync -p 8080:8080 \
  -e PROMPT_SYNC_DATABASE_URL="postgres://user:pass@host:5432/prompt_sync?sslmode=disable" \
  prompt-sync

The image is a static binary on distroless (non-root, no shell, ~6 MB binary), so its HEALTHCHECK calls prompt-sync -healthcheck, which pings the local /healthz and exits non-zero on failure — no curl needed in the image. That is what docker ps reports as the container health.

Deploy to a Linux host

# from the repo root, on the Mac
cd Server
GOOS=linux GOARCH=arm64 go build -o prompt-sync .    # amd64 for x86 hosts
scp prompt-sync <host>:/tmp/ && ssh <host> 'sudo mv /tmp/prompt-sync /usr/local/bin/'

# on the host: create the database and role, then
sudo cp Server/prompt-sync.service /etc/systemd/system/
sudo systemctl edit --full prompt-sync   # set PROMPT_SYNC_DATABASE_URL
sudo systemctl enable --now prompt-sync
journalctl -u prompt-sync -f             # verify it is listening

Put it behind TLS if it is reachable from the internet — the code is the only secret, and it travels in the URL path. Both plain HTTP and HTTPS work from the app; an HTTP URL is often fine over a LAN or Tailscale.

Endpoints

Method Path Behaviour
POST /api/codes Generates a unique code, inserts an empty group
GET /api/sync/{code} {"version":N,"updatedAt":…,"data":{…}|null}, 404 if unknown
GET /api/sync/{code}/version {"version":N} — cheap poll used by the app
PUT /api/sync/{code} Upserts the payload, returns the incremented version
GET /healthz Liveness (200)

Payloads are opaque JSONB; the server never parses note contents. Bodies are capped at 50 MB, which is generous enough for base64 photos.

Backups

The whole state is one table, so pg_dump prompt_sync is a complete backup:

pg_dump prompt_sync | gzip > prompt_sync-$(date +%F).sql.gz