id: e3e948d299184fd993f7269bd616138f
parent_id: c0cf480db3ee4daea7cebc04dc635003
item_type: 1
item_id: 980c3eb587294e4383474b94988f2f88
item_updated_time: 1785659286666
title_diff: "[]"
body_diff: "[{\"diffs\":[[0,\"f-hosted\"],[1,\", no Docker\"],[0,\")\\\n\\\n> Res\"]],\"start1\":566,\"start2\":566,\"length1\":16,\"length2\":27},{\"diffs\":[[0,\"4e).\"],[-1,\" Git source of truth: Forgejo on the Hetzner box ([migration note](joplin://91ddc5785c174087b03fa6ae216a8611)).\"],[0,\"\\\n\\\n| \"]],\"start1\":715,\"start2\":715,\"length1\":119,\"length2\":8},{\"diffs\":[[0,\"rm; \"],[-1,\"full stack locally via `docker-compose.yml` (\"],[1,\"local \"],[0,\"Post\"]],\"start1\":880,\"start2\":880,\"length1\":53,\"length2\":14},{\"diffs\":[[0,\"SQL \"],[-1,\"16 \"],[0,\"+ Redis \"],[-1,\"7\"],[1,\"(native packages or VirtualBox VM\"],[0,\"); b\"]],\"start1\":897,\"start2\":897,\"length1\":20,\"length2\":49},{\"diffs\":[[0,\"| **\"],[-1,\"Jenkins** CI (lint → test → build → Docker image, tagged by git SHA). A **bhyve Debian VM** runs the *same* Docker Compose as prod (separate DB, `staging.<domain>`), mirroring the prod OS\"],[1,\"Forgejo** (primary git host), **Jenkins** CI (lint → test → cross-compile Linux x86_64 → transfer binary). A **bhyve Debian VM** mirrors prod: bare systemd units (PG + Redis + app binary + Caddy), separate DB\"],[0,\". Also \"],[-1,\"the \"],[0,\"arti\"]],\"start1\":1068,\"start2\":1068,\"length1\":206,\"length2\":223},{\"diffs\":[[0,\"nt. \"],[-1,\"On\"],[1,\"Bar\"],[0,\"e **\"],[-1,\"Caddy** (TLS) already fronts **Forgejo**; add `api.<domain>` → game API (Docker) and `play.<domain>` → built static frontend. Prod Docker Compose = PostgreSQL + Redis + app image (tagged). Jenkins deploys by SSH + `docker compose up -d`\"],[1,\"systemd** services: PostgreSQL, Redis, Skara Brae binary, Caddy (TLS). Jenkins deploys via SSH: copy binary → `systemctl restart skara-brae`. Hetzner also hosts a self-hosted **Codeberg** instance as git mirror/backup\"],[0,\". |\\\n\"]],\"start1\":1369,\"start2\":1369,\"length1\":250,\"length2\":232},{\"diffs\":[[0,\"orgejo (\"],[-1,\"Hetzner\"],[1,\"basement\"],[0,\") → Jenk\"]],\"start1\":1640,\"start2\":1640,\"length1\":23,\"length2\":24},{\"diffs\":[[0,\"ins \"],[-1,\"(basement) builds & images\"],[1,\"builds & cross-compiles Linux binary\"],[0,\" → d\"]],\"start1\":1664,\"start2\":1664,\"length1\":34,\"length2\":44},{\"diffs\":[[0,\"ins \"],[-1,\"deploys the same image tag to Hetzner prod\"],[1,\"SCPs the binary to Hetzner prod → `systemctl restart`\"],[0,\".\\\n\\\n*\"]],\"start1\":1755,\"start2\":1755,\"length1\":50,\"length2\":61},{\"diffs\":[[0,\"and \"],[-1,\"already runs Forgejo\"],[1,\"will also run Codeberg\"],[0,\". A \"]],\"start1\":1880,\"start2\":1880,\"length1\":28,\"length2\":30},{\"diffs\":[[0,\" MMORPG \"],[-1,\"is\"],[1,\"binary is very\"],[0,\" light, \"]],\"start1\":1919,\"start2\":1919,\"length1\":18,\"length2\":30},{\"diffs\":[[0,\"ed RAM (\"],[-1,\"Forgejo\"],[1,\"Codeberg\"],[0,\" + PG + \"]],\"start1\":1965,\"start2\":1965,\"length1\":23,\"length2\":24},{\"diffs\":[[0,\"ing \"],[-1,\"the box \"],[0,\"or s\"]],\"start1\":2028,\"start2\":2028,\"length1\":16,\"length2\":8},{\"diffs\":[[0,\"ting\"],[-1,\" DB/app onto a second VPS. bhyve on the basement box can also host a hot-spare/standby if needed\"],[0,\".\\\n\\\n*\"]],\"start1\":2040,\"start2\":2040,\"length1\":104,\"length2\":8},{\"diffs\":[[0,\"bian\"],[-1,\" + Docker\"],[0,\". Ru\"]],\"start1\":2126,\"start2\":2126,\"length1\":17,\"length2\":8},{\"diffs\":[[0,\"ises\"],[-1,\" the Docker layer doesn't fully hide\"],[0,\". Us\"]],\"start1\":2206,\"start2\":2206,\"length1\":44,\"length2\":8},{\"diffs\":[[0,\"ns, \"],[-1,\"backups), not for the Docker-based game\"],[1,\"Forgejo, backups).\\\n\\\n**No Docker.** All services run as native systemd units (Linux) or jails (FreeBSD). No container overhead, simpler debugging, direct access to the binary\"],[0,\".\\\n\\\n-\"]],\"start1\":2282,\"start2\":2282,\"length1\":47,\"length2\":181},{\"diffs\":[[0,\"e.\\\n\\\n```\\\n\"],[-1,\"github.com/{org}/\"],[1,\"jan@basement/skara-brae/        # Forgejo org or user repos\"],[0,\"\\\n├── ska\"]],\"start1\":2617,\"start2\":2617,\"length1\":33,\"length2\":75},{\"diffs\":[[0,\"\\\n```\\\n\\\n> \"],[-1,\"S\"],[1,\"Git s\"],[0,\"ource of\"]],\"start1\":2899,\"start2\":2899,\"length1\":17,\"length2\":21},{\"diffs\":[[0,\"of truth\"],[-1,\" is\"],[1,\":\"],[0,\" **Forge\"]],\"start1\":2918,\"start2\":2918,\"length1\":19,\"length2\":17},{\"diffs\":[[0,\" on \"],[-1,\"Hetzner, not GitHub — the `github.com/{org}/` path above is conceptual only\"],[1,\"the basement server. Hetzner mirrors via a self-hosted Codeberg instance\"],[0,\". Se\"]],\"start1\":2939,\"start2\":2939,\"length1\":83,\"length2\":80},{\"diffs\":[[0,\"f-hosted\"],[1,\"-no-docker\"],[0,\").\\\n\\\n### \"]],\"start1\":3067,\"start2\":3067,\"length1\":16,\"length2\":26},{\"diffs\":[[0,\"├── \"],[-1,\"docker-compose.yml\\\n├── Dockerfile\\\n└── README.md\\\n```\\\n\\\n> Note: `sb-shared` above is the **server-internal** Rust crate. The **cross-language** shared repo is `skara-brae-common` (consumed via `sb-common = { path = \\\"../skara-brae-common/rust\\\" }`). See [Multi-Repo Structure](#multi-repo-structure) above.\"],[1,\"systemd/                     # systemd .service unit files for deployment\\\n│   ├── skara-brae.service\\\n│   ├── skara-brae.env          # environment variables (DB URL, Redis URL, etc.)\\\n│   └── install.sh              # install script: copy binary + unit → /etc/systemd/system/\\\n│\\\n└── README.md\\\n```\\\n\\\n> Note: `sb-shared` above is the **server-internal** Rust crate. The **cross-language** shared repo is `skara-brae-common` (consumed via `sb-common = { path = \\\"../skara-brae-common/rust\\\" }`). See [Multi-Repo Structure](#multi-repo-structure) above.\\\n\\\n---\\\n\\\n## systemd Deployment (prod & staging)\\\n\\\n### skara-brae.service\\\n```ini\\\n[Unit]\\\nDescription=Skara Brae Game Server\\\nAfter=network.target postgresql.service redis.service\\\nWants=postgresql.service redis.service\\\n\\\n[Service]\\\nType=simple\\\nUser=skara-brae\\\nGroup=skara-brae\\\nExecStart=/opt/skara-brae/bin/skara-brae-server\\\nEnvironmentFile=/opt/skara-brae/skara-brae.env\\\nRestart=always\\\nRestartSec=5\\\nLimitNOFILE=65536\\\n\\\n[Install]\\\nWantedBy=multi-user.target\\\n```\\\n\\\n### skara-brae.env\\\n```env\\\nDATABASE_URL=postgres://skara-brae:password@127.0.0.1:5432/skara_brae\\\nREDIS_URL=redis://127.0.0.1:6379\\\nLISTEN_ADDR=127.0.0.1:8080\\\nJWT_SECRET=changeme\\\nRUST_LOG=info\\\n```\\\n\\\n### Caddy (reverse proxy + TLS)\\\n```\\\napi.skara-brae.tld {\\\n    reverse_proxy 127.0.0.1:8080\\\n}\\\n\\\nplay.skara-brae.tld {\\\n    root * /srv/skara-brae-web\\\n    file_server\\\n    try_files {path} /index.html\\\n}\\\n```\\\n\\\n### Jenkins deploy step (SSH to Hetzner)\\\n```bash\\\nscp target/release/skara-brae-server hetzner:/opt/skara-brae/bin/\\\nssh hetzner \\\"systemctl restart skara-brae\\\"\\\n```\\\n\\\n### Install script (first-time setup)\\\n```bash\\\n#!/bin/bash\\\nset -euo pipefail\\\nsudo useradd -r -s /bin/false skara-brae\\\nsudo mkdir -p /opt/skara-brae/{bin,config}\\\nsudo cp skara-brae-server /opt/skara-brae/bin/\\\nsudo cp systemd/skara-brae.service /etc/systemd/system/\\\nsudo cp systemd/skara-brae.env /opt/skara-brae/\\\nsudo systemctl daemon-reload\\\nsudo systemctl enable --now skara-brae\\\n```\"],[0,\"\\\n\\\n--\"]],\"start1\":11254,\"start2\":11254,\"length1\":309,\"length2\":1946},{\"diffs\":[[0,\" 2026.\\\n\\\n\"],[1,\"7. **Single static binary**: Cross-compile for Linux x86_64 on workhorse, deploy the binary to staging/prod. No runtime dependencies, no Docker. Systemd manages the process lifecycle.\\\n\\\n\"],[0,\"---\\\n\\\n## \"]],\"start1\":23482,\"start2\":23482,\"length1\":16,\"length2\":201}]"
metadata_diff: {"new":{},"deleted":[]}
encryption_cipher_text: 
encryption_applied: 0
updated_time: 2026-08-02T08:29:54.788Z
created_time: 2026-08-02T08:29:54.788Z
is_locked: 0
type_: 13