08749df050
Swap modernc.org/sqlite for jackc/pgx/v5 with no observable change: same endpoints, same wire format, same updated_at ordering rule. The schema now comes from numbered SQL embedded in the binary and applied on startup, one transaction each, recorded in schema_migrations. That replaces two pieces of SQLite-era machinery, both deleted rather than ported: the column probing (Postgres has ADD COLUMN IF NOT EXISTS, and there is no legacy database left to probe) and the Asura key rewrite, which has run clean on every start for months now that the userscripts strip build hashes before writing. Its regexp survives as latest.asuraBuildHash, where the poller still needs it to scope chapter links to a series whose slug carries a rotating hash. Types get real: favorite is a boolean, chapter numbers double precision, timestamps stay unix-ms bigint. SQLite's null-safe IS NOT becomes IS DISTINCT FROM, which is what implements the rule that only reading progress reorders a list. Inside COALESCE/NULLIF the status and kind parameters need an explicit ::text -- there is no target column to infer from and Postgres refuses to guess. Tests lose their free t.TempDir() database, so Docker is now a hard prerequisite for `go test ./...`: internal/pgtest starts one postgres:17-alpine per test binary and hands each test a database of its own. Also lands CONTEXT.md and the four ADRs written while scoping #18. BREAKING CHANGE: DB_PATH is retired for DATABASE_URL, which is required and has no default. Compose gains a postgres service on an internal network with its own volume; POSTGRES_PASSWORD joins .env. The old bookmarks-data volume is deliberately left undeclared so `docker compose down -v` cannot take the pre-migration database with it. main is not deployable until #25 and #26 land. Closes #20 Co-authored-by: Sulthan Zaki <sultankiki05@gmail.com> Co-committed-by: Sulthan Zaki <sultankiki05@gmail.com>
64 lines
3.0 KiB
YAML
64 lines
3.0 KiB
YAML
# Production override: join an existing Traefik network and let Traefik route
|
|
# bookmark-api.<domain> -> this service with TLS. No host port published.
|
|
#
|
|
# docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
|
|
#
|
|
# Set in .env:
|
|
# BOOKMARK_API_HOST=bookmark-api.example.com # your subdomain (required)
|
|
# BOOKMARK_WEB_HOST=bookmark.example.com # browser UI subdomain, same container (required)
|
|
# PROXY_NETWORK=proxy # Traefik's network name, if not "proxy"
|
|
# TRAEFIK_ENTRYPOINT=websecure # your HTTPS entrypoint name
|
|
# TRAEFIK_CERTRESOLVER=le # your ACME/cert resolver name
|
|
#
|
|
# The network must already exist and Traefik must watch it:
|
|
# docker network create proxy # if it doesn't yet
|
|
|
|
services:
|
|
bookmark-api:
|
|
# Traffic arrives over the Traefik network, not a published port.
|
|
ports: !reset []
|
|
environment:
|
|
# Must be an IP, not the DNS name — see the base file's comment on this
|
|
# same key: Chrome's DevTools HTTP handler 500s any Host header that
|
|
# isn't an IP or "localhost".
|
|
BROWSER_WS_URL: ${BROWSER_WS_URL:-ws://172.28.0.10:9222}
|
|
depends_on:
|
|
headless-shell:
|
|
condition: service_started
|
|
postgres:
|
|
condition: service_healthy
|
|
# `networks:` here replaces the base file's list entirely, so all three must
|
|
# be named: `proxy` for Traefik routing, and `browser` / `db` (defined in
|
|
# the base file) to keep reaching headless-shell and Postgres without
|
|
# putting either on `proxy`.
|
|
networks:
|
|
- proxy
|
|
- browser
|
|
- db
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.docker.network=${PROXY_NETWORK:-proxy}"
|
|
- "traefik.http.routers.bmapi.rule=Host(`${BOOKMARK_API_HOST:?set BOOKMARK_API_HOST in .env}`)"
|
|
- "traefik.http.routers.bmapi.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
|
- "traefik.http.routers.bmapi.tls=true"
|
|
- "traefik.http.routers.bmapi.tls.certresolver=${TRAEFIK_CERTRESOLVER:-le}"
|
|
- "traefik.http.services.bmapi.loadbalancer.server.port=8080"
|
|
# Second hostname for the browser UI, same container. Traefik needs the
|
|
# service named explicitly once more than one router targets it.
|
|
- "traefik.http.routers.bmapi.service=bmapi"
|
|
- "traefik.http.routers.bmweb.rule=Host(`${BOOKMARK_WEB_HOST:?set BOOKMARK_WEB_HOST in .env}`)"
|
|
- "traefik.http.routers.bmweb.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}"
|
|
- "traefik.http.routers.bmweb.tls=true"
|
|
- "traefik.http.routers.bmweb.tls.certresolver=${TRAEFIK_CERTRESOLVER:-le}"
|
|
- "traefik.http.routers.bmweb.service=bmapi"
|
|
|
|
# headless-shell is untouched here: it keeps its `browser` network membership
|
|
# from the base file and must never join `proxy` — that network is shared
|
|
# with whatever else sits behind Traefik on this host, and an exposed
|
|
# CDP endpoint on it would be remote code execution for any of them.
|
|
|
|
networks:
|
|
proxy:
|
|
external: true
|
|
name: ${PROXY_NETWORK:-proxy}
|