Files
maptool/docs/deployment.adoc
T
randy 260006ad65 Support multiple allowed users for write access
MAPTOOL_ALLOWED_GITEA_USER now accepts a comma-separated list of
Gitea usernames instead of just one, for when more than one person
needs upload/delete/move access.
2026-08-27 18:25:31 +02:00

5.5 KiB
Raw Blame History

Deployment

MapTool deploys as a Docker container on the same server the Gitea Actions runners run on. Two separate runners are registered against the Gitea instance:

  • The existing Docker-based runner (gitea/runner:3), labels ubuntu-latest/ubuntu-24.04/ubuntu-22.04 — runs the check job in a fresh, isolated container per job, same as before.

  • A dedicated native runner, installed directly on the host (no Docker around it), label deploy:host — runs only the deploy job.

They’re separate because the Docker-based runner’s jobs (whether in its usual isolated per-job containers, or tried directly inside its own container via a host-scheme label) have no reliable access to the host’s filesystem, systemctl, or even a full toolset (its own container image is missing rsync, docker CLI, Node.js — it’s built for spawning per-job containers via its docker.sock mount, not for running arbitrary host commands). The native runner sidesteps all of that: it is a host process, so docker, git, etc. are just whatever’s installed on the server.

Layout on the server

/srv/docker/maptool/ holds:

  • A checkout of this repository (kept in sync by the deploy job, see below) — Dockerfile, docker-compose.yaml, application code.

  • data/, not part of that sync — uploaded GPX files and offline.pmtiles. Persisted via a bind mount (./data:/app/data in docker-compose.yaml), so a redeploy (docker compose up --build) never touches it.

The native deploy runner

Installed from the official binary (see https://docs.gitea.com/runner/installation/binary/), running as a systemd service (gitea-runner.service) under a dedicated gitea-runner system user, which needs:

  • Docker access (member of the docker group).

  • Write access to /srv/docker/maptool.

Its config.yaml (/etc/gitea-runner/config.yaml) declares a single label:

runner:
  labels:
    - "deploy:host"

The host scheme means jobs run directly via the runner process’s own shell — no container spawned — which is exactly what a plain, single purpose deploy runner needs.

Puma’s bind address

config/puma.rb binds to 127.0.0.1 by default (see docs/architecture.adoc) — fine for local dev, but inside a container that would make the app unreachable through Docker’s published port (loopback is scoped to the container’s own network namespace). docker-compose.yaml sets BIND_HOST=0.0.0.0 to override that for the deployed container specifically; Docker’s own port publishing (ports: in the compose file) is the network boundary there instead of Puma’s bind address.

Secrets (.env)

docker-compose.yaml reads SESSION_SECRET, GITEA_OAUTH_CLIENT_ID, GITEA_OAUTH_CLIENT_SECRET and MAPTOOL_ALLOWED_GITEA_USER from the environment (see docs/architecture.adoc for what they’re for) — docker compose loads these automatically from a .env file next to docker-compose.yaml, i.e. /srv/docker/maptool/.env. That file is gitignored and dockerignored on purpose: nothing here ever puts secrets in the repository or a built image layer.

It’s not created by hand on the server — the deploy job’s "Write .env" step regenerates it on every deploy from repo-level Gitea Actions secrets (Settings → Actions → Secrets): SESSION_SECRET, OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, MAPTOOL_ALLOWED_GITEA_USER. The OAuth2 ones aren’t named GITEA_OAUTH_CLIENT_* to match the .env var names 1:1 — Gitea rejects secret names starting with GITEA_ or GITHUB_ as reserved — but the workflow step maps them to the right .env var names regardless. Gitea masks secret values in the job log automatically. Set these once in the repo settings and every deploy since keeps .env in sync — no server-side step needed beyond the first-time OAuth2 application registration below.

SESSION_SECRET must stay the same value across deploys (regenerating it every push would silently log everyone out every time main changes) — generate it once (openssl rand -hex 32) and store that same value as the secret from then on, don’t regenerate per-deploy.

Register the OAuth2 application once, under any one Gitea account (doesn’t need to be an allowed user itself — any Gitea account can authenticate through it, MAPTOOL_ALLOWED_GITEA_USER is what actually decides who gets write access): Settings → Applications → Manage OAuth2 Applications → Create a new OAuth2 Application, with redirect URI https://map.priesol.net/auth/callback. That gives you the client id/secret to store as the two OAUTH_CLIENT_* secrets above.

MAPTOOL_ALLOWED_GITEA_USER itself is a comma-separated list of Gitea usernames, e.g. randy,friend1,friend2 — not their emails, the login field from Gitea’s /api/v1/user.

CI deploy job

.gitea/workflows/ci.yml’s `deploy job runs after check passes, only on a push to main, with runs-on: deploy:

  1. Checks out the repo with plain git (fetch + checkout, authenticated via a one-off http.extraHeader) rather than actions/checkout@v4 — a leftover from when this job ran in a Node-less container; kept as-is since there’s no reason to bring Node back in for something this already does.

  2. Copies the checkout into /srv/docker/maptool/ using plain cp/rm: clears everything in the target except data/, copies the fresh checkout over, then drops the copied .git.

  3. cd /srv/docker/maptool && docker compose -p maptool up --build -d — rebuilds the image and recreates the container if anything changed; a no-op if nothing did.