EN /HU | Login

Wiki Site

This wiki is a Grav CMS site whose content lives in a git repository, plus two companion services: a fetcher that keeps the live checkout and git in sync in both directions, and a telnet BBS front-end. Changes surface on a JSON API, which the Updater bot polls to announce them on Discord.

Overview

One repository holds everything — services/wiki-pages: content (pages/), Grav config, theme, companion services. Docker Compose runs two services from it:

Service What it does
grav Grav CMS (PHP 8.1 + Apache). pages/, config/, themes/ are bind-mounted, so the site serves the working tree. The active theme is ttgwiki2 (the teletypegames.org look, with dark/light and a retro mode); pages.theme: ttgwiki in config/system.yaml brings the old one back.
wiki-fetcher Go sync service (fetcher/). Takes pushes in on the Gitea webhook (POST /hook) or every 10 minutes, commits and pushes the server's content edits, reports on /health and /metrics. See Sync below.

The telnet front-end, TTG Wiki BBS, has its own repository and stack (bbs/wiki-bbs) since 2026-11-01; it reads the JSON API as http://wiki-grav, the grav container's alias on the interstack network.

Content

Grav routes by folder: a page is pages/<section>/<slug>/default.<lang>.md with YAML frontmatter. The sections follow the Gitea orgs where there is one: services, platforms (the build-org toolchains), engines, stores, dos, infrastructure, plus projects, team, technologies and development (guides such as the RoR styleguide). Slugs are hyphenated, and every route that moved has a redirect in config/site.yaml — with the /hu prefix too — so old links still resolve. The sidebar is curated by hand in config/sitenav.yaml. Two ways to edit:

  • In git — edit default.<lang>.md, commit, push; the webhook brings it live within seconds.
  • In the admin panel (/admin) — Grav writes into the bind-mounted working tree, and the fetcher commits and pushes it within about a minute.

Pages under projects/ and team/ need a login (through Authentik); a section's child index hides them from guests. Images live in assets/, served at /wiki-assets/….

Languages

The wiki is bilingual: English is the default and has no URL prefix, Hungarian lives under /hu. A page carries its language in its filename — default.en.md or default.hu.md — and the language switcher sits in the top bar.

English pages have a Hungarian translation. Hungarian-only pages (the association paperwork, the meeting notes, the Neumatronic Universe lore) do not get an English one: content_fallback in config/system.yaml makes the English request fall through to the Hungarian file, so nothing 404s.

The JSON API follows the same rule: /custom/pages.json is English, /hu/custom/pages.json is Hungarian. Each response echoes the requested lang, and every page carries a locale saying which language its body is actually written in — the two differ exactly where the fallback kicked in.

Update flow

  1. Someone pushes to wiki-pages on Gitea, or edits a page in the admin panel.
  2. The fetcher brings the two sides together in the live checkout (/srv/stacks/wiki-pages): a push arrives through the Gitea webhook, an admin edit is committed and pushed. A change to the stack itself (compose, Dockerfiles, php/, apache/, fetcher/) also runs the pipeline, whose ci-deploy wiki-pages rebuilds the containers.
  3. The repo is bind-mounted, so the pulled changes are live at once. Asset timestamps (assets.enable_asset_timestamp) make cached browsers fetch changed theme CSS/JS.
  4. /custom/updates.json lists every page by modification time.
  5. The Updater bot polls it, compares against its cache, and posts a Discord notification per new update.

Sync

The live checkout is written from both sides, so the fetcher sorts paths into lanes:

  • Content — pages/, assets/, accounts/: synced both ways. A server edit is committed once its files have not changed for 30 seconds, named by route (wiki (server): /team/meetings).
  • Code — everything else: git only. A change on the server is drift: it shows on /health, the Wiki server drift alert fires after an hour, and it is never committed automatically. docker exec wiki-fetcher wiki-fetcher status lists it; wiki-fetcher commit -m "…" <path> commits it on purpose (e.g. after a GPM update).
  • State — Grav runtime files (data/api-keys.yaml, caches): gitignored.

When both sides have new commits, the server's are rebased onto the remote in a throwaway worktree; the live tree only ever moves by fast-forward. On a conflict the live wiki stays as it was and the server's commits go to a server/conflict-<time> branch: merge it into master and push, and the next round heals by itself. /health answers ok, or 503 with fail, conflict, drift or stale; the Wiki sync failing alert watches the same. Details: fetcher/README.md.

JSON API

The endpoints are Grav pages (pages/custom/) rendered by twig templates in themes/ttgwiki2/templates/*.json.twig. The language comes from the URL prefix (/hu/custom/…):

Endpoint Purpose
/custom/sitemap.json Every routable page: route, title, modified, tags, private flag (no bodies).
/custom/page.json?route=<route> One page with raw markdown + rendered HTML.
/custom/updates.json?limit=N Pages ordered by modification time — this is what the Updater bot watches.
/custom/pages.json?tag=<tag> Pages filtered by taxonomy tag (the public site's Blog and HowTos feeds use this).

A private page exposes its body only with token=<SUPERVISOR_PASSWORD>. The BBS uses the same token for its supervisor login.

Analytics

The theme carries the Matomo tracker (matomo.teletypegames.org, site id in config/themes/ttgwiki2.yaml). It runs without cookies (disableCookies, so there is no _pk_id cookie and no consent is needed) and with the heartbeat timer, so the time spent on single-page visits is recorded too.

Repository

https://git.teletypegames.org/services/wiki-pages