17 Commits

Author SHA1 Message Date
Julien Calixte
243f9a8ed5 docs: add handoff for longpoll HTTP/2 truncation bug
The AuthSession cookie fix is shipped, but PouchDB longpoll requests
get truncated at ~43s by Traefik on HTTP/2. HTTP/1.1 holds fine;
CouchDB and nginx are clean. Captures the diagnosis trail and the
three options for the next session so the work isn't re-derived.
2026-06-02 23:26:13 +02:00
Julien Calixte
1b3fa2c540 fix(signup): copy manifest.toml so Docker builds use the lockfile
Commit 40011e2 added manifest.toml to pin transitive deps, but the
Dockerfile only copies gleam.toml — so every Docker build still
re-resolves from scratch and can pick up incompatible versions within
the allowed ranges, defeating the lockfile's purpose.
2026-06-02 22:31:29 +02:00
Julien Calixte
f97956fa8e fix(nginx): build nginx image instead of bind-mounting config
Coolify's build context and runtime mount paths don't share the repo
tree the way a plain docker compose up does — the bind mount of
./nginx/default.conf failed at container start because the host path
didn't exist. Switch to a build: context: ./nginx pattern (same as
the signup service) so the config is baked into the image at build
time.
2026-06-02 22:18:00 +02:00
Julien Calixte
40011e2003 chore(signup): bump wisp/mist to latest, lock with manifest.toml
Two prior deploy failures came from transitive deps drifting on every
fresh build (gleam_httpc 4.1.1 calling result.then, then glisten 8.0.3
calling list.range). The root cause is no manifest.toml — every build
re-resolved deps from scratch and tripped on broken combinations within
the allowed ranges.

- Bump wisp 1.x → 2.x and mist 4.x → 6.x so the resolver picks
  modern, mutually compatible versions of the whole tree
  (stdlib 1.0.3, glisten 9.0.1, httpc 5.0.0).
- Commit manifest.toml so deps are pinned by checksum.
- Adapt to wisp 2.x: json_response now takes String, not StringTree.
- Drop unused gleam/http/response import.
- Add signup/build/ to .gitignore.
2026-06-02 22:14:44 +02:00
Julien Calixte
3d95b2d491 fix(signup): bump gleam_httpc to >= 4.2.0 for result.try
gleam_httpc 4.1.1 still calls result.then, which was removed from
gleam_stdlib 0.71.0. Bumping the lower bound forces the resolver to
pick 4.2.x or later, which uses result.try. The < 5.0.0 cap is also
widened to < 6.0.0; the public API is unchanged between 4.2 and 5.0.

Long term: committing a manifest.toml lockfile would stop transitive
deps from drifting on every fresh build.
2026-06-02 22:10:09 +02:00
Julien Calixte
15ad57a7ad chore(signup): bump Gleam to v1.16.0
gleam_stdlib now requires >= 1.14.0, breaking the v1.7.0 pin during
build. Bump to v1.16.0 — a stable release with several weeks of bake
time rather than today's v1.17.0.
2026-06-02 22:07:15 +02:00
Julien Calixte
2aa10b1711 fix(auth): front CouchDB with nginx to mark AuthSession cookie Secure
CouchDB 3.5 derives the Set-Cookie Secure flag from the raw mochiweb
socket scheme and ignores X-Forwarded-Proto/Ssl for cookie attribution.
Behind a TLS-terminating proxy the cookie was emitted without Secure,
so browsers dropped the SameSite=None AuthSession cookie on cross-origin
requests — POST /_session returned 200 but the next GET /_session showed
userCtx.name: null and PouchDB stayed unauthenticated.

Add an nginx sidecar that adds Secure via proxy_cookie_flags, route the
public domain through it, and make couchdb internal. Also persist
same_site=none in couchdb-init so the setting survives container
recreation (local.d/ is not in the data volume).
2026-06-02 22:05:47 +02:00
Julien Calixte
c3c75787f4 feat(signup): add Gleam signup service gated by shared invite code
A small Gleam HTTP server in signup/ exposes POST /signup that validates
a shared INVITE_CODE env var, then PUTs a user document to CouchDB's
_users database using the existing admin credentials. The frontend gets
back 201 + the username and is expected to call /_session itself to
obtain the AuthSession cookie (chosen over server-side cookie forwarding
because vaquant.at and couch.apoena.dev are different sites and the
cross-site cookie path is fragile under modern browser privacy modes).

