Files
prompt/Server/README.md
T
TZGyn 1bd346ce20 Explain the SSL failure instead of just reporting it
A Postgres URL without an sslmode made lib/pq ask for TLS and die with
"SSL is not enabled on the server", which reads like a server fault when
it is really a connection-string default. The error now says what to
append, and the server retries briefly so a deploy is not marked
unhealthy just because Postgres was still starting.

The sync server URL also defaults to the hosted instance now, so a fresh
install needs no setup.
2026-10-08 10:38:54 +08:00

117 lines
4.5 KiB
Markdown

# 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 <host>:
ssh <host> 'gunzip -c prompt-sync.tar.gz | docker load'
ssh <host> '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
```