#!/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