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>
106 lines
3.9 KiB
Bash
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!"
|