# Changelog ## 1.7.3 — 2026-10-16 ### Fixed - **Wine deploys start on Wine 10+ images.** The generated start script called `wine64`, a launcher Wine stopped shipping — on the current WineHQ base image (wine 11) the first boot died with `wine64: not found`, in a loop. The script now asks the image which binary it has (`wine64` or `wine`) and refuses plainly when neither exists. Found live, on this project's own Enshrouded server, minutes after recommending the migration — the fix went to the running server first and to the generator second. ## 1.7.2 — 2026-10-15 ### Added - **The Settings tab warns about traps the config file does not show.** A game's file can carry a rule no field reveals: Enshrouded ignores its entire `gameSettings` block — durability, difficulty, day length — until `gameSettingsPreset` says `Custom`, so a hand-tuned setting under any other preset looks saved and does nothing (found the hard way: durability off, and tools kept wearing out). Config files can now carry a game-specific heads-up, shown on the Settings tab whenever the file is in such a state. Advice, never enforcement: the file stays yours to edit. ### Fixed - **"A restart installs it" became "usually", and GameKeepr's own servers earn the stronger claim.** On Enshrouded's release day a restart kept answering "already up to date" while Steam demonstrably shipped a new build — SteamCMD sessions can hold stale app info and skip an update that exists. The composed Steam servers' start script now forces `+app_info_update 1` before every `+app_update`, so their restarts really do install what Steam ships; the update card is honest about template images, where GameKeepr does not own the script: stale app info can delay the update briefly, and the card names the manual escape hatch. ## 1.7.1 — 2026-10-15 ### Fixed - **Setting Enshrouded's player password no longer stops the server from booting.** 1.7.0 wrote the player password onto the Friend *and* Guest groups — and Enshrouded refuses to start when two groups share a password (`Internal Error: user groups passwords must be unique`), which a save from the Settings tab or a fresh deploy's auto-apply could trip. The player password now goes to the Guest group alone; the Friend group gets a fresh random password in the same edit, so it stays unique and never keeps an image template's publicly-known default. A new whole-file check also refuses — in the form, before any restart — a player and admin password that are identical, since the game would refuse them too. ## 1.7.0 — 2026-10-14 ### Added - **The Joining card's promise is now kept by the portal itself, not by hoping the image reads its env.** The typed join settings always landed on the container as environment variables — which only ever worked for images that read them. One that does not (Nodiaque's Enshrouded, say) booted wearing its template's own name and its template's publicly-known passwords, and the only clue was the verification's "calls itself something else" note. Now the deploy verification finishes the job: once the first boot proves the game up — the one moment its config file certainly exists — the typed values are checked against the game's **own config file** and, only when they differ, written in (through the same backed-up surgery the Settings tab uses) with exactly one automatic restart to load them. The deploy screen narrates it, the outcome note records it, and images that already honoured their env are left untouched — no churn, no second restart. Games whose images regenerate the file from env on every boot are skipped on purpose; there the env *is* the file's source. - **Enshrouded's passwords became editable — nested where they really live.** The registry's config-file surgery only knew top-level keys, and Enshrouded keeps its passwords per user group since the userGroups update; the Settings tab honestly refused to touch them. JSON config files can now carry accessors for nested values: for Enshrouded the player password sets the Friend **and** Guest groups (one join password, and no group left wearing an image template's publicly-known default) and the admin password sets the Admin group — on the Settings tab and in the deploy auto-apply alike. All notable changes to GameKeepr. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow [semantic versioning](https://semver.org). The `latest` image on ghcr.io moves only when a version is released; `edge` tracks the main branch between releases. ## 1.6.0 — 2026-10-10 ### Added - **The owner can remove config-file servers from the portal.** A server from `config/servers.json` used to answer the bin with "remove it there" — but the file is mounted read-only on purpose, so the portal now does its own half honestly: the entry is recorded as removed and stops being listed, schedules, access exceptions and caches go with it, and the container (which the portal did not deploy) is deliberately left untouched. The confirmation on the card says exactly that. Deleting the line from the file stays yours to do; re-adding it later brings the server back. - **Opted-in installs report hourly instead of daily.** The public statistics page sums each install's newest report, and a day-old number on a page that counts running servers and players was the stalest thing on it. The payload is unchanged — same fields, shown literally before you opt in — only the cadence moved. The status strip now says *Hourly snapshot*, and the page footnote is honest about stragglers: older versions still report daily. - **Every shipped Steam server now carries its OS, checked up front.** The Enshrouded lesson, applied to the whole list: a new generator (`server/scripts/enrich-steam-os.mts`) classified all 579 shipped servers through SteamCMD's own app info — importing the deploy preflight's classifier rather than reimplementing it, so the baked value and the deploy-time judgement cannot drift apart. The result: 283 are Windows-only (they run through Wine and the row now says so with a **Windows-only · Wine** badge), 237 ship both builds, 42 are Linux-only, 9 have no server build at all (**no server build** badge), 8 are delisted. The badge informs; the deploy still inspects the app live and decides from that. - **The Unraid catalogue shows every game server, not only trusted publishers'.** The feed was always read whole; templates from authors outside the trusted list were silently dropped, which read as "GameKeepr cannot run this game" when the truth was "this author is not on your list". They now appear behind a **community publisher** badge, sorted after the trusted ones, and deploy only past an explicit warning the operator acknowledges — you are trusting that image's author with the server's data and your network, and the portal says so instead of deciding for you. What does not change, for any publisher: privileged mode, host devices, forbidden host paths and host networking are refused outright, and every mount stays inside the server's own directory. `TRUSTED_PUBLISHERS` in `.env` still names who deploys without the warning. Validation runs still skip unlisted publishers: they start containers unattended, which is no place to execute an unknown image. - **Windows-only Steam servers now deploy — through Wine.** Plenty of games (Enshrouded, Sons of the Forest) publish no Linux dedicated server at all, and the Steam tab used to be a dead end for them. GameKeepr now composes those on ich777's WineHQ base image — the same author whose per-game Wine containers are the Unraid community's standard way of running these servers — forces SteamCMD to the Windows build, keeps a 64-bit Wine prefix inside the serverfiles volume (so it survives recreates and is deleted with the server's data), and starts the `.exe` under `wine64` on a virtual display. The deploy form says all of this up front and asks for an acknowledgement, because Wine is proven but not native. An app with neither a Linux nor a Windows build is still refused, now with that exact reason. - **"Unknown" on the statistics page became a wish list.** A server whose game the registry does not recognise used to count as `Unknown`, which told nobody anything. Those servers are now named from GameDig's public catalogue of 358 games and reported separately as `gamesWanted` — the public page shows them under **Most wanted**, so everyone can see which games people already run on GameKeepr that it does not fully support yet. The safety rail: a game id is only ever reported when it appears in that public catalogue, so a hand-written `type` in someone's `servers.json` can never leak out as text — it still counts as `Unknown`. - **A Star button on the project page.** GitHub has no URL that stars a repository — starring is an authenticated write, and a link that performed one would be a security hole — so the button is honest about what it does: it opens the repository, where the Star button is, and says so on hover. The count beside it is read from GitHub's public API by the reader's own browser and simply stays hidden if that call fails, rather than showing a stale number baked in at build time. - **The same, once, inside the portal.** The people most likely to star this are the ones already running it, so the wiki's Welcome page closes with a single line about it — no pop-up, no badge, no counter that keeps asking. It says what the link does and that skipping it changes nothing. ### Changed - **The public statistics page reads like a command centre.** A status strip under the title says what the page is — a snapshot, with a still dot — when the last ping landed, how many installs reported in the last 24 hours and how many days of history exist. The four headline tiles carry a sparkline and, once there is a full week to compare against, how much they moved — never before, because a trend invented from two days is exactly the kind of confident nonsense this page exists not to produce. Games and the wish list became a board of counts instead of bars: every install runs one or two of a game, so a bar chart there was six bars of identical length, a shape that looks like data and carries none. The two trends now sit side by side, and *Versions in use* says why it is there — whether an update actually reached anyone. On a phone the tiles are a 2×2 board instead of a column. Still no chart library: this is a Cloudflare Worker, and every form is hand-rolled inline SVG on the validated palette. - **The documentation was audited against the code before this release.** Twenty stale claims found and fixed across the README, wiki, changelog, `.env.example` and the Unraid template: the catalogue and Steam sections now describe the warn-and-acknowledge publisher model and the Wine path, every "once a day" became "once an hour", the router section names all four providers, and the `SESSION_SECRET` comment admits it guards every stored integration credential, not just the router's. One real bug surfaced by the audit: the template review told operators a host-networking template "will be deployed on a bridge network instead" while the deploy refuses it — the review now refuses it too, with the same reason. The Steam-catalogue and statistics-invite screenshots were re-shot on 1.6.0 (Wine badges, hourly wording). - **Fresh screenshots on the landing page and in the wiki.** The shots on [the project page](https://kengoossens.github.io/Gamekeep/) dated from before 1.0.0 and showed a portal that no longer exists. Servers, the command centre, the Steam catalogue and the log console were all re-shot on 1.5.0, and the page now also says what deploy verification, tournaments and update detection do — three features it never mentioned. ### Fixed - **A Windows-only server no longer composes a container that can never start.** Steam's app info often leaves the OS off a launch entry (Enshrouded's dedicated server lists plain `enshrouded_server.exe` with no OS at all), and "no OS given" used to count as Linux-capable — so the preflight waved the deploy through, SteamCMD downloaded nothing for the Linux platform, and bash tried to run a Windows `.exe` forever. A launch entry's file name is now the honest tiebreaker (`.exe` means Windows), the download size shown at deploy time counts the depots that will actually be pulled, and the preflight routes such apps to the new Wine path instead. - **A server deployed from the Steam catalogue gets its artwork immediately.** The Steam deploy route saved the recognised game's app id but never started the artwork download — only the template-catalogue route and portal startup did — so a freshly deployed Steam server sat on a lettered tile until the next portal restart. The route now fetches the poster right after the deploy, the same way the catalogue path does. - **The Rename and Delete buttons on a server card are visible again.** They were fully transparent until the mouse hovered that exact card, which read as "servers cannot be deleted" rather than "hover to see more". They now rest dimmed on every card an owner or operator can act on, and brighten on hover or focus. (Servers defined in `config/servers.json` delete too — see the Added entry above: the entry is recorded as removed, the container is left untouched.) - **The statistics page called itself live, and it is not.** Every number on that page is the sum of each install's most recent report — now at most an hour old for current versions, up to two days for a straggler. A pulsing "LIVE" dot over that is the one dishonest thing on a page whose whole argument is that it says exactly what it knows. It now reads *Hourly snapshot*, with a still dot. Three labels went with it: "N pings in the last 24 h" counted installs, not pings (only the newest report per install is kept), "servers up right now" is the count at each portal's last report, and "players at today's peak" sums each portal's own busiest minute, measured at different moments. - **"Unknown" no longer appears as a most-wanted game.** A portal older than the wish-list split reports an unrecognised server as `Unknown` inside `games`, and the compatibility rule moved it onto the wish list — where it showed up as a bar literally labelled *Unknown*, which told a reader nothing and read like a bug. Those are now counted apart and written out as one sentence under *Games people run*: how many servers run something neither the registry nor GameDig could name, and why that is by design. Requires a mothership deploy to take effect. - **A failed metrics write could take the whole portal down.** The sampler runs on a timer with no error handler, so a single failing insert — a database locked by a backup, a full disk — became an unhandled rejection and Node exited. Caught while shooting the new screenshots, where a second process held the database for a moment. The write is now guarded and the sample is dropped with a line in the log; the same guard was put on the down-detector and the update sweep, which had the same shape. ## 1.5.0 — 2026-10-08 ### Added - **An invitation to be counted, shown once.** A fresh install answers the statistics question during setup; a portal that *upgraded* into the feature never saw that box and would never learn the invitation existed. Owners now get a single dialog — with the same literal payload in it — offering *Yes, count me in* or *No thanks*. Either answer spends the invitation: nobody is asked twice, and the Settings switch stays available forever. - **The public statistics page became a real dashboard.** Four headline tiles (active installs, servers watched, servers up, players at today's peak), a bar chart of the games people run, a donut of the platforms, a bar chart of which features get used, and two trends over time — installs and the daily player peak. Charts are hand-rolled inline SVG (a worker ships no chart library), with a palette validated for the dark surface: worst adjacent colour-blind separation ΔE 8.4, and every value is also written as text, so colour is never the only channel. - **Richer, still-anonymous numbers.** The daily ping now also carries how many servers are *up*, how many people are playing right now, the busiest minute of the last day, and four more feature booleans (backups, mods, Discord notifications, router integration). Counts only: player names are known to the portal and never leave it. The wiki's table documents every new field, and the preview in Settings shows them before anyone opts in. ### Fixed - **An Unraid box could report itself as plain `linux`.** Platform detection hung on a template mount whose path can differ from the variable naming it; it now also reads the `HOST_OS` marker Unraid stamps into its containers. - **Copying `.env.example` silently disabled statistics forever.** It carried an empty `TELEMETRY_ENDPOINT`, which is the "send nothing, ever" setting — so anyone following the Docker install instructions could opt in and still never be counted. The line is commented out now, and says what an empty value means. - The stats page prints human time (in the reader's own clock and language) instead of `2026-10-07T21:30:47.688Z`, and its trend axes read `6 Oct` rather than `2026-10-06`. - The README's link to the Community Applications template pointed at a path that does not exist (`unraid/`); it is `templates/gamekeep.xml`. ## 1.4.0 — 2026-10-07 ### Added - **Anonymous usage statistics — off by default, opt-in, radically transparent.** An unticked box at setup and a switch under Settings that shows the **literal payload** before you decide: one daily ping carrying the version, the platform, recognised *game names* with counts (never your server names, addresses, ports or logs), three feature booleans, and a random install id that identifies nothing. The receiving side is in the open repository too (`mothership/` — a Cloudflare Worker that never reads your IP and sweeps inactive installs after 90 days) and feeds a **public statistics page**, so everyone who shares the numbers can see them. The wiki's security page documents every field, line by line. - **Report an issue, without error telemetry.** Logs and stack traces carry player names and addresses, so GameKeepr deliberately never collects them. Instead, every server's Controls card carries *Something wrong? Report an issue*: it opens a GitHub issue prefilled with that server's sanitised context — game, container state with exit code, update strategy, Steam build comparison — visible in the form before you press submit. Never server names, addresses or log contents; consent per report, nothing silent. A portal-wide button sits next to the statistics switch, and both features have their own wiki pages now. ### Fixed - The Sign out button no longer folds onto two lines on wide screens. ## 1.3.0 — 2026-10-04 ### Security & hardening Findings from a three-pass adversarial review of everything added since 1.2.1 (correctness/concurrency, security/resources, principles/efficiency). All confirmed findings fixed: - **A validation run can no longer touch anything it did not create.** The worst finding: a real server deployed under a `gk-validate-*` name could be force-removed — volume, world and all — by a later validation run's teardown. Deploys now refuse the reserved prefix outright, validation keeps its hands off any container that already exists under its name (skipped, with the reason), a name conflict never triggers a teardown, and the delete-downloads guard demands the exact appdata parent plus the prefix instead of a loose substring match. - **Update detection actually works now.** The feature shipped reading its Steam install receipts through the hand-editor's file reader, which refuses `.acf` files — so every server reported "no receipt". The receipt is now read through a dedicated confined reader; the volume search walks only the server's own data roots with `-xdev` (never `/proc`, `/sys` or a host bind), a failed search keeps the previous verdict instead of overwriting it with a confident wrong one, "Check now" has a 60-second cooldown, stale entries are pruned when servers are deleted, and a successful restart clears the badge immediately. - **A failed game-settings save no longer leaves the server stopped.** Everything that can refuse (missing file, missing key, broken JSON) now runs *before* the stop, the restart sits in a `finally`, concurrent saves and saves during a running restart job are refused with a 409, and match servers are off limits. A save's re-verification now audits and reports as a settings change — not as a "deploy", which is what Discord used to hear. - **Crash loops are called failures again.** The deploy verification only ever saw `exited` containers as failed, and `unless-stopped` restarts hid exactly that; the watcher now reads Docker's restart counter and settles `failed` after three revivals. A container removed mid-watch ends the watch honestly instead of being polled for the rest of an hour, a second verification on the same server supersedes the first, and when download activity cannot be measured the verdict says so instead of claiming "nothing has moved". - **A `$` in a password no longer corrupts XML configs** (JavaScript's replacement-pattern rules were mangling `pa$$word` on the 7DTD path), and a Steam app name with control characters can no longer smuggle a line into the generated root start script. - **Bracket overrides now heal downstream byes.** Overriding a result whose loser had already received an automatic bye used to leave the old beneficiary seeded in both brackets; automatic byes (never played, never overridden) are now re-derived from the corrected graph. Double elimination's drop-ins also use the conventional mirrored slot, so "lost to you in round one, meet you again immediately" stops being a coin-flip certainty. - Assorted smaller fixes from the same review: port-conflict refusals name the owning container, validation targets are capped (50 per run) and parsed by one shared function, the settings-scan caches its file location instead of re-searching the volume on every tab open, UI pages stop polling when the tab is hidden and a settled verification stops polling at all (and no longer vanishes from an open page), duplicated recreate and Steam-scaffold logic now has a single owner, and a validation run's bookkeeping failure can no longer crash the portal. ### Added - **Update detection: the portal now says when Steam ships a newer build.** Every six hours each server's installed build — read from its own Steam install receipt (`appmanifest` in the volume), so it works whatever image built the server — is compared against the current public build from Steam's app info. When Steam is ahead: an **update** badge on the card, a Game updates card on the overview with both build ids and the one sentence that matters (*a restart installs it* — these servers run SteamCMD on every start), and a Discord notification **once per build**, never once per polling round. Operators get a Check now button; a server without a Steam receipt honestly says it cannot tell instead of claiming to be current. - **Validation runs can be scheduled.** Save the current selection with a time and days — the night before a release is the classic — and the run fires on the portal's own clock. The same three rules as every other schedule here: a missed minute stays missed, it never stacks on a run already going, and the outcome still lands in the history and the Discord summary. ### Fixed - **The dashboard's window buttons work again.** The 1h/6h/24h/7d switch had stopped sending its value with the request, so every click silently refetched the default six hours. The buttons now really change the window. - **The navigation bar no longer folds.** Adding the Validation page made "Add server" wrap onto two lines; nav labels now never wrap, and on a width where the items genuinely do not fit the strip pans instead of stacking. ### Added - **Validation runs: the portal proves itself on your own machine.** A new owner-only Validation page validates **both deploy paths**: any Unraid catalogue app and any Steam dedicated server, picked through the same searches as the Add server tab — not just the games the registry recognises. Steam targets walk the Steam tab's own road (inspect, compose, generated start script); one without a Linux build is *refused* with the same words a deploy would use, and one that refuses anonymous downloads is *skipped* honestly, since a run without an account can prove nothing about it. A recognised game is held to the full bar (it must answer as the name it was given); any other app is verified as far as it honestly can be, its port accepting connections, and the outcome note says which bar was used. Each app is deployed for real — one at a time, under a validation name, never on the Servers page — followed through its first boot, and then torn down completely: container, volume, downloads. Outcomes are the verification's three plus two honest extras: *template refused* (the catalogue template fails the same review a user would hit) and *skipped* (this attempt could not run, said with the reason). Validation servers publish no host ports — the verification talks over the internal Docker network — so a run never collides with a live server of the same game (Satisfactory and a Terraria validation can both think 7777 is theirs) and a throwaway server never stands open on the LAN. Runs land in a history on the page and as one Discord summary. The deliberate price, per ADR-0003: no download cache survives a run — a pass on last month's download would prove nothing about the path a new user walks. - **The registry now knows where ten games keep their real settings.** The Settings Scan grew from three games to ten — Minecraft (Java and Bedrock), Project Zomboid, 7 Days to Die, Factorio, Terraria, V Rising, ARK, Conan Exiles and Enshrouded — each researched against the game's own documentation and the source of its common Docker image, never guessed. Sectioned INIs (ARK keeps its name and its passwords in different sections) and JSON configs (Factorio, V Rising, Enshrouded) are now understood, V Rising's factory-defaults copy under StreamingAssets is filtered out in favour of the live file, and typed join settings were added for ARK (name, map, password, admin — no spaces, as its launch line demands), Rust (name plus RCON, its only credential — vanilla Rust has no join password, and the portal now says so instead of warning about one), Core Keeper (name, and a password that only direct connections ever ask for), V Rising and Enshrouded's role-group passwords. Just as deliberate is what is absent: Satisfactory is claimed in-game, Space Engineers hashes its password, Soulmask configures via launch arguments, Lethal Company has no dedicated server — each absence documented in the wiki rather than papered over. - **The Settings Scan: the game's own config file, found and edited in place.** For games the registry knows well enough (Minecraft, Project Zomboid, 7 Days to Die to start), the Settings tab now locates the file the game actually loads — `server.properties`, `servertest.ini`, `serverconfig.xml` — inside the server and shows its join settings with labels. The file beats environment variables as a source of truth where the image allows it: its keys are defined by the game and identical under every image, while env spellings differ per maintainer (the Valheim lesson, attacked at the root). Saving stops the server, edits only the asked-for lines (same timestamped backup as a hand edit), restarts, and then runs the same first-boot verification a deploy gets — the game answering as the name you just set is the proof the change landed. Images known to regenerate the file from environment variables (Minecraft's itzg) are shown read-only with a pointer to the matching variables, instead of offering an edit that would be silently undone. A verified deploy now points at this section for the "now set your server name and password" moment. - **Deploy verification: created is not the same claim as works.** Every deploy now gets a first-boot watch that follows the new server until the game itself answers — and where the game reports a server name, until it answers *as the name it was configured with*, which proves the settings actually landed (the failure the Joining-card bug taught us). Three honest endings: **verified** (the game answered as itself), **unconfirmed** (it runs but full proof never came — a silent game, a mismatched name, or no query protocol to ask), and **failed** (the container died; its exit code and a pointer to the Logs tab come along). First boots get first-boot patience: past the game's normal startup budget the watch keeps waiting as long as the download demonstrably progresses (network and disk counters moving), up to an hour — slow is never reported as broken. The outcome lands on the deploy screen live, in the activity feed, and on Discord (three new notification events). A failed first boot keeps the server by default — a 35 GB download is not thrown away over a late answer — with "clean up" one deliberate click away. - **Deploy preflight.** The values you type into a deploy form are now judged before anything exists, in the same pass/warn/fail report language the template and image reviews already speak. A join setting that breaks the game's own rules is refused in the form (a four-character Valheim password used to boot a server that then refused to start, minutes later and much more quietly); deploying without a server password proceeds only after an explicit acknowledgement that anyone who finds the address can join. - **The Steam tab gained the review step it never had.** Whether an app allows anonymous SteamCMD downloads is judged up front from Valve's own dedicated-servers list (103 apps carry a definitive answer; three with contradictory wiki rows are deliberately left "unknown" rather than guessed): a known "no" warns you to set a Steam account before the first start fails on it, instead of an hour into the download in a log line. A wrong list entry never blocks a deploy — acknowledge and try. The missing Linux build refusal now speaks the same report language. - **Admin passwords are a first-class join setting.** Games with a separate administrator credential (Palworld today, more as the registry grows) ask for it at deploy time; afterwards it is visible to operators on the Joining card and never to members, masked in Settings like every other credential. - **Text settings can carry length rules.** The registry can now say "at least five characters" about a password, and both the deploy form and the Settings tab enforce it — Valheim's five-character minimum is the first. - **Double elimination and round robin.** The tournament's format is picked at creation: single elimination (the classic), double elimination (a losers bracket catches every first loss; the two survivors meet in a single grand final — no bracket reset; needs at least three teams) or round robin (everyone plays everyone once, the standings crown the champion). Byes work in every format — in double elimination a bye never produces a losers-bracket opponent, and the bracket accounts for that on its own. The bracket, the schedule columns and the public page all speak the format's language (winners/losers rounds, grand final, round-robin rounds). - **Check-in.** Each match card carries a Check in button for the two captains; a green ✓ next to the team tells everyone who is ready. It never blocks a match — it is the readiness signal, and the paper trail when a no-show becomes a forfeit. - **Self-reported results with confirmation.** For games that cannot report their own scores, both captains get "team won" buttons; the result stands the moment the two reports agree. Disagreeing reports decide nothing — the card says "reports disagree" and the organizer settles it through Decide. CS2 matches refuse self-reports; the game already told the truth. - **Tournament cloning.** Clone copies a tournament's settings — game, format, team size, series length, map pool — and its entered teams into a fresh draft with its own public address. Matches, results and seeds stay behind. ### Fixed - **Valheim's password now shows on the Joining card.** ich777's Valheim image spells its variables `SRV_PWD`/`SRV_NAME`/`WORLD_NAME` where others use `SERVER_PASS`/`SERVER_NAME`; the Joining card only knew the second spelling and reported "no password set" on a server that very much had one. The connect settings now know both spellings (only variables the container actually has are shown), and `PWD`-style variables are masked in Settings like every other credential. - **Match servers are servers now.** While a tournament match server lives it stands in Servers like any other card — match badge, its game's poster, player count, performance charts, live logs, console and Files through the existing tabs. What it never gets is a steering wheel: restart, stop, rename, delete and the standing-server tabs are refused, because its lifecycle belongs to the tournament, and the card disappears on its own at teardown (which the watcher knows is not an outage). Team management moved off the Tournaments list onto each tournament's own Teams tab, beside the bracket. - **Tournaments for any game.** The game picker now offers games where team-vs-team is real (Minecraft, V Rising, Palworld, Terraria) next to Counter-Strike 2, plus a free "Other game…" field for any contest. CS2 keeps its automatic flavour (a server per match, results from the game); every other game plays wherever you play and the organizer records results through Decide — bracket, seeding, scheduling, standings and the public page are identical. Maps are optional for non-CS2 tournaments, and the card wears the chosen game's poster. ## 1.2.1 — 2026-10-03 ### Security Findings from a full adversarial review (three independent passes: authorization/injection, secrets/exposure, memory/lifecycles). No SQL injection, no XSS and no authorization bypasses were found; these are the hardenings that came out of it: - **Match webhook payloads are clamped.** A compromised match server could send `map_number: 100000000` and allocate a hundred million objects in one synchronous loop — one request, portal gone. Map numbers are now bounded by the series length, scores to sane integers, map names truncated. - **Steam start commands can no longer reach a root shell.** The generated start script interpolated the operator's command into a root-context string, so a `$(...)` in it ran as container root before the privilege drop — reachable even via a hostile Steam appinfo prefill. The command now travels in a single-quoted variable; substitutions execute only as the unprivileged game user, as the product always promised. - **Match tokens stay out of logs.** The MatchZy config URL carried the per-match bearer token, and Fastify logs URLs; whoever reads logs could forge match results. The config is now fetched with the token in a header (never serialized), the header is on the redact list for good measure, and the organizer's override response no longer returns the token to a browser. - **A failed match-server boot no longer deadlocks tournaments.** A boot that gave up used to sit in 'provisioning' forever — leaking the container, its port and its Steam token, and blocking every other match behind the one-boot-at-a-time gate, across restarts. It is now torn down and returned to the queue. - Smaller hardenings: unexpected server errors answer a generic message instead of echoing internals; a dead rcon connection can no longer pass for a successful command; port-forward deletion is scoped to the server in the URL; the router TLS pin is enforced inside the handshake (before credentials flush) for UniFi and MikroTik; tournament updates enforce the same bounds as creation; deleted servers are evicted from every cache. ### Fixed - **The 7d window shows seven days now.** Metrics retention was 24 hours, so the dashboard's 7d button silently showed the same single day as 24h. Retention is a week, long series are downsampled server-side (charts and phones get ~400 points, with counter rates and player peaks preserved), the per-server Metrics tab gained its own 7d range, and the restart counters are counted over the whole log in SQL instead of the last 500 rows — which could quietly undercount on a busy window. - **CPU is of the machine now.** Metrics used docker-stats' one-core convention, so a server using just over one core showed "104% CPU" on a sixteen-core box. The percentage is now of the whole machine (0-100), with the per-core truth beside it as "1.04 of 16 cores" — the number that shows a single-threaded game being CPU-bound while the machine looks idle. History recorded before the change ages out within the 24-hour retention window. ## 1.2.0 — 2026-10-03 ### Added - **Tournaments (beta).** Single-elimination brackets played on CS2 servers the portal builds per match and retires after: teams with captains and Steam64 rosters, seeding, byes, per-round scheduling, in-game map veto via MatchZy, automatic results and bracket advancement, the organizer's override as the one correction path, Discord pings when a match server is ready and when results land, standings, and a shareable public page at /t/ in the portal's own look (team names always; player names only when the organizer says so). Each match server gets its own port, password and Steam game server token, minted and retired automatically. The whole feature wears a **beta** tag until real tournament nights have worn the edges off; the provisioning chain itself has been validated end to end on real hardware. - **Three more router vendors.** Settings → Router now also offers **Fritz!Box** (TR-064), **MikroTik** (RouterOS 7 REST, certificate pinned on first contact) and generic **UPnP** for most consumer routers, next to UniFi. Written to the vendors' published APIs and awaiting real-hardware confirmation — the connect test is a real call, so problems surface at connect time. - **Forwarding verified without a router.** The Network tab asks the game itself through the public address — an answer proves container port, forward and router in one go, with no router integration needed. When nothing answers, it says plainly which ports to forward manually (with the NAT-hairpin caveat spelled out). With a router connected the probe is skipped: the rules listing is the authoritative answer there. - **Start commands Steam never wrote down.** When an app's Steam info lists no Linux launch entry, the registry fills in the known start script for recognised games (7 Days to Die, Project Zomboid), and for everything else the command may stay empty: the server finds its own conventional start script (`startserver.sh` and friends) on first boot — and refuses loudly instead of guessing when none exists. ### Changed - **Licence: AGPL-3.0-or-later** from this release. Still genuinely open source and free to self-host; anyone offering GameKeepr as a service to others must publish their modifications. Versions up to 1.1.0 were MIT and remain MIT. ## 1.1.0 — 2026-10-02 ### Added - **Joining, up front.** Deploying a recognised game now asks for the join settings — password, world, server name — in their own section at the top of the deploy form, instead of leaving them buried among template variables. - **A Joining card on every server's overview**: address, port, server name, world and the password, visible to everyone who may see the server. A fresh server's password used to be visible to nobody at all. - **Automatic port forwards.** With a router connected, a deploy opens the game's ports by itself and says so in the deploy log. Administrative ports (RCON, web consoles) are never opened automatically — those keep the deliberate click on the Network tab. - **Rename from the card.** Operators rename a server where they see it; the container, id and URLs stay put. Servers from `config/servers.json` are declined with directions to the file. - **Delete from the card.** Owners can delete a portal-deployed server: the container is stopped and removed, the portal forgets it — and the game's data and backups deliberately stay on disk. - **A picture on every Steam server.** Steam artwork now falls back through the store's capsule and header images, and a Steam deploy records which app id its art belongs to (the game's, not the dedicated-server tool's). The lettered tile is now a bug, not a fallback. - **Settings → Steam game server tokens**: paste a Steam Web API key once and the portal mints and retires game server login tokens (GSLTs) itself — groundwork for tournaments. ## 1.0.0 — 2026-10-01 First public release, and the version submitted to Unraid Community Applications (listed in the catalog on 2026-10-02). - Restarts verified against the game itself, with per-game startup times, a cooldown, and an activity feed. - Three roles (owner, operator, member) with per-server exceptions; sessions, audit log, Discord notifications. - Install game servers from Unraid Community Applications templates (reviewed before anything runs) or from ~580 dedicated servers on Steam, composed on Valve's official steamcmd image. - Schedules that skip when players are online, world backups with safe restores, live logs with a console, typed per-game settings, file editor. - Mod installs with hash verification, archive-safety checks and optional VirusTotal/ClamAV scanning; Steam Workshop declarations. - Cloudflare Access guest list management, UniFi port forwarding, metrics, and a built-in wiki documenting all of it. - Runs on any Docker host; hardened variant through docker-socket-proxy. Release tags and diffs live on [GitHub releases](https://github.com/KenGoossens/Gamekeep/releases).