Wired into docker-compose as a second service, reachable on
signup-couch.apoena.dev with its own Traefik labels and the same
hardcoded UUID network attachment as couchdb. Reuses COUCHDB_USER /
COUCHDB_PASSWORD for the admin call. INVITE_CODE must be set in Coolify
env vars.
2026-06-01 22:16:29 +02:00
Julien Calixte
67787bdf31 fix(cors): enable credentials, enumerate origins for PouchDB sessions
PouchDB-style apps call fetch with credentials: 'include' to maintain
the CouchDB _session cookie. Without Access-Control-Allow-Credentials:
true in the preflight response, the browser silently blocks the
mutating request (Firefox HAR shows status 0, _securityState insecure).
Switching cors/credentials to true forces enumerating allowed origins
(wildcard + credentials is forbidden by the CORS spec). Listed are the
common Vite/Next/etc. local dev ports plus the vaquant.at frontend.
2026-06-01 21:55:13 +02:00
Julien Calixte
0ac46ecadb docs: explain hardcoded host and Coolify network UUID
Coolify does not interpolate ${...} inside the labels: block, so the
two strings tying this template to its specific Coolify resource cannot
currently be variables. Document where they appear and how to swap
them when redeploying as a new resource.
2026-06-01 21:27:19 +02:00
Julien Calixte
78fe916c2f fix: hardcode host and Coolify network UUID
Coolify is preventing variable interpolation in compose labels — the
container's labels were stored literally as Host(\`\${SERVICE_FQDN_COUCHDB_5984}\`)
etc., so Traefik matched no router and fell back to default_redirect_503.
Variable substitution works in environment: (CouchDB env was correct),
just not in labels: — likely Coolify sets labels via the Docker API
after compose runs, bypassing compose interpolation. Hardcoding the host
and the per-resource network name is the only thing left that works.
2026-06-01 21:24:34 +02:00
Julien Calixte
e05a86fad8 fix: put Traefik labels back, scoped to coolcouch, pinned to UUID net
The previous diagnostic showed Coolify does not auto-inject Traefik
labels just because SERVICE_FQDN_<NAME>_<PORT> is set — the container
had zero traefik.* labels after deploy, which is why the host returned
503. Adding explicit router+service labels and binding the docker
provider to the per-resource UUID network gives Traefik a definite
backend. Router/service names use the coolcouch prefix so they cannot
clash with any Coolify-managed couchdb router on the same install.
2026-06-01 21:07:30 +02:00
Julien Calixte
b9bf218b9b fix: attach to per-resource Coolify network, not the global one
Traefik in this Coolify install resolves backends on a per-resource
network named after the resource UUID, not the shared "coolify" network.
COOLIFY_RESOURCE_UUID is injected into the compose env by Coolify, so
naming the external network from it lets Traefik find couchdb.
2026-06-01 21:02:59 +02:00
Julien Calixte
516daf0b07 fix: attach couchdb to coolify external network for Traefik routing
The container was only on the project's default network, so Coolify's
Traefik (on the shared coolify network) had no route to it — Traefik
matched the router but reported "no available server". Putting couchdb
on both networks lets Traefik reach 5984 while leaving the init sidecar
talking to it over the default network as before.
2026-06-01 20:52:40 +02:00
Julien Calixte
6e35ac4ad4 fix: drop manual Traefik labels, let Coolify auto-route
When SERVICE_FQDN_<NAME>_<PORT> is set, Coolify auto-injects its own
Traefik router and service labels on the couchdb container. Our manual
labels reused the same router/service name (couchdb), so the two
definitions collided and Traefik ended up with a router whose backing
service had no servers — Traefik returned "no available server" for
couch.apoena.dev. Dropping the manual labels lets Coolify own routing
end-to-end.
2026-06-01 20:50:49 +02:00
Julien Calixte
cff802bd3f fix: drop curl healthcheck and cors.ini mount; configure CORS via API
The couchdb:3 image purges curl after the build, so the curl-based
healthcheck could never succeed. The cors.ini bind-mount also looks
like the cause of the ~600ms container exit observed in the first two
Coolify deploys. Both pieces are removed; CORS is now applied by the
init sidecar via PUTs against /_node/_local/_config/..., which is
idempotent on redeploy.
2026-06-01 20:45:32 +02:00
Julien Calixte
38089ef269 init 2026-06-01 20:37:14 +02:00