diff --git a/compose.yaml b/compose.yaml index 63bcf135b..bc7266c4d 100644 --- a/compose.yaml +++ b/compose.yaml @@ -2,6 +2,8 @@ # docker compose up -d --build # Settings come from a .env file next to this one (never committed): REPO_URL is required, GIT_TOKEN # for a private repo, and the OIDC_* settings for the sign-in; every setting is in deployment/cloud/README.md. +# It is the same .env that `bun run dev` reads, and all of it reaches the container: on a server, keep +# laptop-only settings (DX_PB_URL, DX_SYNC_*) out of it. services: dx-maps: build: . diff --git a/deployment/cloud/README.md b/deployment/cloud/README.md index 38cd4425e..643234ae1 100644 --- a/deployment/cloud/README.md +++ b/deployment/cloud/README.md @@ -7,7 +7,9 @@ publishing. With **no OIDC settings** it runs open, like `bun run dev` on a laptop. With **OIDC settings** a sign-in page ("Sign in with *your provider*") sits in front of everything: the Studio, every map, -and the hot-reload websocket. Only people in the allowed groups get in. +and the hot-reload websocket. Only people in the allowed groups get in. Open is for a laptop or a +private network only: it has none of the public mode's guards. On a public host, use the sign-in or +[`ACCESS=public`](#public-studio-accesspublic). ## Deploy @@ -37,7 +39,7 @@ The container needs a Dockerfile build, a persistent volume at `/data` and about | Coolify | the "Dockerfile" build pack (default location `/Dockerfile`) | Persistent Storage, mount path `/data` | | Dokploy | Build Type "Dockerfile" (path `Dockerfile`) | Advanced, Volumes: mount path `/data` | | Railway | finds the root `Dockerfile` by itself; set `PORT=5173` | add a volume at `/data`. Railway deploys from GitHub, a local directory or an image | -| A server with Docker | `docker compose up -d --build`, with the root [`compose.yaml`](../../compose.yaml) | the compose file's `dx-data` volume | +| A server with Docker | `docker compose up -d --build`, with the root [`compose.yaml`](../../compose.yaml). It reads the repo-root `.env`, the same file `bun run dev` reads, so on a server keep only the container's settings there | the compose file's `dx-data` volume | | Kubernetes | build the image in CI (BuildKit, Buildah) and push it to a registry | a PersistentVolumeClaim at `/data` | | DigitalOcean App Platform | not suitable | it has no persistent volumes, so PocketBase's data would be lost on every deploy | @@ -108,7 +110,8 @@ there. Only then does it swap the new build in. | `OIDC_ADMIN_GROUPS` | comma-separated groups allowed to build and to open PocketBase's dashboard | | `PB_SUPERUSER_EMAIL`, `PB_SUPERUSER_PASSWORD` | PocketBase's superuser, set on start in both instances | | `PB_OIDC_CLIENT_SECRET` (+ `PB_OIDC_CLIENT_ID`, default `dx-maps-pb-dev`) | turns pb-dev into the team backend: laptops use it directly (`DX_PB_URL=https:///pb`), and people sign in to PocketBase through the same OIDC provider, with its own client whose redirect URI is `https:///pb/api/oauth2-redirect`. Only members of `OIDC_ALLOWED_GROUPS` get in (a sign-in hook baked into the image and pinned; the repo's own hooks never run). Password sign-in is off, new collections are superuser-only until a migration opens them, and data never leaves `/data` | -| `PRODUCTION_BRANCH` | the branch `/cdn/` is built from (default `production`); the dev server keeps following `BRANCH`. Promote with `git push origin main:production` | +| `PRODUCTION_BRANCH` | the branch `/cdn/` is built from (default `main`); the dev server keeps following `BRANCH`. It moves only on a publish | +| `EMBED_BASE_URL` | the address the published `dx-maps/` folder sits under, so the manifests' copy-paste snippets are absolute: `https:///cdn` (it is served at `/cdn/dx-maps/`), or a research subdomain's `https://research.` (served at `/dx-maps/`). Unset, the snippets stay relative (`./…`) and other sites can't paste them | | `CDN_AUTOBUILD_MINUTES` | rebuild `/cdn/` by itself once `PRODUCTION_BRANCH` has held a new commit this long (default `2`; `0` = only the admin button) | | `SUBDOMAIN_APPS` | publish plugins as their own PUBLIC sites: `host=bundle/profile`, comma-separated, e.g. `research-gallery.example.org=doxa-maps-spa/doxa-maps-spa`. Served from the `/cdn` build by a separate server on `SUBDOMAIN_PORT` (default `5176`) that answers only those hosts and only with published files; point each host's proxy route at that port. No sign-in is skipped anywhere else | | `PUBLIC_API_BASE` | the public API URL written into the build's `runtime-config.js` (e.g. `https:///api-prod`) | diff --git a/deployment/cloud/admin.mjs b/deployment/cloud/admin.mjs index dd1db9c67..659c45d19 100644 --- a/deployment/cloud/admin.mjs +++ b/deployment/cloud/admin.mjs @@ -89,11 +89,10 @@ const publishedCommit = () => (existsSync('/data/cdn/COMMIT') ? readFileSync('/d // The production PocketBase's schema: the snapshot its migrations came from (pb-prod-migrations.sh), or 'empty'. const prodSchema = () => { try { return basename(realpathSync('/data/pb-prod-migrations/current')) } catch { return null } } -// AUTO-BUILD: the public build follows PRODUCTION_BRANCH by itself. main is the shared sandbox; production -// moves only when someone promotes (git push origin main:production), so rebuilding on every move is -// the point. Debounced: build once the branch has held the same commit for CDN_AUTOBUILD_MINUTES +// AUTO-BUILD: the public build follows PRODUCTION_BRANCH by itself (default main). The dev server follows +// BRANCH, the shared table; main moves only on a publish, so rebuilding on every move is the point. Debounced: build once the branch has held the same commit for CDN_AUTOBUILD_MINUTES // (default 2; 0 = off, admin button only). A commit whose build failed is not retried automatically. -const PROD = env.PRODUCTION_BRANCH || 'production' +const PROD = env.PRODUCTION_BRANCH || 'main' const AUTO_MIN = env.CDN_AUTOBUILD_MINUTES === undefined || env.CDN_AUTOBUILD_MINUTES === '' ? 2 : Number(env.CDN_AUTOBUILD_MINUTES) const watch = { seen: null, since: 0, failed: null, error: null } async function checkProduction() { diff --git a/deployment/cloud/build-cdn.sh b/deployment/cloud/build-cdn.sh index a419fefc9..574046466 100755 --- a/deployment/cloud/build-cdn.sh +++ b/deployment/cloud/build-cdn.sh @@ -1,7 +1,7 @@ #!/bin/bash # build-cdn.sh — one public build of the PRODUCTION branch into /data/cdn, one at a time. -# main is the shared sandbox the dev server follows; the public build comes only from -# PRODUCTION_BRANCH (default production), which moves when someone promotes main to it. +# The dev server follows BRANCH (the team's shared table, e.g. development); the public build comes only +# from PRODUCTION_BRANCH (default main), which moves only on a publish. # fetch that branch into its own ref -> snapshot it -> apply ITS public exclusions -> # the build steps WITHOUT the gates (memory-capped) -> check the output -> the exclusion check again # (refuses the swap) -> atomic swap of /data/cdn/current. @@ -14,7 +14,7 @@ exec 9>/tmp/build-cdn.lock flock -n 9 || { echo "another build is running"; exit 75; } echo $$ > /tmp/build-cdn.pid cd /app/repo -PB="${PRODUCTION_BRANCH:-production}" +PB="${PRODUCTION_BRANCH:-main}" # Its own ref, and no FETCH_HEAD: the dev server's pull loop uses FETCH_HEAD for main at the same time. git fetch -q --no-write-fetch-head --depth 1 origin "+refs/heads/$PB:refs/cdn/$PB" \ || { echo "cannot fetch branch '$PB' (create it with: git push origin main:$PB)"; exit 1; } diff --git a/deployment/cloud/pb-prod-migrations.sh b/deployment/cloud/pb-prod-migrations.sh index 58697aad5..ee22c9790 100755 --- a/deployment/cloud/pb-prod-migrations.sh +++ b/deployment/cloud/pb-prod-migrations.sh @@ -42,7 +42,7 @@ install() { case "${1:-}" in fetch) - PB="${PRODUCTION_BRANCH:-production}" + PB="${PRODUCTION_BRANCH:-main}" cd /app/repo || exit 1 # Its own ref and no FETCH_HEAD, exactly as build-cdn.sh: the dev server's pull loop uses FETCH_HEAD. if ! git fetch -q --no-write-fetch-head --depth 1 origin "+refs/heads/$PB:refs/cdn/$PB"; then