---
title: 'Wiki Site'
url: 'https://wiki.teletypegames.org/services/wiki-site'
markdown: 'https://wiki.teletypegames.org/services/wiki-site.md'
lang: en
date: '2026-11-01'
description: '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. Overvie…'
---

# Wiki Site

> This wiki is a [Grav CMS](https://getgrav.org/) 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](https://wiki.teletypegames.org/services/updater) polls to announce them on Discord.

# Overview

One repository holds everything — [`services/wiki-pages`](https://git.teletypegames.org/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](https://wiki.teletypegames.org/services/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](https://wiki.teletypegames.org/infrastructure/idp)); 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](https://wiki.teletypegames.org/services/updater) 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

'; var prevLevel = 2; for (var i = 0; i < headings.length; i++) { var h = headings\[i\]; var text = h.textContent.trim(); var slug = text.toLowerCase() .replace(/\[áàâä\]/g,'a').replace(/\[éèêë\]/g,'e').replace(/\[íìîï\]/g,'i') .replace(/\[óòôöő\]/g,'o').replace(/\[úùûüű\]/g,'u') .replace(/\[^a-z0-9\]+/g, '-').replace(/^-|-$/g, ''); if (!slug) slug = 'heading-' + i; if (used\[slug\]) { slug += '-' + i; } used\[slug\] = true; h.id = slug; var level = parseInt(h.tagName.charAt(1)); var cls = level > 2 ? ' class="toc-child"' : ''; html += '- [' + text + '](#'%20+%20slug%20+%20')
'; } html += '
'; toc.innerHTML = html; // Scroll-spy: highlight current heading var links = toc.querySelectorAll('a'); var ids = \[\]; for (var j = 0; j < links.length; j++) ids.push(links\[j\].getAttribute('href').slice(1)); function updateActive() { var current = ''; for (var k = 0; k < ids.length; k++) { var el = document.getElementById(ids\[k\]); if (el && el.getBoundingClientRect().top <= 80) current = ids\[k\]; } for (var m = 0; m < links.length; m++) { links\[m\].parentNode.classList.toggle('active', links\[m\].getAttribute('href') === '#' + current); } } window.addEventListener('scroll', updateActive, { passive: true }); updateActive(); })();

---

## Navigation

- Parent: [Services](https://wiki.teletypegames.org/services.md)
- Previous: [TTG Wiki BBS](https://wiki.teletypegames.org/services/wiki-bbs.md)
