diff --git a/bundles/left4me/files/etc/left4me/host.env.mako b/bundles/left4me/files/etc/left4me/host.env.mako index 693dd5c..a5bbc03 100644 --- a/bundles/left4me/files/etc/left4me/host.env.mako +++ b/bundles/left4me/files/etc/left4me/host.env.mako @@ -1,3 +1,6 @@ # Managed by ckn-bw bundles/left4me. Local edits will be reverted. # Deployment units use fixed /var/lib/left4me paths; regenerate units if this changes. 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 diff --git a/bundles/left4me/items.py b/bundles/left4me/items.py index 37cdd2e..767dfec 100644 --- a/bundles/left4me/items.py +++ b/bundles/left4me/items.py @@ -29,6 +29,7 @@ directories = { '/var/lib/left4me/runtime': {'owner': 'left4me', 'group': 'left4me'}, '/var/lib/left4me/workshop_cache': {'owner': 'left4me', 'group': 'left4me'}, '/var/lib/left4me/tmp': {'owner': 'left4me', 'group': 'left4me'}, + '/opt/left4me/steam': {'owner': 'left4me', 'group': 'left4me'}, '/usr/local/libexec/left4me': { 'owner': 'root', 'group': 'root', @@ -129,8 +130,42 @@ actions = { 'command': 'sysctl --system >/dev/null', '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 = { '/opt/left4me/src': { 'repo': node.metadata.get('left4me/git_url'), @@ -197,10 +232,11 @@ actions['left4me_pip_upgrade'] = { actions['left4me_pip_install'] = { # Single pip invocation installs both editable packages from the same - # checkout. Runs on every apply and self-heals after partial failures; - # `unless` short-circuits when both packages are already importable. + # checkout. Runs on every apply: pip install -e is fast on no-op, and + # 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', - 'unless': 'sudo -u left4me /opt/left4me/.venv/bin/python -c "import l4d2host, l4d2web"', 'cascade_skip': False, 'needs': [ 'git_deploy:/opt/left4me/src', diff --git a/bundles/left4me/metadata.py b/bundles/left4me/metadata.py index 67a5721..c55ba28 100644 --- a/bundles/left4me/metadata.py +++ b/bundles/left4me/metadata.py @@ -25,6 +25,15 @@ defaults = { 'python3-venv': {}, 'python3-pip': {}, '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': {