# 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 ```bash 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. If the database is not reachable yet it retries five times, 2s apart, before giving up. > **`sslmode` matters.** lib/pq defaults to `sslmode=require`, so a connection > string copied from a host that runs Postgres *without* TLS (Coolify's > internal Postgres, a plain `docker run postgres`, Homebrew) fails with > `pq: SSL is not enabled on the server`. Append `?sslmode=disable` in that > case — the server prints exactly that hint when it hits this. ## Deploying with Coolify Use a Dockerfile application pointed at this repo with base directory `Server/` (so Coolify builds `Server/Dockerfile`), expose port `8080`, and set: | Variable | Value | | --- | --- | | `PROMPT_SYNC_DATABASE_URL` | your Postgres URL **plus `?sslmode=disable`** | | `PROMPT_SYNC_ADDR` | `:8080` (this is already the default) | Coolify detects the `HEALTHCHECK` in the Dockerfile and runs `prompt-sync -healthcheck` with its 5s start period, so the container only goes live once `/healthz` answers. It also logs a generic warning that a healthcheck needs `curl` or `wget` — that does not apply here, the binary does its own check, so there is no reason to add either to the image (it is distroless and has no shell). ## Run with Docker The image is a static binary on `distroless` (non-root, no shell), so its `HEALTHCHECK` calls `prompt-sync -healthcheck`, which pings the local `/healthz` — no curl needed in the image. That is what `docker ps` reports as the container health. ```bash 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 docker logs -f prompt-sync # request log ``` Postgres stays outside the container; the server only needs a URL. If Postgres runs on the Docker host itself, `host` is `host.docker.internal` on macOS and Windows, or the host's LAN address on Linux (`--network host` also works). ## Deploy to a host Build for the host's architecture and ship the image, or build it on the host itself (a `git clone` plus `docker build` is usually simplest): ```bash # build on the Mac for a linux/arm64 host, then copy it over docker build --platform linux/arm64 -t prompt-sync Server/ docker save prompt-sync | gzip > prompt-sync.tar.gz scp prompt-sync.tar.gz : ssh 'gunzip -c prompt-sync.tar.gz | docker load' ssh 'docker run -d --name prompt-sync --restart unless-stopped \ -p 8080:8080 \ -e PROMPT_SYNC_DATABASE_URL="postgres://user:pass@db-host:5432/prompt_sync?sslmode=disable" \ prompt-sync' ``` 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: ```bash pg_dump prompt_sync | gzip > prompt_sync-$(date +%F).sql.gz ```