Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions apps/daemon/src/server/installer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
26 changes: 15 additions & 11 deletions docs/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
52 changes: 31 additions & 21 deletions packages/templates/src/catalog/source-install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
'',
Expand All @@ -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',
Expand Down
Loading