From 25febfbf82bc06020822883bc42e11712b3dd789 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Tue, 2 Jun 2026 15:25:05 +0100 Subject: [PATCH] Fix provider MSP compose mount identity --- deploy/provider-msp/.env.example | 3 +++ deploy/provider-msp/docker-compose.yml | 14 +++++------ deploy/provider-msp/setup.sh | 23 ++++++++++++++++++- .../subsystems/deployment-installability.md | 5 +++- .../installtests/provider_msp_deploy_test.go | 14 ++++++++++- 5 files changed, 49 insertions(+), 10 deletions(-) diff --git a/deploy/provider-msp/.env.example b/deploy/provider-msp/.env.example index ad3e2a768..59057696c 100644 --- a/deploy/provider-msp/.env.example +++ b/deploy/provider-msp/.env.example @@ -15,6 +15,9 @@ CP_ENV=production CP_ADMIN_KEY= PULSE_PROVIDER_MSP_DATA_DIR=/data PULSE_PROVIDER_MSP_DOCKER_NETWORK=pulse-provider-msp +PULSE_PROVIDER_MSP_DOCKER_SOCKET=/var/run/docker.sock +PULSE_PROVIDER_MSP_HOST_ROOT=/ +PULSE_PROVIDER_MSP_DOCKER_DATA_DIR=/var/lib/docker CP_PROVIDER_MSP_LICENSE_FILE=./provider-msp-license.jwt CP_TRIAL_ACTIVATION_PRIVATE_KEY= CP_TENANT_MEMORY_LIMIT=536870912 diff --git a/deploy/provider-msp/docker-compose.yml b/deploy/provider-msp/docker-compose.yml index 34f20a916..92018d488 100644 --- a/deploy/provider-msp/docker-compose.yml +++ b/deploy/provider-msp/docker-compose.yml @@ -5,7 +5,7 @@ services: - "80:80" - "443:443" volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro + - ${PULSE_PROVIDER_MSP_DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock:ro - ./traefik.yml:/etc/traefik/traefik.yml:ro - ./traefik-dynamic.yml:/etc/traefik/traefik-dynamic.yml:ro - acme-data:/etc/traefik/acme @@ -22,16 +22,16 @@ services: control-plane: image: ${CONTROL_PLANE_IMAGE:?CONTROL_PLANE_IMAGE must be set to a digest-pinned image} volumes: - - ${PULSE_PROVIDER_MSP_DATA_DIR:-/data}:/data - - /:/host-root:ro - - /var/lib/docker:/host-var-lib-docker:ro - - /var/run/docker.sock:/var/run/docker.sock + - ${PULSE_PROVIDER_MSP_DATA_DIR:-/data}:${PULSE_PROVIDER_MSP_DATA_DIR:-/data} + - ${PULSE_PROVIDER_MSP_HOST_ROOT:-/}:/host-root:ro + - ${PULSE_PROVIDER_MSP_DOCKER_DATA_DIR:-/var/lib/docker}:/host-var-lib-docker:ro + - ${PULSE_PROVIDER_MSP_DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock secrets: - provider_msp_license env_file: - .env environment: - - CP_DATA_DIR=/data + - CP_DATA_DIR=${PULSE_PROVIDER_MSP_DATA_DIR:-/data} - CP_ENV=${CP_ENV} - CP_CONTROL_PLANE_MODE=provider_hosted_msp - CP_BIND_ADDRESS=0.0.0.0 @@ -45,7 +45,7 @@ services: - CP_ALLOW_DOCKERLESS_PROVISIONING=${CP_ALLOW_DOCKERLESS_PROVISIONING:-false} - CP_STORAGE_GUARDRAILS_ENABLED=${CP_STORAGE_GUARDRAILS_ENABLED:-true} - CP_STORAGE_ROOT_PATH=/host-root - - CP_STORAGE_DATA_PATH=/data + - CP_STORAGE_DATA_PATH=${PULSE_PROVIDER_MSP_DATA_DIR:-/data} - CP_STORAGE_DOCKER_PATH=/host-var-lib-docker - CP_STORAGE_MIN_ROOT_AVAILABLE=${CP_STORAGE_MIN_ROOT_AVAILABLE:-10GiB} - CP_STORAGE_MIN_DATA_AVAILABLE=${CP_STORAGE_MIN_DATA_AVAILABLE:-5GiB} diff --git a/deploy/provider-msp/setup.sh b/deploy/provider-msp/setup.sh index 025fee258..4675ed992 100755 --- a/deploy/provider-msp/setup.sh +++ b/deploy/provider-msp/setup.sh @@ -8,6 +8,9 @@ IFS=$'\n\t' PULSE_PROVIDER_MSP_INSTALL_DIR="${PULSE_PROVIDER_MSP_INSTALL_DIR:-/opt/pulse-provider-msp}" PULSE_PROVIDER_MSP_DATA_DIR="${PULSE_PROVIDER_MSP_DATA_DIR:-/data}" PULSE_PROVIDER_MSP_DOCKER_NETWORK="${PULSE_PROVIDER_MSP_DOCKER_NETWORK:-pulse-provider-msp}" +PULSE_PROVIDER_MSP_DOCKER_SOCKET="${PULSE_PROVIDER_MSP_DOCKER_SOCKET:-/var/run/docker.sock}" +PULSE_PROVIDER_MSP_HOST_ROOT="${PULSE_PROVIDER_MSP_HOST_ROOT:-/}" +PULSE_PROVIDER_MSP_DOCKER_DATA_DIR="${PULSE_PROVIDER_MSP_DOCKER_DATA_DIR:-/var/lib/docker}" PULSE_PROVIDER_MSP_BUNDLE_URL="${PULSE_PROVIDER_MSP_BUNDLE_URL:-}" PULSE_PROVIDER_MSP_EXPECT_ENV="${PULSE_PROVIDER_MSP_EXPECT_ENV:-production}" PULSE_PROVIDER_MSP_SKIP_PULL="${PULSE_PROVIDER_MSP_SKIP_PULL:-0}" @@ -244,6 +247,7 @@ Edit it now and set required values: - CP_ADMIN_KEY - PULSE_PROVIDER_MSP_DATA_DIR - PULSE_PROVIDER_MSP_DOCKER_NETWORK + - PULSE_PROVIDER_MSP_DOCKER_SOCKET - CP_PROVIDER_MSP_LICENSE_FILE - CP_TRIAL_ACTIVATION_PRIVATE_KEY @@ -269,7 +273,7 @@ validate_env_file() { local missing=() local k v - for k in DOMAIN ACME_EMAIL CF_DNS_API_TOKEN CP_ENV TRAEFIK_IMAGE CONTROL_PLANE_IMAGE CP_ADMIN_KEY CP_PULSE_IMAGE PULSE_PROVIDER_MSP_DATA_DIR PULSE_PROVIDER_MSP_DOCKER_NETWORK CP_PROVIDER_MSP_LICENSE_FILE CP_TRIAL_ACTIVATION_PRIVATE_KEY CP_TENANT_MEMORY_LIMIT CP_ALLOW_DOCKERLESS_PROVISIONING CP_STORAGE_GUARDRAILS_ENABLED CP_STORAGE_MIN_ROOT_AVAILABLE CP_STORAGE_MIN_DATA_AVAILABLE CP_STORAGE_MIN_DOCKER_AVAILABLE CP_STORAGE_MAX_DOCKER_BUILD_CACHE CP_PROOF_TENANT_MAX_AGE CP_PROOF_TENANT_MATCHERS CP_REQUIRE_EMAIL_PROVIDER PULSE_EMAIL_FROM PULSE_EMAIL_REPLY_TO; do + for k in DOMAIN ACME_EMAIL CF_DNS_API_TOKEN CP_ENV TRAEFIK_IMAGE CONTROL_PLANE_IMAGE CP_ADMIN_KEY CP_PULSE_IMAGE PULSE_PROVIDER_MSP_DATA_DIR PULSE_PROVIDER_MSP_DOCKER_NETWORK PULSE_PROVIDER_MSP_DOCKER_SOCKET PULSE_PROVIDER_MSP_HOST_ROOT PULSE_PROVIDER_MSP_DOCKER_DATA_DIR CP_PROVIDER_MSP_LICENSE_FILE CP_TRIAL_ACTIVATION_PRIVATE_KEY CP_TENANT_MEMORY_LIMIT CP_ALLOW_DOCKERLESS_PROVISIONING CP_STORAGE_GUARDRAILS_ENABLED CP_STORAGE_MIN_ROOT_AVAILABLE CP_STORAGE_MIN_DATA_AVAILABLE CP_STORAGE_MIN_DOCKER_AVAILABLE CP_STORAGE_MAX_DOCKER_BUILD_CACHE CP_PROOF_TENANT_MAX_AGE CP_PROOF_TENANT_MATCHERS CP_REQUIRE_EMAIL_PROVIDER PULSE_EMAIL_FROM PULSE_EMAIL_REPLY_TO; do v="$(env_value "${k}" "${env_path}")" if [[ -z "${v}" ]]; then missing+=("${k}") @@ -284,6 +288,20 @@ validate_env_file() { die "CP_ENV must be '${expected_env}' for this setup run (got '${cp_env}')" fi + local path_var path_value + for path_var in PULSE_PROVIDER_MSP_DATA_DIR PULSE_PROVIDER_MSP_DOCKER_SOCKET PULSE_PROVIDER_MSP_HOST_ROOT PULSE_PROVIDER_MSP_DOCKER_DATA_DIR; do + path_value="$(env_value "${path_var}" "${env_path}")" + if [[ "${path_value}" != /* ]]; then + die "${path_var} must be an absolute path" + fi + done + if [[ ! -S "$(env_value PULSE_PROVIDER_MSP_DOCKER_SOCKET "${env_path}")" ]]; then + die "PULSE_PROVIDER_MSP_DOCKER_SOCKET must point to a reachable Docker socket" + fi + if [[ ! -d "$(env_value PULSE_PROVIDER_MSP_HOST_ROOT "${env_path}")" ]]; then + die "PULSE_PROVIDER_MSP_HOST_ROOT must point to an existing directory" + fi + local image_ref for k in TRAEFIK_IMAGE CONTROL_PLANE_IMAGE CP_PULSE_IMAGE; do image_ref="$(env_value "${k}" "${env_path}")" @@ -306,6 +324,9 @@ validate_env_file() { if ! truthy "$(env_value CP_STORAGE_GUARDRAILS_ENABLED "${env_path}")"; then die "CP_STORAGE_GUARDRAILS_ENABLED must be true for provider-hosted MSP deploys" fi + if [[ ! -d "$(env_value PULSE_PROVIDER_MSP_DOCKER_DATA_DIR "${env_path}")" ]]; then + die "PULSE_PROVIDER_MSP_DOCKER_DATA_DIR must point to the host Docker data directory" + fi local proof_matchers required_matcher proof_matchers="$(env_value CP_PROOF_TENANT_MATCHERS "${env_path}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')" diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index c1dff9785..7acd80048 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -158,7 +158,10 @@ surfaces. missing signed MSP license files, Dockerless production provisioning, disabled storage guardrails, or Stripe/cloud-signup variables, validate compose, and optionally hand off to `run-install-proof.sh` when the provider account name - and owner email are supplied. + and owner email are supplied. Because provider-hosted MSP provisions tenant + containers through the host Docker socket, the provider data directory must + be mounted at the same absolute path inside the control-plane container that + the host Docker daemon will later use for tenant runtime bind mounts. `pulse-control-plane provider-msp status` is the non-mutating operational companion to that proof: it must report registry readiness, tenant state/health counts, stuck provisioning workspaces, Docker runtime diff --git a/scripts/installtests/provider_msp_deploy_test.go b/scripts/installtests/provider_msp_deploy_test.go index acce93073..208580b07 100644 --- a/scripts/installtests/provider_msp_deploy_test.go +++ b/scripts/installtests/provider_msp_deploy_test.go @@ -22,9 +22,12 @@ func TestProviderMSPDeployComposeIsProviderModeAndStripeFree(t *testing.T) { text := string(composeBytes) assertContainsAll(t, text, "CP_CONTROL_PLANE_MODE=provider_hosted_msp", + "CP_DATA_DIR=${PULSE_PROVIDER_MSP_DATA_DIR:-/data}", "CP_PROVIDER_MSP_LICENSE_FILE=/run/secrets/provider_msp_license", "CP_DOCKER_NETWORK=${PULSE_PROVIDER_MSP_DOCKER_NETWORK:-pulse-provider-msp}", - "${PULSE_PROVIDER_MSP_DATA_DIR:-/data}:/data", + "CP_STORAGE_DATA_PATH=${PULSE_PROVIDER_MSP_DATA_DIR:-/data}", + "${PULSE_PROVIDER_MSP_DATA_DIR:-/data}:${PULSE_PROVIDER_MSP_DATA_DIR:-/data}", + "${PULSE_PROVIDER_MSP_DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock", "provider_msp_license:", "name: ${PULSE_PROVIDER_MSP_DOCKER_NETWORK:-pulse-provider-msp}", ) @@ -48,6 +51,9 @@ func TestProviderMSPDeployEnvExampleMatchesBootstrapPath(t *testing.T) { "CP_ENV=production", "PULSE_PROVIDER_MSP_DATA_DIR=/data", "PULSE_PROVIDER_MSP_DOCKER_NETWORK=pulse-provider-msp", + "PULSE_PROVIDER_MSP_DOCKER_SOCKET=/var/run/docker.sock", + "PULSE_PROVIDER_MSP_HOST_ROOT=/", + "PULSE_PROVIDER_MSP_DOCKER_DATA_DIR=/var/lib/docker", "CP_PROVIDER_MSP_LICENSE_FILE=./provider-msp-license.jwt", "CP_TRIAL_ACTIVATION_PRIVATE_KEY=", "sudo -E ./setup.sh", @@ -104,6 +110,12 @@ func TestProviderMSPSetupScriptMatchesProviderContract(t *testing.T) { "CP_TRIAL_ACTIVATION_PRIVATE_KEY", "PULSE_PROVIDER_MSP_DATA_DIR", "PULSE_PROVIDER_MSP_DOCKER_NETWORK", + "PULSE_PROVIDER_MSP_DOCKER_SOCKET", + "PULSE_PROVIDER_MSP_HOST_ROOT", + "PULSE_PROVIDER_MSP_DOCKER_DATA_DIR", + "must be an absolute path", + "must point to a reachable Docker socket", + "must point to the host Docker data directory", "must not be configured in provider-hosted MSP mode", "CP_ALLOW_DOCKERLESS_PROVISIONING must be false", "CP_STORAGE_GUARDRAILS_ENABLED must be true",