Pulse/deploy/provider-msp/upgrade.sh
rcourtman c7e50d5602 Harden provider-hosted MSP isolation
Broker provider control-plane Docker access through a socket proxy, remove broad host mounts, align audit and rate-limit proxy trust, harden tenant runtime containers, restrict workspace report logo paths, and update provider deploy guardrails.
2026-06-02 21:10:13 +01:00

228 lines
7.1 KiB
Bash
Executable file

#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
usage() {
cat <<'USAGE'
Usage:
./upgrade.sh [options]
Runs the provider-hosted MSP pre-upgrade and upgrade flow:
1. validates .env and docker-compose.yml
2. checks provider status and install preflight
3. creates and verifies a fresh provider MSP backup
4. dry-runs restore into a separate target data directory
5. pulls and starts the provider Traefik, Docker socket proxy, and control-plane services
6. prints the tenant runtime rollout plan for CP_PULSE_IMAGE
7. optionally rolls all tenant runtimes onto CP_PULSE_IMAGE
Options:
--dry-run Print the non-mutating upgrade plan only
--rollout-tenants Execute tenant-runtime rollout --all after provider services are updated
--prune-previous Remove preserved pre-rollout tenant containers after successful tenant rollout
--skip-compose-pull Do not run docker compose pull for provider services
--skip-runtime-image-pull Pass --skip-image-pull to provider-msp preflight
--backup-output PATH Write the fresh backup archive to PATH
--restore-target DIR Restore dry-run target data dir (default: <data-dir>/upgrade-restore-drill)
--run-id ID Operator-visible tenant reconcile run id
--health-timeout DURATION Tenant rollout health timeout (default: 90s)
-h, --help Show this help
Environment equivalents:
PROVIDER_MSP_UPGRADE_DRY_RUN=1
PROVIDER_MSP_UPGRADE_ROLLOUT_TENANTS=1
PROVIDER_MSP_UPGRADE_PRUNE_PREVIOUS=1
PROVIDER_MSP_SKIP_COMPOSE_PULL=1
PROVIDER_MSP_SKIP_RUNTIME_IMAGE_PULL=1
PROVIDER_MSP_UPGRADE_BACKUP_OUTPUT=/data/backups/provider-msp/pre-upgrade.tar.gz
PROVIDER_MSP_UPGRADE_RESTORE_TARGET=/data/upgrade-restore-drill
PROVIDER_MSP_UPGRADE_RUN_ID=provider-msp-upgrade-20260602T120000Z
PROVIDER_MSP_UPGRADE_HEALTH_TIMEOUT=90s
USAGE
}
die() {
echo "error: $*" >&2
exit 1
}
truthy() {
case "$(echo "${1:-}" | tr '[:upper:]' '[:lower:]')" in
true|1|yes|on) return 0 ;;
*) return 1 ;;
esac
}
env_value() {
local key="$1"
local env_path="${2:-.env}"
local value
if [[ ! -f "${env_path}" ]]; then
return 0
fi
value="$(grep -E "^${key}=" "${env_path}" | tail -n 1 | cut -d= -f2- || true)"
value="${value%\"}"; value="${value#\"}"
value="${value%\'}"; value="${value#\'}"
echo "${value}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
}
extract_field() {
local key="$1"
awk -F= -v key="${key}" '$1 == key { print substr($0, length($1) + 2); exit }'
}
run_control() {
docker compose run --rm --no-deps control-plane "$@"
}
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "${script_dir}"
dry_run="${PROVIDER_MSP_UPGRADE_DRY_RUN:-0}"
rollout_tenants="${PROVIDER_MSP_UPGRADE_ROLLOUT_TENANTS:-0}"
prune_previous="${PROVIDER_MSP_UPGRADE_PRUNE_PREVIOUS:-0}"
skip_compose_pull="${PROVIDER_MSP_SKIP_COMPOSE_PULL:-0}"
skip_runtime_image_pull="${PROVIDER_MSP_SKIP_RUNTIME_IMAGE_PULL:-0}"
backup_output="${PROVIDER_MSP_UPGRADE_BACKUP_OUTPUT:-}"
restore_target="${PROVIDER_MSP_UPGRADE_RESTORE_TARGET:-}"
run_id="${PROVIDER_MSP_UPGRADE_RUN_ID:-provider-msp-upgrade-$(date -u +'%Y%m%dT%H%M%SZ')}"
health_timeout="${PROVIDER_MSP_UPGRADE_HEALTH_TIMEOUT:-90s}"
while (($# > 0)); do
case "$1" in
--dry-run)
dry_run=1
shift
;;
--rollout-tenants)
rollout_tenants=1
shift
;;
--prune-previous)
prune_previous=1
shift
;;
--skip-compose-pull)
skip_compose_pull=1
shift
;;
--skip-runtime-image-pull)
skip_runtime_image_pull=1
shift
;;
--backup-output)
backup_output="${2:-}"
shift 2
;;
--restore-target)
restore_target="${2:-}"
shift 2
;;
--run-id)
run_id="${2:-}"
shift 2
;;
--health-timeout)
health_timeout="${2:-}"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "unknown option: $1" >&2
usage >&2
exit 2
;;
esac
done
if [[ ! -f .env ]]; then
die "deploy/provider-msp/.env is required; copy .env.example to .env and fill in the provider values"
fi
provider_data_dir="$(env_value PULSE_PROVIDER_MSP_DATA_DIR .env)"
provider_data_dir="${provider_data_dir:-/data}"
tenant_runtime_image="$(env_value CP_PULSE_IMAGE .env)"
if [[ -z "${tenant_runtime_image}" ]]; then
die "CP_PULSE_IMAGE is required"
fi
if [[ -z "${restore_target}" ]]; then
restore_target="${provider_data_dir%/}/upgrade-restore-drill"
fi
if [[ "${restore_target%/}" == "${provider_data_dir%/}" ]]; then
die "--restore-target must not point at the live provider data dir (${provider_data_dir})"
fi
docker compose version >/dev/null
docker version >/dev/null
docker compose config --quiet
preflight_args=(provider-msp preflight)
if truthy "${skip_runtime_image_pull}" || truthy "${dry_run}"; then
preflight_args+=(--skip-image-pull)
fi
echo "provider_msp_upgrade_dry_run=$(truthy "${dry_run}" && echo true || echo false)"
echo "provider_msp_upgrade_run_id=${run_id}"
echo "provider_msp_upgrade_restore_target=${restore_target}"
echo "provider_msp_upgrade_tenant_runtime_image=${tenant_runtime_image}"
run_control provider-msp status
run_control "${preflight_args[@]}"
if truthy "${dry_run}"; then
run_control tenant-runtime rollout --all --image "${tenant_runtime_image}" --dry-run
echo "tenant_runtime_rollout_applied=false"
echo "provider_msp_upgrade_plan_ok=true"
exit 0
fi
backup_args=(provider-msp backup create)
if [[ -n "${backup_output}" ]]; then
backup_args+=(--output "${backup_output}")
fi
backup_output_text="$(run_control "${backup_args[@]}")"
printf '%s\n' "${backup_output_text}"
archive_path="$(printf '%s\n' "${backup_output_text}" | extract_field archive_path)"
if [[ -z "${archive_path}" ]]; then
die "provider-msp backup create did not print archive_path"
fi
run_control provider-msp backup verify "${archive_path}"
run_control provider-msp backup restore "${archive_path}" --target-data-dir "${restore_target}" --dry-run
run_control provider-msp status --require-backup
if ! truthy "${skip_compose_pull}"; then
docker compose pull traefik docker-socket-proxy control-plane
fi
docker compose up -d traefik docker-socket-proxy control-plane
run_control provider-msp status --require-backup
run_control tenant-runtime rollout --all --image "${tenant_runtime_image}" --dry-run
if truthy "${rollout_tenants}"; then
reconcile_args=(
tenant-runtime rollout
--all
--image "${tenant_runtime_image}"
--run-id "${run_id}"
--health-timeout "${health_timeout}"
)
if truthy "${prune_previous}"; then
reconcile_args+=(--prune-previous)
fi
run_control "${reconcile_args[@]}"
run_control provider-msp status --require-backup
echo "tenant_runtime_rollout_applied=true"
else
echo "tenant_runtime_rollout_applied=false"
echo "tenant_runtime_rollout_next_command=docker compose run --rm --no-deps control-plane tenant-runtime rollout --all --image ${tenant_runtime_image} --run-id ${run_id} --health-timeout ${health_timeout}"
fi
docker compose ps
echo "backup_path=${archive_path}"
echo "restore_target_data_dir=${restore_target}"
echo "provider_msp_upgrade_ok=true"