diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 63c0318..3eaf6d0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -324,3 +324,4 @@ jobs: dist/agentlinux-*.tar.gz.sha256 dist/catalog-*.json dist/agentlinux_*.deb + dist/VERSION diff --git a/packaging/curl-installer/install.sh b/packaging/curl-installer/install.sh index 6156781..4ba8793 100755 --- a/packaging/curl-installer/install.sh +++ b/packaging/curl-installer/install.sh @@ -165,8 +165,15 @@ main() { # HTTP 404 HTML body would land in the tarball path and sha256sum -c would # emit a confusing "FAILED" verdict. Asserting the gzip magic bytes BEFORE # sha256 gives a precise error. - if ! file -- "${tmpdir}/${tarball}" | grep -q 'gzip compressed'; then - die "downloaded ${tarball} is not a gzip archive — possible 404-as-HTML or proxy-rewrite; refusing to proceed" + # + # Read the first two bytes via `head` + `od` rather than `file(1)`: the + # `file` package is NOT preinstalled on minimal Ubuntu/Debian cloud images + # (and many Docker base images). `head` and `od` are coreutils, always + # present. Magic for gzip is 1f 8b (RFC 1952). + local _magic + _magic=$(head -c 2 "${tmpdir}/${tarball}" 2>/dev/null | od -An -tx1 | tr -d ' \n') + if [[ "$_magic" != "1f8b" ]]; then + die "downloaded ${tarball} is not a gzip archive (magic bytes: ${_magic:-empty}) — possible 404-as-HTML or proxy-rewrite; refusing to proceed" fi # SHA256 verification BEFORE extraction (T-06-02 — hard security gate). diff --git a/plugin/provisioner/20-sudoers.sh b/plugin/provisioner/20-sudoers.sh index bff6129..fe58171 100644 --- a/plugin/provisioner/20-sudoers.sh +++ b/plugin/provisioner/20-sudoers.sh @@ -37,6 +37,16 @@ log_info "20-sudoers: starting" +# Minimal Ubuntu/Debian cloud images (and many Docker base images) ship without +# the `sudo` package, which provides both the `sudo` binary AND `visudo`. We +# need `visudo` to validate the drop-in before installing it (T-05.1-01), and +# the agent user obviously needs `sudo` afterwards. Mirror the pattern used by +# 10-agent-user.sh's `locales` install. +if ! command -v visudo >/dev/null 2>&1; then + log_warn "visudo not found; installing 'sudo' package" + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends sudo +fi + readonly SUDOERS_FILE="/etc/sudoers.d/agentlinux" # Single-quoted heredoc — no shell expansion, byte-stable across re-runs. The # meaningful policy is the single line `agent ALL=(ALL) NOPASSWD: ALL`; the diff --git a/scripts/build-release.sh b/scripts/build-release.sh index e9a0a0c..69137ef 100755 --- a/scripts/build-release.sh +++ b/scripts/build-release.sh @@ -315,6 +315,19 @@ if [[ "$SRC_SHA" != "$SNAPSHOT_SHA" ]]; then exit 1 fi +# --------------------------------------------------------------------------- +# 10b. VERSION sentinel asset. +# packaging/curl-installer/install.sh resolves an unpinned tag by +# following https://github.com/.../releases/latest/download/VERSION and +# capturing the redirect URL with curl -fsSIL. The asset itself doesn't +# need to be machine-parsed — but it MUST exist so curl -f doesn't fail +# on the redirect target. Without this file shipped on every release, +# `curl -fsSL https://agentlinux.org/install.sh | bash` fails with +# "could not resolve latest version" against any release that lacks the +# sentinel (dogfood-discovered against v0.3.2-rc1). +# --------------------------------------------------------------------------- +printf '%s\n' "$TAG" >dist/VERSION + # --------------------------------------------------------------------------- # 11. Optional .deb via fpm (ADR-006 — optional v0.3.0 path). # Skip gracefully if fpm is absent or SKIP_DEB=1 or --no-deb — the @@ -345,4 +358,4 @@ fi # --------------------------------------------------------------------------- # 12. Final summary (stdout-only; no emojis per CLAUDE.md). # --------------------------------------------------------------------------- -printf 'Built: %s + .sha256 + catalog-%s.json%s\n' "$TARBALL" "$TAG" "$DEB_SUFFIX" +printf 'Built: %s + .sha256 + catalog-%s.json + VERSION%s\n' "$TARBALL" "$TAG" "$DEB_SUFFIX"