server-monitor-manager/tests/release-verification/run-positive-installation.sh
Ochenstarik c1e47684e6 fix(verification): verify releases the way an operator does
PR #40 correctly publishes the keyless signing certificate and makes
verify_archive require manifest, signature and certificate together. Three
things around it were inconsistent.

The release-verification scripts fetched assets with `gh release download`.
The workspace-isolation step deliberately removes `.git` so that the install
comes from the release rather than the source tree, and `gh` resolves the
repository from that git context — the tool fought the isolation it runs
inside, which is what broke the first runs. An operator has neither `gh` nor a
token, so the scripts now use public `curl`, `sha256sum` and `cosign` only.
`verify-assets.sh` keeps `gh`: it runs before isolation and only lists assets.

The monitor check addressed a user and a home directory that do not exist.
The bootstrap creates `ochenstarik-monitor` with `/var/lib/ochenstarik-monitor`;
the script used `ochenstarik-smm-monitor` under the Control state directory, so
that section could never have passed. It now also asserts that the forced
command is pinned in `authorized_keys` before running it, and quotes the
command instead of splitting it on whitespace.

The expected asset list did not include the new certificate, so a correct
release would have been reported as unexpected.

Also: a negative case for an archive published without its certificate, removal
of drafting comments that quoted the task text, and documentation of the three
signature files, since `verify-release` now requires them beside the archive and
the documented download list stopped being sufficient.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 13:16:27 +07:00

106 lines
3.9 KiB
Bash

#!/bin/bash
# Installs a published release exactly the way an operator does it: public curl
# downloads, checksum verification, signature verification, then the documented
# bootstrap commands. No gh CLI and no token, because the operator has neither —
# and because gh resolves the repository from git context, which the isolated
# workspace deliberately removes.
set -euo pipefail
TAG="${1:-}"
REPOSITORY="${SMM_REPOSITORY:-ochenstarik-ui/server-monitor-manager}"
if [[ -z "$TAG" ]]; then
echo "Usage: $0 <tag>" >&2
exit 1
fi
BASE_URL="https://github.com/${REPOSITORY}/releases/download/${TAG}"
CONTROL_PORT=17443
MONITOR_USER="ochenstarik-monitor"
MONITOR_HOME="/var/lib/ochenstarik-monitor"
METRICS_SCRIPT="/usr/local/libexec/ochenstarik-smm-metrics"
echo "Running positive installation test for $TAG..."
download() {
local name="$1"
curl --fail --silent --show-error --location --retry 3 \
-o "$name" "${BASE_URL}/${name}" \
|| { echo "FAIL: asset is not downloadable: $name" >&2; exit 1; }
}
case "$(uname -m)" in
x86_64) RUNTIME="linux-x64" ;;
aarch64|arm64) RUNTIME="linux-arm64" ;;
*) echo "FAIL: unsupported architecture $(uname -m)" >&2; exit 1 ;;
esac
ARCHIVE="server-monitor-manager-${RUNTIME}.tar.gz"
download smm-setup.sh
download smm-setup.sh.sha256
sha256sum -c smm-setup.sh.sha256
download "$ARCHIVE"
download "$ARCHIVE.sha256"
sha256sum -c "$ARCHIVE.sha256"
# Signature material must sit beside the archive: verify_archive looks for it there.
download server-monitor-manager-manifest.json
download server-monitor-manager-manifest.sig
download server-monitor-manager-manifest.pem
sudo bash smm-setup.sh --tag "$TAG" preflight
sudo bash smm-setup.sh --tag "$TAG" verify-manifest \
server-monitor-manager-manifest.json \
server-monitor-manager-manifest.sig \
server-monitor-manager-manifest.pem
sudo bash smm-setup.sh --tag "$TAG" verify-release "$ARCHIVE"
sudo bash smm-setup.sh --tag "$TAG" install-control "$ARCHIVE" 127.0.0.1 "$CONTROL_PORT"
sudo bash smm-setup.sh --tag "$TAG" mesh-init 127.0.0.1 51820
echo "Checking Control healthz..."
for _ in {1..30}; do
if sudo curl --fail --silent \
--cacert /etc/ochenstarik-server-monitor-manager/control-ca.crt \
"https://127.0.0.1:${CONTROL_PORT}/healthz" >/dev/null; then
break
fi
sleep 1
done
sudo curl --fail --silent --show-error \
--cacert /etc/ochenstarik-server-monitor-manager/control-ca.crt \
"https://127.0.0.1:${CONTROL_PORT}/healthz"
echo "Enrolling a node..."
NODE_CODE="$(sudo bash smm-setup.sh --tag "$TAG" node-code test-node)"
SMM_ENROLL_CODE="$NODE_CODE" SMM_ACCEPT_CA_FINGERPRINT=1 \
sudo --preserve-env=SMM_ENROLL_CODE,SMM_ACCEPT_CA_FINGERPRINT \
bash smm-setup.sh --tag "$TAG" install-node "$ARCHIVE"
sudo systemctl is-active --quiet ochenstarik-smm-agent.service
sudo systemctl is-active --quiet ochenstarik-smm-control.service
echo "Installing monitor role..."
ssh-keygen -t ed25519 -N "" -f /tmp/monitor_key -q
sudo bash smm-setup.sh --tag "$TAG" install-monitor "$(cat /tmp/monitor_key.pub)"
echo "Verifying monitor snapshot against the contract..."
sudo grep -Fq "command=\"${METRICS_SCRIPT}\"" "${MONITOR_HOME}/.ssh/authorized_keys" \
|| { echo "FAIL: forced command is not pinned in authorized_keys" >&2; exit 1; }
SNAPSHOT="$(sudo -u "$MONITOR_USER" "$METRICS_SCRIPT")"
EXPECTED_KEYS="$(cut -d'=' -f1 tests/contracts/monitor-snapshot-v1.txt | sort)"
ACTUAL_KEYS="$(cut -d'=' -f1 <<<"$SNAPSHOT" | sort)"
if [[ "$EXPECTED_KEYS" != "$ACTUAL_KEYS" ]]; then
echo "FAIL: monitor snapshot keys do not match the contract" >&2
diff <(echo "$EXPECTED_KEYS") <(echo "$ACTUAL_KEYS") >&2 || true
exit 1
fi
echo "PASS: monitor snapshot matches the contract"
sudo bash smm-setup.sh --tag "$TAG" uninstall-monitor
sudo bash smm-setup.sh --tag "$TAG" uninstall-agent --purge
sudo bash smm-setup.sh --tag "$TAG" uninstall-control --confirm-destroy-control
echo "Positive installation test passed!"