NixOS + Docker stack for always-on Assetto Corsa practice lobbies and extra race containers on different ports. This folder is not game content. Keep it next to the Steam install so you can pack tracks from here; the git tree reaches the box through CI (see Deploy).
| Path | Role |
|---|---|
catalog/tracks/*.json |
Add a track by copying a file (folder name, pits, cars) |
catalog/statics.json |
Always-on practice lobbies and which ports they own |
scripts/sync_content.py |
Copy server-side cars/tracks from this AC install |
scripts/pack_content.py |
Zip cars/tracks locally; use publish_124.py for the GitHub Release |
scripts/publish_124.py |
Pack and upload patched 124 to GitHub Releases |
site/ |
Public GitHub Pages player README (see site/README.md) |
scripts/check_car.py |
Reject a car with DX11-illegal textures or missing blurred-object meshes |
scripts/render_cfg.py |
Write server_cfg.ini + entry_list.ini |
scripts/acctl.py |
Start/stop static lobbies and race containers |
sidecar/ |
SteamID whitelist via AUTH_PLUGIN_ADDRESS |
bot/ |
Discord /steam-link (manual bot token later) |
modules/ac-host.nix |
Docker, firewall port range, systemd static lobbies |
hosts/ac-box/ |
Gone since 8 Sep 2026 (40e9f48): the pre-homelab example host, not what runs. The host is homelab's hosts/arcade-box/; this tree ships only the module |
Three practice lobbies stay up together (124, GR86, Civic FK8, NSX NA2 S1, NA Miata, Elise SC, E30): Blackhawk Farms, Road America and Gingerman Raceway, as listed in catalog/statics.json. Brainerd Competition and Donnybrooke remain in the catalog but have no lobby. Magione stays in the catalog as a stock-track fallback for races. The GR86 and Civic need Custom Shaders Patch. The player README and live leaderboard are on GitHub Pages (site/); the home box pushes status outbound — no :8099.
From ac-host/:
python scripts/sync_content.py --track blackhawk
python scripts/sync_content.py --track road-america
python scripts/sync_content.py --track gingerman
python -m unittest sidecar/test_auth.pyCopy content/ to arcade-box (/var/lib/ac-host/content). The git tree is not copied by hand; CI stages it (see Deploy).
The lobbies run on arcade-box (Lenovo M920q, 192.168.1.50, ssh alias arcade-box, root). homelab owns that host and its NixOS switch (homelab ADR 0001: the racing tenant no longer owns the host; hosts/arcade-box/ there, the deploy key in its ssh-keys.local.nix). This tree is a tenant — the module homelab composes plus the compose stack the box runs — and none of it is copied to the box by hand: a green main build stages the tree (queue-prod writes /var/lib/ac-host/pending-src), the bot's 03:00 DOWNTIME build applies it and recycles the lobbies once, and a human applies early with homelab/scripts/hub-deploy.sh. docs/ci-cd.md has the pipeline. Since 26 Sep 2026 (homelab ADR 0010) the Z840 at 192.168.1.51 is model server only, no docker, no lobbies, nothing of this tree; since 28 Sep its name and ssh alias are llm-box.
Password SSH is off on both hosts. A fresh Windows machine needs the operator key (%USERPROFILE%\.ssh\id_ed25519_ac-host, already listed in homelab's ssh-keys.local.nix) and this block in ~/.ssh/config, the same one homelab's WSL carries:
Host arcade-box
HostName 192.168.1.50
User root
IdentityFile ~/.ssh/id_ed25519_ac-host
IdentitiesOnly yes
scripts/bootstrap_ssh.py, the pre-homelab bootstrap (pubkey over the installer's password login, pull hardware-configuration.nix, write hosts/ac-box/ssh-keys.local.nix here), was retired on 28 Sep 2026. It wrote into hosts/ac-box/, which left this tree with the host config on 8 Sep 2026 (40e9f48), so from then on no mode of it completed: --install-only still copied and verified a key, then failed writing the hardware config. Each of its steps belongs to homelab now: the key list is its hosts/<host>/ssh-keys.local.nix, the hardware config is fetched into hosts/<host>/ with scp and tracked, and with password ssh off there is no installer login left to use. A new host's first key goes on by hand, the way arcade-box's did (homelab docs/runbook-arcade-box-cutover.md, 0.1). The block above is the ~/.ssh/config entry the script used to write.
Copy content/ separately when you want the tracks on the box (Road America is large).
Once per box, what homelab's switch does not do:
- Copy packed content to
/var/lib/ac-host/content. cp compose/env.example /var/lib/ac-host/.envand setAC_ADMIN_PASSWORD.
The switch is homelab's: nixosModules.ac-host is an input of its arcade-box host, and this flake has no nixosConfigurations, so there is nothing here to nixos-rebuild. Or merge nixosModules.ac-host into your own flake and keep your own hardware config.
Without waiting for systemd:
export AC_STATE=/var/lib/ac-host AC_CONTENT=/var/lib/ac-host/content
python3 scripts/acctl.py --env prod up-staticManual-only lobby on slot 8 (9608 / 8089 / 8189). Does not restart production lobbies. See DEV.md.
sudo cp compose/env.dev.example /var/lib/ac-host-dev/.env
python3 scripts/acctl.py --env dev up-static
python3 scripts/acctl.py --env prod restart-plugin # after sidecar updates; no lobby kick| Env | State dir | Compose project | Auth | Static catalog |
|---|---|---|---|---|
prod (default) |
/var/lib/ac-host |
ac-host |
:18080 |
catalog/statics.json |
dev |
/var/lib/ac-host-dev |
ac-host-dev |
:18081 |
catalog/statics-dev.json |
Optional RENDER_SKIN_MODE in .env: pinned (prod default), empty (let CM send skin), or cycle (variety per pit).
python3 scripts/acctl.py start-race --name sprint1 --track magione
# players join IP:9603 (first port not owned by a static lobby)
python3 scripts/acctl.py stop-race sprint1Join from Content Manager (public IP 167.237.13.200):
| Lobby | Port | Join |
|---|---|---|
| Practice — Blackhawk Farms | 9600 / HTTP 8081 |
Join |
| Practice — Road America | 9601 / HTTP 8082 |
Join |
| Practice — Gingerman Raceway | 9602 / HTTP 8083 |
Join |
Official acServer does not send cars or tracks to joiners. The public join README and live leaderboard live on GitHub Pages (site/). Content Manager Online can auto-download only the patched 124 via content.json on the GitHub Release. GR86, Civic, tracks, and CSP must be installed from source links on the page first.
Player steps: PLAYERS.md. One-time Pages setup: site/README.md.
Pack and upload the 124 from this PC:
python scripts/publish_124.py --owner imkarrerup-static writes state/dist/content.json (124 Release URL only) for the CM details sidecar. Set AC_GITHUB_OWNER, GITHUB_STATUS_TOKEN, and GITHUB_STATUS_REPO in /var/lib/ac-host/.env on the box.
Forward 9600–9615 TCP+UDP, 8081–8096 TCP, and 8181–8196 TCP (Content Manager details) to the NixOS box. Do not add a new rule per race. Confirm the UniFi WAN address is a real public IP (not CGNAT). 8099 is no longer used.
Not required to get Magione up. When you are ready: create a bot, enable Server Members Intent, create role ac-practice, put the token in .env, docker compose --profile bot up -d bot, set AUTH_OPEN=0. Grid Finder is still a manual event listing + results JSON upload.
Both are static lobbies in catalog/statics.json (slot 1, port 9601; slot 2, port 9602). Install each OverTake zip by dragging it onto Content Manager, then sync:
python scripts/sync_content.py --track road-america
python scripts/sync_content.py --track gingerman- Road America: LilSki 1.0, folder
content/tracks/lilski_road_america, https://www.overtake.gg/downloads/road-america.32732/ - Gingerman: shin956 / cyruscloud 1.0, folder
content/tracks/gingerman_raceway, layoutfull_forward, https://www.overtake.gg/downloads/gingerman-raceway.62436/
Brainerd (catalog/tracks/brainerd-*.json, GutBomb 1.1, folder gb_brainerd) stays in the catalog for one-off races with acctl.py start-race.
- Install the track in Content Manager on this PC.
- Copy
catalog/tracks/blackhawk.jsontocatalog/tracks/<id>.json. - Set
folderto the directory name undercontent/tracks,layoutif it has variants,maxClients≤ pit boxes, andcars. python scripts/sync_content.py --track <id>- For an always-on lobby, add a row to
catalog/statics.jsonwith the next freeslot(0 = 9600, 1 = 9601, …). For a one-off,acctl.py start-race --track <id>.