Poll sync cheaply and make the server deployable

Polling re-downloaded the whole store, photos included, every five
seconds. The app now checks a tiny version endpoint and only fetches the
payload when it actually moved, and the server gained request logging,
graceful shutdown, and self-hosting docs so it can run on a VPS instead
of only on localhost.

💘 Generated with Crush

Assisted-by: Crush:kimi-k3
This commit is contained in:
2026-10-08 10:19:55 +08:00
parent 88780f7701
commit f3e6fc8fa6
6 changed files with 189 additions and 10 deletions
+69
View File
@@ -0,0 +1,69 @@
# 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.
## Deploy to a Linux host
```bash
# 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:
```bash
pg_dump prompt_sync | gzip > prompt_sync-$(date +%F).sql.gz
```