PLAN.md §13 phase 12, the last one. Four decisions of record, D54–D57, taking the count to fifty-seven; recorded in §6, "How phase 12 delivered it". A two-stage Dockerfile, a pull-only docker-compose.yml carrying both bind mounts, .env.example, the workflow that publishes and deploys, CONTRIBUTING.md, the community-health files this was the only repository of the ten to lack, and DEPLOY.md. D54 — a merge deploys, amending D6. build-image.yml pushes runicgateway-site:latest and :sha-<7>, then rolls the container over on the `rgcom` runner out of /opt/runicgateway.com, and waits for the container's own healthcheck rather than for `up -d` to return. D55 — the site runs on its own host behind a generic reverse proxy, so DEPLOY.md states the four requirements rather than one worked example, and the container binds 127.0.0.1 so the safe configuration is the default. D56 — @astrojs/node derives the request protocol from req.socket.encrypted and never reads x-forwarded-proto, so behind a TLS-terminating proxy the browser sends Origin: https://… while the container computes http://… and Astro's CSRF check compares them for equality. Every beta signup, from every visitor, was answered 403. serve.mjs now normalises both forwarded headers, unconditionally — the image should deploy and work. Two assertions in test/headers.test.mjs hold both halves. D57 — DEPLOY.md rather than a README section; SECURITY.md and CODE_OF_CONDUCT.md are pointers to the org's copies rather than copies, because a copy would hard-code the contact address D13 confines to brand.json. Verified: npm run verify green (eleven checks, 36 unit tests, 7 served tests, astro check 0 errors). The image was built and run with both mounts — a mounted brand reached 51 files and all 50 search pages, /brand/* fell back per file, a proxy-shaped signup reached the store, and the export CLI wrote both Play files to the host mount. docker compose config caught a YAML trap in the healthcheck: a block sequence reads the `: ` in `r.ok ? 0 : 1` as a mapping. Co-Authored-By: Claude <noreply@anthropic.com>
6.7 KiB
Contributing to runicgateway.com
Thanks for your interest. This repository is the public marketing and documentation site for Runic Gateway — the platform that puts a private game server's live state on a public website without ever exposing the game to the internet.
It is a site, not a component. Nothing else in the organisation depends on it, and it depends on everything: almost every sentence here describes something that lives in another repository.
By participating you agree to abide by our Code of Conduct.
Read the plan first
PLAN.md is the design of record. It is not a sketch — it carries the verified
platform state, the org lead's fifty-odd decisions, the information architecture, the accuracy
machinery and the build phases. A change that contradicts a decision recorded there needs the
decision changed first, in the same pull request, with the reasoning written down.
Two things it records are worth knowing before you write a line:
- §1 — the site never re-specifies a contract.
docs/is normative for the protocol, the module API, the backend design and the installer. This site teaches, links out, and quotes versions from data rather than prose. A page that restates a contract is a page that will be wrong later, and nothing will notice. - §12 — the checks are the mechanism, and their failure is the feature. When the platform moves, this repository goes red so that somebody updates the site. Do not route around a check; if one is wrong, fix the check and say why in the pull request.
Ways to contribute
- Report a bug or request a feature through the issue tracker — templates are provided.
- Improve a page, a check or the build by opening a pull request.
- Never report a security vulnerability in a public issue. See SECURITY.md.
If you spot a claim on the site that is wrong about the platform — a version, a capability, a command that no longer exists — that is the most valuable report this repository can receive.
Development setup
Prerequisites: Node 22 LTS or newer. Nothing else — no database, no game server, no container runtime for ordinary work.
npm install
npm run dev # http://localhost:4321
npm run build # → dist/ (prerendered pages + the Node server entry)
npm start # serve the built site, exactly as the container does
Two directories are bind mounts at runtime and not in the repository: brand/ overrides the
stock branding per file, and data/ holds the beta signup store. Both are optional locally; an
absent brand/ produces exactly the stock site, and data/ is created on first write.
The checks
There are eleven, plus two test suites. npm run verify runs all of them in dependency order, and
that is what CI does on every pull request.
npm run verify
Three of them read other repositories over the Gitea API and need a token with access to the organisation, not just this repository:
GITEA_TOKEN=<token> npm run check:facts # every version agrees with its authority
GITEA_TOKEN=<token> npm run check:quickstart # the install page still matches website's own files
GITEA_TOKEN=<token> npm run check:reference # every name the Reference lists still exists
Without a token they fail rather than skip, deliberately: a check that silently passes when it could
not do its job is worse than no check. CI maps the org-level REGISTRY_TOKEN secret into
GITEA_TOKEN for those three steps.
The README's "The checks, and why they are not optional" section explains what each one guards.
Read it before adding a page — several of them constrain how a page may be written, particularly
check:tokens (no colour literal outside src/styles/tokens.css) and check:links (no commit
permalinks into org repositories).
Writing for this site
- Understated honesty (D8). The site reads as finished. Where something is not built, the
absence is stated as data in
src/data/notBuilt.mjsand rendered — never implied by silence and never dressed up as a roadmap. - No version in prose. Every externally-sourced fact lives in
src/data/platform.jsonand is re-read from its authority bycheck:facts. If you find yourself typing a version number into a sentence, put it in the data file instead. - No email address in
src/orscripts/(D13). The published contact is abrand.jsonfield so that changing it stays a file copy and a restart.check:factsenforces this. - British spelling, in common with the rest of the organisation's prose.
Branch and pull-request workflow
- Branch from
mainwith a descriptive name (feature/…,fix/…,docs/…,chore/…). - Keep changes focused; small pull requests are easier to review.
- Run
npm run verifybefore opening the pull request. - Push and open a pull request against
main. Fill out the template, including the AI-assisted contributions disclosure. - A maintainer will review; address feedback with follow-up commits.
Commit messages
Conventional Commits — type(scope): summary, in common
with every repository in the organisation. For example fix(docs): correct the installer flag on the quickstart.
What merging does
Merging to main builds a container image, publishes it to the Gitea registry and deploys it
(.gitea/workflows/build-image.yml). There is no separate release step and no manual promotion, so
a merge is a publication. DEPLOY.md describes the whole path, including how to roll
back to a previous build.
AI-assisted contributions (disclosure required)
This project is developed openly with AI assistance, and we ask the same transparency of everyone. If you used an AI tool (Claude, Copilot, ChatGPT, Cursor, etc.) to help produce a contribution, you must disclose it:
- Tick the AI-usage box in the pull-request template and name the tool(s).
- Mark AI-authored commits with a trailer, e.g.
Co-Authored-By: Claude <noreply@anthropic.com>orAssisted-By: <tool>. - You remain responsible for every line you submit: review it, understand it, and make sure it is correct and that you have the right to contribute it.
Disclosed AI assistance is welcome. Undisclosed AI-generated contributions are not, and may be closed.
Licence
Runic Gateway is licensed under the GNU General Public License v3.0 or later (see LICENSE). By submitting a contribution you agree that it is licensed under the same terms (inbound = outbound) and that you have the right to contribute it.