Pulse/deploy/provider-msp/.env.example
rcourtman 6be2af1c19 Let providers evaluate MSP without asking permission first
Two mandatory round-trips stood between an interested MSP and their first
screen, and neither was technical.

setup.sh required four image digests shipped as literal <pin>
placeholders, so the only way to get them was to ask. All four images are
publicly readable, so there was never anything to hand out. setup.sh now
resolves each blank pin to an immutable digest from its published tag via
buildx imagetools and writes it back to .env; hand-set values are left
alone.

setup.sh then died outright without a licence file, so nobody could start
the stack, create a workspace, or see the portal until a human minted a
licence for them. The control plane already ran unlicensed via
ProviderMSPPlanSourceEnvFallback; only the installer refused. A licence
path that is set but missing is still a hard failure, since that is a
misconfiguration rather than a choice.

Unlicensed now means evaluation rather than the cheapest paid tier. The
env fallback defaulted to msp_starter, handing every unlicensed
deployment the full 5-client Starter allowance and leaving no boundary
between evaluating and buying. Adds msp_eval at 2 workspaces: same
capabilities, smaller cap, not purchasable, not on the public ladder.

An isolation guarantee is the one claim a provider cannot evaluate from a
screenshot, and both MSP leads this year went quiet at exactly this step.

Contracts: cloud-paid records the unlicensed plan rule and the
strictly-below-paid invariant; deployment-installability records
credential-free, correspondence-free installability.

Verification: TestMSPEvalCapStaysBelowCheapestPaidTier,
TestCanonicalizePlanVersion_MSPEval,
TestProviderMSPSetupScriptSupportsUnlicensedEvaluation. The last was
negative-tested by reintroducing a <pin> placeholder and confirming it
fails. ensure_image_pins exercised against the live registries.
licensing, cloudcp, control-plane and installtests all green.
2026-08-03 16:31:24 +01:00

149 lines
6.3 KiB
Text

