From bf421f78d496b234e2d619a548e764c5cbd5fed1 Mon Sep 17 00:00:00 2001 From: aaldersondev Date: Tue, 25 Aug 2026 15:33:25 +0200 Subject: [PATCH] fix(templates): the terminal was not the whole of that refusal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v0.19.2 said `Missing configuration` meant the install container had a terminal, and told operators to update their node. An installation on a node already carrying that fix — daemon 0.19.2, `TERM` unset in the container, verified — refused its first call with exactly that message and downloaded all 6.87 GB on the second. So the message sent people to check a version that was already right, and that is the worst kind of diagnosis: precise, confident, and pointing away from the fix. It now names both causes and says which one a retry can get past. The tty finding stands as measured — seven failures out of seven with one, three successes out of three without, and a warm-up `+quit` beforehand does not help — and `Tty: false` stays. What was wrong was treating a cause found first as the only one. Nothing else changes: the retry that carried this installation through was already there, and this is what it was for. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01ReX2JSG6VKYShanE1F69KP --- apps/daemon/src/server/installer.ts | 5 ++ docs/templates.md | 26 ++++++---- .../templates/src/catalog/source-install.ts | 52 +++++++++++-------- 3 files changed, 51 insertions(+), 32 deletions(-) diff --git a/apps/daemon/src/server/installer.ts b/apps/daemon/src/server/installer.ts index 6a9aaff..954dbaa 100644 --- a/apps/daemon/src/server/installer.ts +++ b/apps/daemon/src/server/installer.ts @@ -301,6 +301,11 @@ export function installCreateOptions(options: { * {@link DockerFrameReader} is the eight bytes of header this adds — and * that programs which check for a terminal print their plain output. On an * install log, that second one is a gain: `apt-get` stops redrawing. + * + * It is not the whole of that failure, and the Source templates say so: + * with no tty at all, a cold SteamCMD still refuses its first call now and + * then, and the next one works. This line takes down a wall; the retry in + * the install script handles what is left standing. */ Tty: false, AttachStdout: true, diff --git a/docs/templates.md b/docs/templates.md index 9ed87c4..4ddbc3c 100644 --- a/docs/templates.md +++ b/docs/templates.md @@ -694,17 +694,21 @@ So the daemon creates install containers with `Tty: false`, from Hopper 0.19.2. problem: 104 of the 274 published eggs read for this catalogue install from SteamCMD, and a tty was a wall in front of every one of them, with a message that names neither the panel nor the terminal. -An install script does not have to do anything about this. What it should do is **retry** — a -download of several gigabytes dies in the middle sometimes, and Steam keeps what it has already -staged under `steamapps/downloading`, so a second attempt resumes. The shipped Source install makes -three, then names which of three things happened, because none of their fixes is guessable from what -SteamCMD prints: - -| What SteamCMD says | What it means | -| ----------------------- | -------------------------------------------------------------------------------------------------------------------- | -| `Missing configuration` | The install container had a terminal — this node's daemon is older than the panel that sent the script. Update it. | -| `state is 0x202` | Out of room. The depot stages into `steamapps/downloading`, so the install wants about twice the finished size free. | -| `No subscription` | The app id is not anonymously installable — a game client's id, most often. | +**The terminal is not the whole of it.** With no terminal at all, on a node already carrying that +fix, a cold SteamCMD still refused its first call — and the next attempt downloaded all 6.87 GB. +Twice in the runs watched, and a warm-up `+quit` beforehand does not prevent it. + +So an install script that fetches from Steam should **retry**, for that refusal and for a download of +several gigabytes dying in the middle. Steam keeps what it has already staged under +`steamapps/downloading`, so a second attempt resumes rather than starting over. The shipped Source +install makes three, then names what happened, because none of these is guessable from what SteamCMD +prints: + +| What SteamCMD says | What it means | +| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Missing configuration` | Either the install container had a terminal — a node older than 0.19.2 — or a cold SteamCMD refused its first call. No retry gets past the first; the second one usually clears on the next attempt. | +| `state is 0x202` | Out of room. The depot stages into `steamapps/downloading`, so the install wants about twice the finished size free. | +| `No subscription` | The app id is not anonymously installable — a game client's id, most often. | The second was measured by giving the volume 1.9 GB for a 6.87 GB depot. diff --git a/packages/templates/src/catalog/source-install.ts b/packages/templates/src/catalog/source-install.ts index e28ea0b..675a12a 100644 --- a/packages/templates/src/catalog/source-install.ts +++ b/packages/templates/src/catalog/source-install.ts @@ -126,23 +126,29 @@ export function steamInstallScript(game: SteamInstall): string { '# does not match. That is what makes a reinstall a repair rather than a', '# gamble, and it is the whole update mechanism for this server.', '#', - '# Retried, and the retry is not what fixed the failure people arrive here', - '# with.', + '# Retried, because SteamCMD refuses a call it will accept a minute later.', '#', - `# A tty on the install container is what stopped app ${appId} installing:`, - `# "ERROR! Failed to install app '${appId}' (Missing configuration)", exit 8,`, - '# before a byte of depot. Measured eight times, interleaved so that neither', - '# time nor a Steam outage explains it — seven failures out of seven with a', - '# tty, three successes out of three without. Nothing a script can do gets', - '# out from under it, so the fix is where the container is created and not', - "# here: `Tty: false` in the daemon's installer, from Hopper 0.19.2.", + `# Two things produce "Failed to install app '${appId}' (Missing`, + '# configuration)", exit 8, before a byte of depot, and they were found in', + '# that order.', '#', - '# What this loop is for is the ordinary thing instead: a download of', - '# several gigabytes that dies in the middle. Three attempts and not thirty,', - '# because a real refusal — a wrong app id, a full volume — comes back the', - '# same every time and is better reported in a minute than in an hour. A', - '# retry costs little, since Steam keeps what it has already staged under', - '# steamapps/downloading.', + '# A terminal on the install container does it every time: seven failures', + '# out of seven with one, three successes out of three without, interleaved', + '# so that neither time nor a Steam outage explains it, and a warm-up run', + '# beforehand does not help. Nothing a script can do gets out from under a', + '# tty, so that half is fixed where the container is created — `Tty: false`,', + '# from Hopper 0.19.2.', + '#', + '# And with no terminal at all, a cold SteamCMD still refused its first call', + '# — twice in the runs watched, one of them on a node already carrying that', + '# fix, and the next attempt downloaded all 6.87 GB. That is what these three', + '# attempts are for, and why the diagnosis below names both causes instead of', + '# sending everybody to check their node version.', + '#', + '# Three and not thirty: a real refusal — a wrong app id, a full volume —', + '# comes back the same every time and is better reported in a minute than in', + '# an hour. A retry costs little, since Steam keeps what it has already', + '# staged under steamapps/downloading.', 'STEAMCMD_LOG=/tmp/steamcmd.log', 'STEAMCMD_ATTEMPTS=3', '', @@ -160,12 +166,16 @@ export function steamInstallScript(game: SteamInstall): string { ' echo "" >&2', '', ' if grep -q "Missing configuration" "${STEAMCMD_LOG}"; then', - ' echo "This is what a terminal on the install container does to" >&2', - ' echo "SteamCMD: it logs in, then refuses the app configuration" >&2', - ' echo "without downloading anything. The panel stopped giving that" >&2', - ' echo "container a terminal in Hopper 0.19.2, so this node is running" >&2', - ' echo "a daemon older than the panel that sent this script. Update" >&2', - ' echo "hopperd on it, then reinstall." >&2', + ' echo "SteamCMD logged in and then refused the app configuration," >&2', + ' echo "three times, without downloading anything. Two things do that:" >&2', + ' echo "" >&2', + ' echo " - A terminal on the install container. The panel stopped" >&2', + ' echo " giving it one in Hopper 0.19.2, and against an older node" >&2', + ' echo " no number of attempts gets past it. Check that first." >&2', + ' echo "" >&2', + ' echo " - A cold SteamCMD refusing its first call. That one the next" >&2', + ' echo " attempt usually gets through, which is what these three" >&2', + ' echo " were for. Reinstall in a few minutes." >&2', ' elif grep -q "state is 0x202" "${STEAMCMD_LOG}"; then', ' echo "SteamCMD stopped with state 0x202. Measured on this depot with" >&2', ' echo "1.9 GB free, that is what running out of room looks like: the" >&2',