Skip to content

Syncloud Game Hub: full feature work on top of phase-0 snap skeleton#1

Open
cyberb wants to merge 126 commits into
masterfrom
wip
Open

Syncloud Game Hub: full feature work on top of phase-0 snap skeleton#1
cyberb wants to merge 126 commits into
masterfrom
wip

Conversation

@cyberb
Copy link
Copy Markdown
Member

@cyberb cyberb commented May 17, 2026

Phases delivered on `wip` (117 commits, all green on the most recent CI run #117):

phase summary
1 server CRUD + SQLite (modernc.org/sqlite)
2 process management — exec.Cmd, SIGTERM+10s+SIGKILL, process-group
3 SteamCMD vendor (amd64, i386 binary, anonymous + user/pass)
3c amd64 lib64 bundle for 64-bit games
4 Pelican egg consumer (parkervcp/eggs)
4b real Teeworlds install + start + UDP probe
5 ring-buffer log endpoint
6 A2S_INFO Source-engine UDP query
7 OIDC client registration via platform
8 Playwright e2e (desktop + mobile)
9 docs
16 mobile bottom-bar nav
17 real Minecraft install (Pelican egg + bundled JRE)
18 OIDC code+PKCE + backend session middleware
19 full minecraft cycle (install → EULA → start → tcp-bind)

Highlights from the last round of CI debugging:

  • SteamCMD wrapper handles exit-42 self-update relaunch loop (Valve's MAGIC_RESTART_EXITCODE)
  • Hook-side seed of the runtime dir; wrapper is env+exec only
  • SQLite WAL + busy_timeout to break installer/poll races
  • games.cli CLI for integration tests (REST is now web-only)
  • Playwright artifacts flattened to per-project screenshots/ + videos/
  • Sticky mobile bottom-nav (was overflowing into content on Firefox/Chrome autohide)

Pairs with syncloud/release#725bfa5 (catalog entry + 128px icon).

Tracking: syncloud/platform#35

cyberb added 30 commits May 11, 2026 23:08
- backend/db: modernc.org/sqlite, servers schema
- backend/server: Store with List/Get/Create/UpdateStatus/Delete
- /api/v1/servers GET/POST, /api/v1/servers/{id} GET/DELETE
- web: install dialog, server delete, tab count badge
- test: create/list/delete + reject unknown gameId
- include web/package-lock.json so 'npm ci' succeeds in CI
- backend/runner: exec.Cmd-based with process-group isolation, SIGTERM
  with 10s SIGKILL fallback
- POST /api/v1/servers/{id}/{start,stop,restart}
- allow startCmd in create payload (transitional; phase 3/4 will derive
  from game type)
- delete now stops first
- integration test: create -> start -> double-start 409 -> stop -> delete
- steamcmd/build.sh: download steamcmd_linux.tar.gz at build time, bundle
  under /snap/game-server/current/steamcmd
- backend/installer: handles both 'steam' and 'egg' sources
  - steam: \`+force_install_dir + +login anonymous|user pass + +app_update\`
  - egg: fetch egg JSON, run install script (best-effort, non-docker),
    render startup with default vars
  - default startCmd templates for common Steam dedicated servers
- POST /api/v1/servers/{id}/install kicks off async install, status
  transitions stopped -> installing -> stopped|install-error
- store.UpdateInstall persists install_dir + start_cmd
- UI: ServerRow now shows Install/Start/Stop button based on state

Known limitations:
- SteamCMD is 32-bit i386; needs glibc multiarch on host docker image.
  May fail until snap bundles 32-bit runtime. To be fixed in follow-up.
- Many Pelican eggs assume docker; we run install scripts directly, so
  scripts that 'apt install' or rely on container fs will fail. Best-
  effort for the bash/native subset (teeworlds works).
- runner: 500-line ring buffer per server captures stdout/stderr
- GET /api/v1/servers/{id}/logs returns {lines: [...]}
- backend/query: A2S_INFO Source-engine UDP query with challenge handshake
- GET /api/v1/servers/{id}/query returns parsed server info (map, players,
  max, bots, name, etc.) for any Source-engine game (cs2/tf2/gmod/etc.)
- integration test: start stub, assert log buffer captures 'hello-from-runner'

Phase 5 streaming (websocket) and RCON push deferred to a later commit.
phase 7 (oidc):
- cli installer registers OIDC client at configure via
  platformClient.RegisterOIDCClient (paperless pattern); secret persisted
  to /var/snap/game-server/current/oidc.secret
- backend still uses nginx forward-auth via authelia; OIDC active
  enforcement is a follow-up (need session store + middleware)

phase 8 (playwright):
- web/e2e/ self-contained TS package, playwright config, desktop + mobile
  projects, data-testid locators
- ci/ui.sh wrapper, drone steps test-ui-desktop and test-ui-mobile on
  mcr.microsoft.com/playwright:v1.59.1-jammy

phase 9 (docs):
- CLAUDE.md captures CI URLs, status, storage layout, constraints, and
  the integration-test fixture choice (teeworlds) for future sessions
- /api/ now uses authelia-authrequest-basic.conf (HTTP Basic via Authelia
  forward-auth, matching the owntracks /pub pattern)
- / stays on cookie-based forward-auth (browser flow)
- tests use device_user / device_password basic auth on all /api/* calls
- consolidate api/auth fixtures, drop per-call boilerplate
test buster failed at DNS resolution before snap install — the buster
service container didn't stay reachable through the run. Matching
owntracks (canonical modern reference shape) which dropped buster in
commit 8089a34. Porting-guide note about buster coverage is stale for
new apps; existing apps with installed users still need it.
- web/e2e/package-lock.json committed (was missing, broke 'npm ci')
- playwright.config: httpCredentials from env (PLAYWRIGHT_USER/PASSWORD,
  defaults syncloud/syncloud which match the device fixture used by
  syncloudlib in CI)
- nginx '/' now uses authelia-authrequest-basic so HTTP Basic Auth works
  for the SPA and Playwright doesn't need to drive the authelia login form
- ci/ui.sh: drop '--with-deps' (mcr.microsoft.com/playwright image already
  has the browsers/deps; --with-deps tries apt which is slow + unneeded)
reverting browser '/' to authelia-authrequest.conf — the basic variant
returns 401 instead of redirecting to the portal, so unauth GET / no
longer gets a 200 (test_index broke).

dropping test-ui-desktop/mobile pipeline steps for now. They need a
proper authelia login flow (page.goto('/auth/login'), fill, submit) or
a pre-seeded session cookie — that's its own piece of work. specs and
helpers stay in repo so it can be re-enabled once the login helper lands.
backend/installer/teeworlds.go: Go-native installer that bypasses the
parkervcp bash script (which assumes apt + /mnt/server). Hits GitHub
releases API, finds linux_x86_64 tar.xz, downloads, extracts via
github.com/ulikunitz/xz + archive/tar. ~10MB download, finishes in
seconds.

integration test:
- POST /install, poll /api/v1/servers/{id} for status=stopped (up to 3min)
- POST /start, assert status=running
- ssh 'ss -ulnp | grep 8313' to confirm teeworlds_srv bound the UDP port
- stop + delete cleanup

Teeworlds doesn't speak Source A2S so /api/v1/servers/{id}/query is not
exercised here — the UDP-bound probe is enough to prove the server is
actually serving.
steamcmd is i386. The platform-bookworm-amd64 docker image our snap
runs in has no multiarch — we have to bundle the 32-bit runtime
ourselves.

steamcmd/build.sh: dpkg --add-architecture i386 in the build container,
apt download libc6:i386 libstdc++6:i386 libgcc-s1:i386 zlib1g:i386
libcurl4:i386 libtinfo6:i386 libncurses6:i386, dpkg -x each, copy the
i386 ld-linux + /lib/i386-linux-gnu/* + /usr/lib/i386-linux-gnu/*
into /snap/game-server/current/steamcmd/lib32. ~20MB added.

bin/steamcmd.sh: wrapper that invokes the i386 linker with
--library-path pointing at our lib32 bundle and execs linux32/steamcmd.

installer.steamStartCmd: hlds-cs now generates a startCmd that uses
the same bundled ld-linux to load hlds_linux against
$dir + $dir/cstrike + lib32.

catalog: HLDS Counter-Strike 1.6 (appid 90, ~250MB, anonymous-friendly,
Source A2S-queryable) added as the CI Steam-path fixture — smallest
real dedicated Steam server.

integration test: install via /api/v1/servers/{id}/install (15min timeout
for the steamcmd download), start, poll /query (A2S_INFO) for up to 60s,
assert response has Game == 'Counter-Strike' or Folder == 'cstrike'.
phase 4b's teeworlds install errored to status=install-error with no log
in journalctl — runInstall swallowed the error. Now:

- log start + success/failure of install() with the error message
- new servers.last_error column (idempotent migration via ALTER) so the
  API surfaces what went wrong to the UI
…ort to installer

- installer.Install now takes a port param; uses it as g.DefaultPort
  override when generating startCmd (was always using catalog default
  rather than the user's chosen port)
- teeworlds_srv 0.7 takes config commands as args, not -p/-port flags
  ('No such command: -p.' in journal logs from build #17)
build #18 failed with 'Steam needs to be online to update' — libcurl4:i386
couldn't TLS-handshake because libssl3:i386 (and other transitive deps
like libnghttp2, libidn2, libsasl2, libssh2, librtmp1, libgssapi-krb5,
libpsl5) weren't bundled.

switching the build script from 'apt download <minimal> + dpkg -x' to
'apt install <topdeps>' system-wide, then bulk-copy
/lib/i386-linux-gnu + /usr/lib/i386-linux-gnu. apt resolves the full
transitive closure for us. ~35-40MB lib32 bundle. Also picks up
libsdl2/libgl1 for SrcDS games down the line.

also commits the previously-held wrapper hardening:
- bin/steamcmd.sh exports HOME to /var/snap/game-server/current/.steam-home
  (snap default /data/data/com.termux/files/home wasn't always writable for steamcmd's scratch)
- backend.installer sets cmd.Dir to install_dir before spawning steamcmd

teeworlds (phase 4b) is fully green in build #18: install -> start ->
loaded dm1.map -> rcon password -> master1.teeworlds.com heartbeats.
bin/steamcmd.sh: 'cd $SCDIR' (where the steamcmd_linux bootstrap files
live) instead of HOME. steamcmd's self-bootstrap looks up package/,
linux32/steam, etc. relative to PWD; cd-ing to HOME made it never find
them, surfacing as 'Steam needs to be online to update'.

Also export SSL_CERT_FILE to /etc/ssl/certs/ca-certificates.crt so
the bundled libcurl4:i386 + libssl3:i386 can verify Steam TLS.

test teardown: scp /var/snap/game-server/current/.steam-home/Steam/logs/
stderr.txt + bootstrap_log.txt so next failure's root cause is visible
in the artifact log dir.
steamcmd self-updates its 'package/' dir and writes state next to its
binary on every run. /snap/game-server/current/ is squashfs (read-only),
so even with PWD set right, steamcmd's update check fails (surfaced as
'Steam needs at least 100MB of free disk space').

bin/steamcmd.sh now seeds /var/snap/game-server/current/.steam-runtime/
with linux32/ + package/ + steamcmd.sh on first run, then cd's and execs
from there. lib32/ stays in /snap (doesn't mutate).
HLDS appid 90 alone only fetches the base server stub. The cstrike/
mod files (maps, mp.dll, server binary) come down only when
+app_set_config 90 mod cstrike is set before +app_update. Without
this, hlds_linux -game cstrike fails to find cstrike/maps/de_dust2.bsp.
build #23 regressed at 'github http 403' — CI hit GitHub API rate limit
across rebuilds. The asset URL is stable and not rate-limited; hardcode
0.7.5 and skip the /releases/latest call entirely. Bump constant when a
new release ships.
teeworlds (phase 4b) is fully green and playable in CI. HLDS keeps
failing at steamcmd bootstrap: 'Steam needs to be online to update' +
'public/steambootstrapper_english.txt missing', log files empty so
steamcmd dies before producing useful output.

xfail rather than skip — when we fix the underlying issue (probably
needs the amd64 lib bundle the user asked for next, plus nss-resolver
or libcurl SSL plugin investigation), the test re-runs automatically
and turns from XFAIL to PASS without us touching it.
steamcmd/build.sh now also installs amd64 base runtime (libc6/libstdc++6/
libgcc-s1/zlib1g/libcurl4/libssl3/libtinfo6/libsdl2/libgl1) and bulk-copies
/lib/x86_64-linux-gnu + /usr/lib/x86_64-linux-gnu into
/snap/game-server/current/steamcmd/lib64. Also bundles
/lib64/ld-linux-x86-64.so.2.

installer.steamStartCmd uses two helpers:
- wrapAmd64: \`lib64/ld-linux-x86-64.so.2 --library-path lib64[:extra] bin args\`
  for CS2 / Valheim
- wrapI386: \`lib32/ld-linux.so.2 --library-path lib32[:extra] bin args\`
  for HLDS / TF2 / GMod (SrcDS is 32-bit)
- Zomboid keeps its own JVM wrapper

Steam games copy-deploy engine libs (libtier0_s.so, libsteam_api.so) but
expect host libc/libstdc++/libgcc/libGL for both archs. With this commit,
the snap supplies both. No host multiarch needed.

snap size +~25MB for lib64. Total snap ~80MB.
…install

runs before the xfailed HLDS install test. dumps:
- ldd on linux32/steamcmd (the 'not found' lines tell us exactly what's
  missing from lib32 bundle)
- ld-linux --verify on the binary
- contents of lib32/, plus a grep for SSL/curl/nss/krb5/idn2 libs we
  *expect* to be there
- getent hosts for Steam CDN (DNS sanity)
- curl -sIv https://steamcdn-a.akamaihd.net/ (TLS sanity from the snap)
- bare 'steamcmd.sh +exit' (does steamcmd even start without app_update?)
- /var/snap/.../.steam-home/Steam/logs/stderr.txt (full steamcmd log)
- LD_DEBUG=libs trace of the +exit attempt

no assertions — pure diagnostic. output is in pytest's per-test capture
which CI surfaces directly in the build log.
root cause (from test_steamcmd_diagnostics in build #27):

steamcmd's bootstrap reads /proc/self/exe to locate its install dir,
then statvfs's it for free space. Our wrapper cd'd to RUNTIME (writable)
but exec'd the binary at \$SCDIR/linux32/steamcmd, which is on the
read-only squashfs mount of /snap. statvfs reports 0 free bytes,
steamcmd prints 'needs 100MB' + 'needs to be online' (the network msg
is a cascade — it never actually reaches a socket) and exits.

The 'lib32 missing transitive deps' theory was wrong — the bundle has
every lib needed (libcurl, libssl, libnss_dns, libgssapi_krb5, libidn2,
libnghttp2, libpsl, libsasl2, libssh2, librtmp, ...). Confirmed by the
diagnostic ls dump. Host TLS to steamcdn-a.akamaihd.net also works
fine from inside the snap container.

fix: exec \${RUNTIME}/linux32/steamcmd instead. /proc/self/exe now
points under $SNAP_DATA which has plenty of space.

drop the xfail on test_hlds_cs_real_install_and_query — if this analysis
is right, HLDS install should now reach the network and download the
~250MB game files.
…wn libstdc++

inspecting steamcmd_linux.tar.gz contents:
  linux32/
  linux32/libstdc++.so.6   <- 2013-era 32-bit libstdc++ Steam bundles
  linux32/steamcmd
  steamcmd.sh
  steam.sh

official steamcmd.sh: \`export LD_LIBRARY_PATH=$STEAMROOT/$PLATFORM:$LD_LIBRARY_PATH\` —
linux32/ wins over system libs. The 2013 steamcmd binary was built
against that libstdc++ ABI; bookworm libstdc++6 has newer symbol
versions, breaking steamcmd init silently. Surfaces as misleading
'needs to be online' / 'needs 100MB' errors before any network call.

fix: prepend ${RUNTIME}/linux32 to both $LD_LIBRARY_PATH and
ld-linux --library-path. lib32/ stays as fallback for libssl/libcurl/
libnss/libkrb5/libidn2/libsdl2/libgl1 which Steam doesn't bundle.
cyberb added 30 commits May 14, 2026 19:40
…d CA

The cli configure hook installs /var/snap/platform/current/syncloud.ca.crt
into /usr/local/share/ca-certificates/ + runs update-ca-certificates,
so curl/openssl/glibc default to a trust bundle that already includes
the Syncloud CA. Pinning SSL_CERT_FILE to one hardcoded path is just
duplicating what the OS already does (and would lock us in if the
platform image relocated its bundle).
Was making an HTTPS call to https://auth.<domain>/api/authz/auth-request
from inside the snap — pointless round-trip through nginx + TLS for a
purely local-to-the-host check. Mirror platform's AutheliaHealth
pattern: http.Transport.DialContext returns net.Dial("unix",
/var/snap/platform/current/authelia.socket), request URL uses a dummy
host (http://authelia/api/authz/auth-request) since the dialer ignores
it. Headers and response handling unchanged. tlsClient stays — OIDC
discovery + token exchange are still over HTTPS for now.
Backend opens a second unix socket /var/snap/games/current/cli.sock
that exposes the same /api/ handlers without the auth middleware —
socket permissions are the gate (games:games 0660, root + games user
only). The first socket (backend.sock, behind nginx) keeps the OIDC
session middleware for browser traffic.

Added subcommands to the existing snap-exposed `games.cli` (Cobra):
  server  list / show / create / install / start / stop / restart /
          delete / logs / query
  games   list / sources
  health

All support --json for scripting (default human-formatted output).
Client is a thin http.Client over the cli.sock with the dummy
http://backend/<path> URL.

Removed userFromBasic + the Authelia local-socket plumbing it needed:
the REST surface behind nginx is browser-only now, no more
Authorization: Basic admitted. Programmatic / test access goes
through games.cli.

test/test.py rewritten to drive everything via SSH+games.cli through
a small test/cli.py helper. Same test cases, none of them are
loadbearing on the HTTP API any more.
The wrapper invokes ld-linux with an explicit --library-path which is
all that's needed for the steamcmd binary's NEEDED entries to resolve
against the bundled lib32. The exported LD_LIBRARY_PATH was extra
cover for dlopen() / subprocess loads — try without; CI's full HLDS
install (which exercises both) tells us if anything in steamcmd
actually relies on the env var.

The runuser -u games in the binary test was leftover from when the
wrapper had a root-detecting chown branch. That branch is gone, so
the wrapper runs identically regardless of uid. Removing the user
plumbing in the test too: just symlink and invoke.
Pre-bootstrap (`+quit` at build time) was added in commit 92 and has
been silently failing every build since — the bootstrap downloads the
new client, exec's it, and the re-exec'd binary exits 42 ("Steam
client init failed") with no useful diagnostic. None of the
subsequent wrapper simplifications fixed it because they were
unrelated.

Going back to the proven setup: the wrapper copies steamcmd's
mutable bundle out of /snap (RO squashfs) into $SNAP_DATA/.steam-
runtime on first run, then invokes ld-linux from the writable copy
so STEAMROOT lands writable. LD_LIBRARY_PATH stays exported because
steamcmd dlopens plugins at runtime.

Kept dropped: the id==0 chown branch (prod always runs as games per
snap.yaml) and the HOME_OVERRIDE indirection (HOME is hardcoded).

If we want a truly RO install later we'll need to figure out what
exit-42 actually fails on — but that's a focused investigation, not
this commit.
After a self-update steamcmd exits 42 to ask the shell to re-exec it. Our
wrapper used exec ld-linux and propagated 42 to the caller. Loop on 42
to match the original /usr/games/steamcmd behaviour.

Reproduced in a privileged debian:bookworm container on 192.168.1.101 with
the steamcmd tree squashfs-mounted RO via squashfuse: old wrapper rc=42,
new wrapper rc=0 on both first and cached runs.
…st import lib)

When test/__init__.py is present, pytest treats test/ as a package and
bare 'from cli import …' resolves outside the dir. Bitwarden uses
'from test import lib' for the same reason — mirror it.
Move the runtime-copy from steamcmd.sh into installer.SeedSteamRuntime,
called from UpdateConfigs (install + post-refresh). Wrapper is now just
env + exec — it assumes $SNAP_DATA/.steam-runtime/linux32/steamcmd is
already seeded. ld-linux.so.2 is always re-copied from our lib32 closure
so it tracks revision changes; the rest of the tree is seeded only if
linux32/steamcmd is missing (preserves post-bootstrap state across
refresh). steamcmd/test.sh seeds inline before invoking the wrapper
since the cli binary isn't built yet at that step.

Verified on 192.168.1.101 in a privileged debian container with a real
RO squashfs mount: rc1=0 (bootstrap), rc2=0 (cached).
ssh non-interactive shells don't include /snap/bin in PATH, so bare
'games.cli' returns 127. Use the snap-alias absolute path instead.
Two direct ssh calls in test.py still ran bare 'games.cli'. Same PATH
issue — switch them to the absolute path.
syncloudlib's run_ssh uses subprocess shell=True, so unescaped $? gets
expanded locally (to the test client's prior exit, usually 0) before
reaching ssh. Escape it as \$? so it passes through.

Also drop the duplicate error print in cli/main.go — cobra already
prints 'Error: ...' to stderr, the extra fmt.Print(err) was a leftover
that wrote the same message a second time to stdout.
…rent ops

Installer goroutine writes status/progress to servers while cli reads via
'server show' (polling at 2s). With the default journal mode, readers
were getting 'database is locked (5) (SQLITE_BUSY)'. WAL lets readers
proceed while a writer is active; busy_timeout(5000) gives SQLite up to
5s to retry transient locks before erroring.
Diagnostic-only test (no assertions) from the phase-3b HLDS debug era.
Its diag.sh used '+exit' (not a real steamcmd command) which hangs the
test session indefinitely when stdin isn't a TTY — happened on #108
and would happen on every subsequent run. The steamcmd path is now
exercised by the steamcmd test step plus the teeworlds and minecraft
integration tests; no need for a redundant diag.
Drop the html reporter and trace.zip outputs — neither was useful from
the artifact server. Reporter is 'list' (drone log captures it).
Screenshots-on-failure and video-on-failure stay enabled.

Post-step walks test-results/<test-dir>/ and copies any *.png into
artifact/screenshots-<project>/<test>.png and video.webm into
artifact/videos-<project>/<test>.webm. Green builds produce empty dirs;
red builds produce one file per failing test, named after the test.
Per-device dumps now land directly at artifact/bookworm/*.log instead
of artifact/bookworm/log/*.log — one less directory level on the
artifact server.
Global config keeps video: 'retain-on-failure' (failures-only) to keep
artifacts lean, but force video: 'on' for 02-install.spec.ts so each
green build leaves a demo recording of the catalog → install dialog →
server detail → delete cycle in videos-<project>/.
info.attach({ body }) stored PNGs inside playwright-report/data/ keyed by
content hash — only retrievable via the html report (which we dropped in
a20020a). Switch to page.screenshot({ path: info.outputPath(...) }) so
each call lands as test-results/<test-dir>/<name>.png, then the ui.sh
flatten step copies them into artifact/screenshots-<project>/.
fullPage: true captures the entire scrollable document, which stamps
position:fixed elements at whatever viewport position they happened to
be at scroll=0 — making the mobile bottom-nav appear mid-page in the
screenshot. Viewport-only captures match what users actually see on
screen, with the nav anchored to the bottom edge.
…t clip content

Firefox/Chrome mobile autohide the address bar on scroll. The visual
viewport shrinks/grows; vh refers to the largest size, so page height
based on vh overshoots when the bar is visible. Switch to 100dvh
(dynamic viewport height) with a 100vh fallback.

Also bump main's bottom padding to clear the fixed bottom-nav + safe
area + a 48px buffer for the autohide transition, so the last few
lines of long pages (About) stay reachable on Firefox mobile.
CSS:
- .bottom-bar uses position: sticky instead of fixed so it takes real
  layout space — content can no longer hide under it on Firefox/Chrome
  mobile autohide-bar transitions.
- drop the calc() main bottom padding and the scroll-padding-bottom /
  scroll-margin-bottom hacks that compensated for fixed positioning.

SettingsPage:
- drop the catalog-sources tagline and the WIP/platform#35 line in about.
- on Steam login success, set steam.linked and steam.linkedUsername from
  the response so 'Connected as <user>' appears immediately rather than
  waiting for a status refetch.

Stub:
- /api/v1/steam/login mutates a closure-scoped steamState; /steam/status
  reflects it. Matches real-backend behaviour for the dev stub.
…verlap

Sticky bottom-nav only stops overlaying content at the very end of the
page scroll — mid-scroll it still pins to viewport bottom, hiding cards
underneath it. Restore html { scroll-padding-bottom } and .card {
scroll-margin-bottom } so scrollIntoView positions cards above the
sticky nav. Without them, the mobile install spec's click on the
Teeworlds Install button fell under the nav and timed out.
Installer no longer copies the platform CA into /usr/local/share or runs
update-ca-certificates. Backend reads authSocket from oidc.json and dials
/var/snap/platform/current/authelia.socket directly; a small RoundTripper
rewrites the https:// URLs in the discovery doc to http:// before sending.
Browser-facing AuthCodeURL stays the public HTTPS URL — only the
server-to-server hops (discovery, token, JWKS) move to the socket.
Removes the package-level init that panicked on bad JSON; main now calls
catalog.Start() explicitly and exits cleanly on parse error. Same global
state, same accessors — only the lifecycle moves into the caller's hands.
Drops hand-curated backend/catalog/steam.go (8 entries). catalog/convert
now also walks a LinuxGSM checkout: parses lgsm/data/serverlist.csv to
enumerate games, then reads each game's lgsm/config-default/config-lgsm/
<name>/_default.cfg for appid + port. Any game with appid > 0 is emitted
with source="linuxgsm" — that produced 108 Steam servers from v26.1.0.

A small shortname remap (cs→hlds-cs, vh→valheim, pz→zomboid) keeps the
IDs that integration tests and existing servers in users' DBs depend on.
An override map preserves the human-curated tier + summary + protocol
hints for the 8 games steam.go used to ship; everything else lands as
tier=experimental with a generated summary. The Pelican path keeps its
own entries — same game can appear under two sources (different install
paths), differentiated by the source badge.

Pinned to LinuxGSM v26.1.0 (d05992d7d2deb88ed0a0c9df5ffc423995947d11),
exposed under sources["linuxgsm"] in catalog.json so the UI / API show
the bound version like the other two sources.
build 124 failed test_hlds_cs_real_install with 'unknown source
"linuxgsm"' — installer dispatcher only knew the old steam.go label.
LinuxGSM entries install via the same SteamCMD path; UI hint + source
badge follow the same equivalence.
… main

installer.go shrinks to Installer + dispatcher; steam.go and egg.go own
the per-source install logic with their own struct + ctor. teeworlds
native fallback moves to a method on EggInstaller. main wires it up:

  inst := installer.New(
      installer.ServersBaseDir,
      installer.NewSteamInstaller(SteamCMDPath, SteamLib32, SteamLib64),
      installer.NewEggInstaller(JREBinDir),
  )

and the install handler chain (handleServerByID → handleServerAction →
runInstall) takes the Installer through instead of calling the old free
function. Lib32/lib64 paths and JRE bin now live as fields on their
respective installers, not package-level globals reached by free fns.
db.New(path) takes the dsn as a field; Start() opens the conn and runs
the schema (was the old Open() one-shot). Server-record CRUD that used
to live in server.Store moves onto *db.DB as ListServers/GetServer/
CreateServer/UpdateServerStatus/UpdateServerInstall/UpdateServerLastError/
DeleteServer — callers no longer touch database/sql. The 'errors.Is(err,
sql.ErrNoRows)' check in main was dead (Exec never returns ErrNoRows on
DELETE) so dropped along with the imports. server pkg shrinks to the
domain struct.
Catalog Game now carries InstallRecipe {method, url, steamAppId,
steamArgs} and Start {binary, wrap, extraLibs, args}. The converter
auto-derives recipes for two methods:
  - steam:           +app_update N grep
  - downloadExtract: literal URL ending in .tar*/.zip
Eggs whose install script needs apt/docker/non-bash get no recipe and
land as tier=unsupported.

A new catalog/overrides.json owns hand-curated knobs (source, tier,
summary, install recipe, start template) for the games we ship with
real start commands — teeworlds + the 8 Steam servers steam.go used
to encode. The lgsmOverrides map inside the converter is gone.

Runtime backend/installer is now one type, RecipeInstaller, with three
methods: installSteam, installDownloadExtract, renderStart. Source-based
dispatch and the EggInstaller / SteamInstaller / teeworlds.go special
case are deleted. Start commands are computed from the Start template
(installer reads StartRecipe.Wrap to decide i386/amd64 lib-loader wrap;
extraLibs entries of "." mean the install dir itself).

UI badge + dialog now branch on installRecipe.method (was source).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant