left4me: install steamcmd + drop importability gate on pip_install

Two changes from the same debug session, both prerequisites for
`l4d2ctl install` to work end-to-end on a fresh node:

1) Install steamcmd via tarball under /opt/left4me/steam.
   - dpkg --add-architecture i386 + libc6:i386 + lib32z1 (32-bit deps;
     bw pkg_apt translates _ to : at install time, hence libc6_i386)
   - curl|tar one-shot, guarded by `test -x steamcmd.sh`
   - LEFT4ME_STEAMCMD in host.env so l4d2host invokes by absolute path
     (mirrors the old bundles/left4dead2/files/setup approach; avoids
     the dirname-$0 trap that bites when steamcmd is reached via a
     PATH symlink)

2) Drop the `unless` on left4me_pip_install. The gate checked
   importability of l4d2host/l4d2web, which is too weak a proxy for
   install state: adding [project.scripts] to pyproject.toml later
   wouldn't be picked up if the package was already importable from a
   prior `pip install -e`. Cost is ~2s/apply for a no-op pip
   resolution — not enough to keep the gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
CroneKorkN 2026-05-10 22:46:45 +02:00
parent 524ad6e89b
commit 1d30830824
Signed by: cronekorkn
SSH key fingerprint: SHA256:v0410ZKfuO1QHdgKBsdQNF64xmTxOF8osF1LIqwTcVw
3 changed files with 51 additions and 3 deletions

View file

@ -1,3 +1,6 @@
# Managed by ckn-bw bundles/left4me. Local edits will be reverted. # Managed by ckn-bw bundles/left4me. Local edits will be reverted.
# Deployment units use fixed /var/lib/left4me paths; regenerate units if this changes. # Deployment units use fixed /var/lib/left4me paths; regenerate units if this changes.
LEFT4ME_ROOT=/var/lib/left4me LEFT4ME_ROOT=/var/lib/left4me
# l4d2host invokes steamcmd by absolute path — bypasses PATH lookup so the
# script's `cd "$(dirname "$0")"` resolves next to the real install dir.
LEFT4ME_STEAMCMD=/opt/left4me/steam/steamcmd.sh

View file

@ -29,6 +29,7 @@ directories = {
'/var/lib/left4me/runtime': {'owner': 'left4me', 'group': 'left4me'}, '/var/lib/left4me/runtime': {'owner': 'left4me', 'group': 'left4me'},
'/var/lib/left4me/workshop_cache': {'owner': 'left4me', 'group': 'left4me'}, '/var/lib/left4me/workshop_cache': {'owner': 'left4me', 'group': 'left4me'},
'/var/lib/left4me/tmp': {'owner': 'left4me', 'group': 'left4me'}, '/var/lib/left4me/tmp': {'owner': 'left4me', 'group': 'left4me'},
'/opt/left4me/steam': {'owner': 'left4me', 'group': 'left4me'},
'/usr/local/libexec/left4me': { '/usr/local/libexec/left4me': {
'owner': 'root', 'owner': 'root',
'group': 'root', 'group': 'root',
@ -129,8 +130,42 @@ actions = {
'command': 'sysctl --system >/dev/null', 'command': 'sysctl --system >/dev/null',
'triggered': True, 'triggered': True,
}, },
'left4me_dpkg_add_i386_arch': {
# steamcmd is 32-bit and pulls libc6:i386 + lib32z1 from the i386 arch.
# apt-get update is part of this action because newly-added foreign
# archs need a fresh package list before any :i386 package resolves.
'command': 'dpkg --add-architecture i386 && apt-get update',
'unless': 'dpkg --print-foreign-architectures | grep -qx i386',
'cascade_skip': False,
},
'left4me_install_steamcmd': {
# Steam's tarball is rolling with no published checksum, so we can't
# use download: (which requires a hash). Guard with a presence check
# on steamcmd.sh — steamcmd self-updates at runtime, so chasing the
# tarball version from bw isn't useful.
'command': (
'sudo -u left4me sh -c "'
'cd /opt/left4me/steam && '
'curl -fsSL https://media.steampowered.com/installer/steamcmd_linux.tar.gz | '
'tar -xz'
'"'
),
'unless': 'test -x /opt/left4me/steam/steamcmd.sh',
'cascade_skip': False,
'needs': [
'directory:/opt/left4me/steam',
'pkg_apt:curl',
'pkg_apt:libc6_i386', # bw pkg_apt convention: _ → :
'pkg_apt:lib32z1',
'user:left4me',
],
},
} }
# steamcmd is invoked by absolute path (LEFT4ME_STEAMCMD in host.env),
# not via PATH lookup — see l4d2host/cli.py:install. We don't need to put
# anything in /usr/local/bin for it.
git_deploy = { git_deploy = {
'/opt/left4me/src': { '/opt/left4me/src': {
'repo': node.metadata.get('left4me/git_url'), 'repo': node.metadata.get('left4me/git_url'),
@ -197,10 +232,11 @@ actions['left4me_pip_upgrade'] = {
actions['left4me_pip_install'] = { actions['left4me_pip_install'] = {
# Single pip invocation installs both editable packages from the same # Single pip invocation installs both editable packages from the same
# checkout. Runs on every apply and self-heals after partial failures; # checkout. Runs on every apply: pip install -e is fast on no-op, and
# `unless` short-circuits when both packages are already importable. # any gate weaker than "egg-info matches pyproject.toml" can mask
# script regeneration — e.g. adding [project.scripts] later wouldn't
# be picked up if `unless` only checks importability.
'command': 'sudo -u left4me /opt/left4me/.venv/bin/pip install -e /opt/left4me/src/l4d2host -e /opt/left4me/src/l4d2web', 'command': 'sudo -u left4me /opt/left4me/.venv/bin/pip install -e /opt/left4me/src/l4d2host -e /opt/left4me/src/l4d2web',
'unless': 'sudo -u left4me /opt/left4me/.venv/bin/python -c "import l4d2host, l4d2web"',
'cascade_skip': False, 'cascade_skip': False,
'needs': [ 'needs': [
'git_deploy:/opt/left4me/src', 'git_deploy:/opt/left4me/src',

View file

@ -25,6 +25,15 @@ defaults = {
'python3-venv': {}, 'python3-venv': {},
'python3-pip': {}, 'python3-pip': {},
'python3-dev': {}, 'python3-dev': {},
# steamcmd is a 32-bit ELF; needs i386 multiarch + these libs.
# `_` → `:` is bundlewrap's pkg_apt convention for multiarch
# names (see pkg_apt.py:48).
'libc6_i386': { # installs libc6:i386
'needs': ['action:left4me_dpkg_add_i386_arch'],
},
'lib32z1': {
'needs': ['action:left4me_dpkg_add_i386_arch'],
},
}, },
}, },
'nftables': { 'nftables': {