clusterflux-public/scripts/deploy-release-candidate.sh
2026-07-20 11:08:26 +02:00

83 lines
4.6 KiB
Bash
Executable file

#!/usr/bin/env bash
set -euo pipefail
manifest=${1:?usage: deploy-release-candidate.sh MANIFEST}
: "${CLUSTERFLUX_DEPLOY_COMMAND:?set the documented exact-candidate deployment command}"
: "${CLUSTERFLUX_STRICT_SSH_TARGET:?set the production SSH target}"
service_unit=${CLUSTERFLUX_STRICT_SERVICE_UNIT:-clusterflux-hosted.service}
proxy_unit=${CLUSTERFLUX_STRICT_PROXY_UNIT:-nginx.service}
evidence_path=${CLUSTERFLUX_DEPLOYMENT_EVIDENCE_PATH:-target/acceptance/deployment.json}
staging=$(mktemp -d)
trap 'rm -rf "$staging"' EXIT
IFS=$'\t' read -r archive expected_archive_sha < <(
node - "$manifest" <<'NODE'
const fs = require("fs");
const path = require("path");
const manifestPath = path.resolve(process.argv[2]);
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
const asset = (manifest.assets || []).find((entry) => entry.name.startsWith("clusterflux-hosted-"));
if (!asset) throw new Error("candidate manifest has no hosted archive");
const file = path.isAbsolute(asset.file)
? asset.file
: path.resolve(path.dirname(manifestPath), asset.file);
process.stdout.write(`${file}\t${asset.sha256}\n`);
NODE
)
actual_archive_sha=$(sha256sum "$archive" | awk '{print $1}')
test "$actual_archive_sha" = "${expected_archive_sha#sha256:}"
tar -xzf "$archive" -C "$staging"
candidate_coordinator=$(find "$staging" -type f -name clusterflux-hosted-service -perm -u+x -print -quit)
test -n "$candidate_coordinator"
candidate_sha=$(sha256sum "$candidate_coordinator" | awk '{print $1}')
export CLUSTERFLUX_CANDIDATE_ARCHIVE="$archive"
export CLUSTERFLUX_CANDIDATE_ARCHIVE_SHA256="sha256:$actual_archive_sha"
export CLUSTERFLUX_CANDIDATE_COORDINATOR="$candidate_coordinator"
export CLUSTERFLUX_CANDIDATE_COORDINATOR_SHA256="sha256:$candidate_sha"
bash -euo pipefail -c "$CLUSTERFLUX_DEPLOY_COMMAND"
main_pid=$(ssh "$CLUSTERFLUX_STRICT_SSH_TARGET" systemctl show --property=MainPID --value "$service_unit")
test "$main_pid" != 0
remote_executable=$(ssh "$CLUSTERFLUX_STRICT_SSH_TARGET" readlink -f "/proc/$main_pid/exe")
remote_sha=$(ssh "$CLUSTERFLUX_STRICT_SSH_TARGET" sha256sum "$remote_executable" | awk '{print $1}')
test "$remote_sha" = "$candidate_sha"
service_fragment=$(ssh "$CLUSTERFLUX_STRICT_SSH_TARGET" systemctl show --property=FragmentPath --value "$service_unit")
service_fragment_sha=$(ssh "$CLUSTERFLUX_STRICT_SSH_TARGET" sha256sum "$service_fragment" | awk '{print $1}')
service_configuration_sha=$(ssh "$CLUSTERFLUX_STRICT_SSH_TARGET" systemctl cat "$service_unit" | sha256sum | awk '{print $1}')
proxy_fragment=$(ssh "$CLUSTERFLUX_STRICT_SSH_TARGET" systemctl show --property=FragmentPath --value "$proxy_unit")
proxy_fragment_sha=$(ssh "$CLUSTERFLUX_STRICT_SSH_TARGET" sha256sum "$proxy_fragment" | awk '{print $1}')
proxy_exec_start=$(ssh "$CLUSTERFLUX_STRICT_SSH_TARGET" systemctl show --property=ExecStart --value "$proxy_unit")
proxy_executable=$(sed -n 's/.*path=\([^ ;]*\).*/\1/p' <<<"$proxy_exec_start")
proxy_configuration=$(sed -n 's/.* argv\[\]=.* -c \([^ ;]*\).*/\1/p' <<<"$proxy_exec_start")
test -n "$proxy_executable"
test -n "$proxy_configuration"
proxy_configuration_sha=$(ssh "$CLUSTERFLUX_STRICT_SSH_TARGET" "$proxy_executable" -T -c "$proxy_configuration" 2>/dev/null | sha256sum | awk '{print $1}')
mkdir -p "$(dirname "$evidence_path")"
EVIDENCE_PATH="$evidence_path" SERVICE_UNIT="$service_unit" \
SERVICE_FRAGMENT="$service_fragment" SERVICE_FRAGMENT_SHA="$service_fragment_sha" \
SERVICE_CONFIGURATION_SHA="$service_configuration_sha" \
REMOTE_EXECUTABLE="$remote_executable" REMOTE_SHA="$remote_sha" \
PROXY_UNIT="$proxy_unit" PROXY_FRAGMENT="$proxy_fragment" \
PROXY_FRAGMENT_SHA="$proxy_fragment_sha" PROXY_EXECUTABLE="$proxy_executable" \
PROXY_CONFIGURATION="$proxy_configuration" PROXY_CONFIGURATION_SHA="$proxy_configuration_sha" node <<'NODE'
const fs = require("fs");
fs.writeFileSync(process.env.EVIDENCE_PATH, JSON.stringify({
kind: "clusterflux-exact-candidate-deployment",
service_unit: process.env.SERVICE_UNIT,
service_unit_fragment: process.env.SERVICE_FRAGMENT,
service_unit_sha256: `sha256:${process.env.SERVICE_FRAGMENT_SHA}`,
service_configuration_sha256: `sha256:${process.env.SERVICE_CONFIGURATION_SHA}`,
hosted_service_executable: process.env.REMOTE_EXECUTABLE,
hosted_service_sha256: `sha256:${process.env.REMOTE_SHA}`,
proxy_unit: process.env.PROXY_UNIT,
proxy_unit_fragment: process.env.PROXY_FRAGMENT,
proxy_unit_sha256: `sha256:${process.env.PROXY_FRAGMENT_SHA}`,
proxy_executable: process.env.PROXY_EXECUTABLE,
proxy_configuration: process.env.PROXY_CONFIGURATION,
proxy_configuration_sha256: `sha256:${process.env.PROXY_CONFIGURATION_SHA}`,
}, null, 2) + "\n");
NODE