# Domain
# Client runtimes are routed as https://<client-id>.${DOMAIN}/.
# Provider-hosted MSP: use the MSP's own domain, for example pulse.example-msp.com.
# Pulse-hosted MSP: use a Pulse-operated provider namespace, for example example-msp.msp.pulserelay.pro, and set CP_CONTROL_PLANE_MODE=pulse_hosted_msp.
DOMAIN=msp.example.com
ACME_EMAIL=admin@example.com
# Cloudflare DNS-01 wildcard TLS
CF_DNS_API_TOKEN=
# Image pins. Leave blank and setup.sh resolves each one to an immutable
# digest from its published tag, then writes the digest back here. All four
# images are public, so this needs no registry credentials.
#
# Pin them by hand instead if you want to hold a specific build; setup.sh
# leaves any value you set alone.
TRAEFIK_IMAGE=
DOCKER_SOCKET_PROXY_IMAGE=
CONTROL_PLANE_IMAGE=
CP_PULSE_IMAGE=
# Control plane
CP_ENV=production
CP_CONTROL_PLANE_MODE=provider_hosted_msp
CP_ADMIN_KEY=
PULSE_PROVIDER_MSP_DATA_DIR=/data
PULSE_PROVIDER_MSP_DOCKER_NETWORK=pulse-provider-msp
PULSE_PROVIDER_MSP_DOCKER_SUBNET=172.30.0.0/24
PULSE_PROVIDER_MSP_DOCKER_SOCKET=/var/run/docker.sock
PULSE_PROVIDER_MSP_ROOT_SPACECHECK_DIR=/var/lib/pulse-provider-msp/spacecheck/root
PULSE_PROVIDER_MSP_DOCKER_SPACECHECK_DIR=/var/lib/docker/.pulse-provider-msp-spacecheck
CP_TRUSTED_PROXY_CIDRS=172.30.0.0/24
# Licence file. Leave BLANK to evaluate: the control plane runs on msp_eval,
# which is the full product capped at 2 client workspaces, so you can stand
# this up and onboard two real clients without talking to anyone first.
#
# Set it to your licence path once you buy. Paid client caps come from the
# licence, never from this file.
CP_PROVIDER_MSP_LICENSE_FILE=
# Entitlement lease signing key. setup.sh generates this; the private key
# never leaves this host. Your provider MSP license must bind the derived
# PUBLIC key (entitlement_signing_public_key); print it with
# `./setup.sh --print-lease-signing-public-key` and include it in your
# license request. The control plane refuses to start if license and key
# do not match.
CP_ENTITLEMENT_SIGNING_PRIVATE_KEY=
CP_TENANT_MEMORY_LIMIT=536870912
CP_ALLOW_DOCKERLESS_PROVISIONING=false
CP_STORAGE_GUARDRAILS_ENABLED=true
CP_STORAGE_MIN_ROOT_AVAILABLE=10GiB
CP_STORAGE_MIN_DATA_AVAILABLE=5GiB
CP_STORAGE_MIN_DOCKER_AVAILABLE=10GiB
CP_STORAGE_MAX_DOCKER_BUILD_CACHE=2GiB
CP_PROOF_TENANT_MAX_AGE=24h
CP_PROOF_TENANT_MATCHERS=proof,canary,rehearsal,msp_prod,ownerseed,owner_seed
# Email is optional. Without RESEND_API_KEY the portal cannot send sign-in
# links or invitation emails; instead, print one-time links on this host:
# docker compose run --rm control-plane provider-msp bootstrap \
# --account-name "Example MSP" --owner-email owner@example.com # owner link
# docker compose run --rm control-plane provider-msp portal-link \
# --email teammate@example.com # teammate link
# The portal sign-in page shows the same instructions in this state.
# Portal sessions last 7 days (CP_SESSION_TTL to change).
# Set CP_REQUIRE_EMAIL_PROVIDER=true once transactional email is configured.
CP_REQUIRE_EMAIL_PROVIDER=false
RESEND_API_KEY=
PULSE_EMAIL_FROM=noreply@example.com
PULSE_EMAIL_REPLY_TO=support@example.com
# Fresh host setup:
# sudo -E ./setup.sh
#
# Optional one-command setup proof after .env is filled:
# sudo -E PULSE_PROVIDER_MSP_ACCOUNT_NAME="Example MSP" \
# PULSE_PROVIDER_MSP_OWNER_EMAIL=owner@example.com \
# ./setup.sh
#
# First owner bootstrap:
# docker compose run --rm control-plane provider-msp bootstrap \
# --account-name "Example MSP" \
# --owner-email owner@example.com
#
# Install readiness check:
# docker compose run --rm control-plane provider-msp preflight
#
# Non-mutating operational status:
# docker compose run --rm control-plane provider-msp status
#
# Pre-upgrade / pre-maintenance status gate. This fails unless a verified
# provider MSP backup archive is available:
# docker compose run --rm control-plane provider-msp status --require-backup
#
# Pre-upgrade / pre-maintenance plan. This is non-mutating and does not pull
# images:
# ./upgrade.sh --dry-run
#
# Apply a provider control-plane upgrade after updating the image pins in this
# file. The runner creates and verifies a fresh backup, dry-runs restore into a
# separate target data dir, pulls provider images, starts Traefik/control-plane,
# and prints the tenant runtime rollout plan for CP_PULSE_IMAGE:
# ./upgrade.sh
#
# Also roll all client runtimes to the configured CP_PULSE_IMAGE line after the
# provider services are updated:
# ./upgrade.sh --rollout-tenants
#
# Fresh-install provider proof. This bootstraps the owner account, checks install
# readiness, creates two proof client workspaces, verifies tenant-local agent
# ingest and portal handoff, creates and verifies a backup, dry-runs restore and
# recovery, then removes the proof workspaces.
# ./run-install-proof.sh \
# --account-name "Example MSP" \
# --owner-email owner@example.com
#
# Equivalent one-off command:
# docker compose run --rm control-plane provider-msp install-proof \
# --account-name "Example MSP" \
# --owner-email owner@example.com
#
# Plan recovery for failed, stuck provisioning, or unhealthy client workspaces:
# docker compose run --rm control-plane provider-msp recover --all-degraded --dry-run
#
# Execute recovery after reviewing the dry-run:
# docker compose run --rm control-plane provider-msp recover --all-degraded
#
# Recovery backup before upgrades or recovery drills:
# docker compose run --rm control-plane provider-msp backup create
#
# Verify a recovery archive before relying on it:
# docker compose run --rm control-plane provider-msp backup verify \
# /data/backups/provider-msp/provider-msp-backup-YYYYMMDDTHHMMSSZ.tar.gz
#
# Recovery dry-run on a fresh target data directory:
# docker compose run --rm control-plane provider-msp backup restore \
# /data/backups/provider-msp/provider-msp-backup-YYYYMMDDTHHMMSSZ.tar.gz \
# --target-data-dir /data-restore-drill \
# --dry-run
#
# Workspace/runtime proof. This creates two proof client workspaces, generates
# client-bound agent install tokens, verifies tenant-local agent report ingest,
# and exercises the portal handoff path. The broader install-proof command above
# wraps this with preflight, backup, restore dry-run, recovery dry-run, cleanup,
# and status checks.
# docker compose run --rm control-plane provider-msp proof \
# --account-name "Example MSP" \
# --owner-email owner@example.com \
# --cleanup