backend_add_from_image ships. Operator hands the platform a pre-built OCI image reference (grafana/grafana:11.3.0, docker.io/library/postgres:16, digest-pinned name@sha256:…) + hostname + container port; the platform pulls onto the VPS, allocates a port, configures DNS, runs nixos-rebuild, lands a Caddy site, and health-probes. New OciImageRef newtype with shell-meta-rejecting validator (image refs ride SSH to podman pull; defense-in-depth at the type boundary). New OciImagePuller trait + LocalSshOciPuller (production: podman image inspect probe → conditional podman pull based on PullPolicy::IfNotPresent/Always/Never) + MockOciPuller (queueable digests for upstream-retag simulation). New BackendSource { Repo {…}, Image {…} } enum on BackendRecord; new image_digest: Option<RemoteImageDigest> field captures the resolved content address. New phase events: ImagePullStarted, ImagePulled, NixDeployStarted, NixDeployCompleted, BackendDeployedFromImage { live_url, image_digest }. BackendImageDecl renders to the same Nix attribute shape as BackendDecl; when a digest is present the unit pins to <name>@sha256:… for byte-exact reproducibility. State YAML schema bumped v6 → v7 (additive — old records load with source: None). Reuses PLATFORM-37's per-VPS mutex + Caddy deployer end-to-end; reuses PLATFORM-38's BasicAuth lock-out preflight verbatim. New [[image_backends]] AppSpec entry deploys via app_create; mixed AppSpec with both [[backends]] (repo) + [[image_backends]] work in one call. 12 new integration tests in tests/backend_image_lifecycle.rs covering happy path / no-op redeploy / different-digest redeploy / pull failure / BasicAuth lock-out / mixed AppSpec / never-but-missing / coexistence / schema migration / missing target VPS. 529 → 565 (+36), all green.secret_put_bcrypt RPC bcrypt-hashes the plain password in-process at cost 14 (CF Access default), drops the SecretString before any further async work, stores the hash as Secret::BcryptHash { hash, cost }, and yields exactly BcryptStored { secret_key } on the event stream — plain password never serializes, never logs, never round-trips through describe/reveal. Caddy renderer's Enforcement::BasicAuth arm now emits basicauth { operator $2a$14$… } with optional realm; bcrypt hash comes from a BasicAuthSecrets map the orchestrator pre-resolves from the SecretStore (renderer stays sync). New [site.protect] TOML section parses into the existing Enforcement tagged enum. backend_add_from_repo for a BasicAuth-protected backend pre-flights the secret at config_load — wrong variant or missing surfaces BackendRollback with a clear secret_put_bcrypt --tenant {} --secret-key {} remediation hint, BEFORE any image build / DNS / Caddy work. CaddyDeployer::deploy trait widened to take the secret map; all three call sites (add, rollback strip, remove) updated. 4 new builder tests + 2 config tests + 2 hub tests + 3 integration tests in tests/backend_basic_auth.rs; render_basic_auth_never_contains_plain_password_substring + happy_path_renders_basic_auth_block_with_hash_and_never_plain pin the no-leak contract. 519 → 529 (+10), all green.backend_add_from_repo's phase chain extends to ... -> UnitCreated -> CaddySiteAdded -> HealthCheckProbing -> BackendDeployed; backend_remove strips the Caddy site block FIRST, then stops the container, then drops DNS. Per-VPS tokio::sync::Mutex at the orchestrator level serializes concurrent backend adds against the same VPS so the read-modify-write of the persisted CaddyDeploymentRecord stays consistent. New BackendStatus::BehindCaddyOnVps { vps, hostname } variant; new LocalPlatform::{put,get,remove}_caddy_deployment accessors (state file schema bumped v5 -> v6); new BackendEvent variants CaddySiteAdded / CaddySiteRemoved / EnforcementVariantNotImplemented / RemoveStepFailed. Enforcement::None is the only V1 variant; BasicAuth/CfAccess/PomeriumOnVps emit a clear "not yet implemented in PLATFORM-37" event and refuse to add (PLATFORM-38/34/35 land them). 5 new integration tests in tests/backend_caddy_lifecycle.rs + 5 unit tests for status round-trip + caddy state persistence. 509 -> 519 (+10), all green.vps_provision + vps_destroy streaming RPCs against the INFRA-2 hetzner-vps OpenTofu module: VpsProvisionInit -> TofuInitStarted -> SshKeyEnsured -> TofuPlanCompleted -> TofuApplyProgress* -> TofuApplyCompleted -> VpsRegistered -> VpsProvisioned. New TofuRunner trait + HetznerSshKeyBootstrap + typed VpsRecord persisted by LocalPlatform (schema bumped v4 -> v5). Apply-failure rolls back via reverse-LIFO: emits VpsProvisionRollback + drives a tofu destroy cleanup so we never leave paid-for cloud resources without state. Idempotent re-run short-circuits with VpsAlreadyExists. 7 integration tests + 35 unit tests, all green via MockTofuRunner + MockHetznerSshKey.image_push streaming RPC ships local OCI images to a remote registry: ImagePushInit -> AuthCaptured -> TagApplied -> PushStarted -> PushProgress -> PushCompleted (or ImageAlreadyPushed when remote already carries the same digest). Sibling to OciBuildExecutor; new OciImagePusher trait + LocalPodmanImagePusher + MockImagePusher. Auth pre-flight reads X-OAuth-Scopes from api.github.com/user; missing write:packages raises typed error with gh auth refresh remediation. 19 new tests (14 unit + 5 integration), all green.nixos_install streaming RPC wraps nixos-anywhere: init -> preflight -> flake_build -> kexec -> partition -> copy -> install -> reboot -> reachable -> completed. Idempotent re-run on already-NixOS short-circuits with idempotent: true. --validate-only emits FlakeBuilt + NixosReachable, no destructive ops. 24 new tests (16 unit + 8 integration), all green via MockNixosAnywhereRunner / MockSshExecutor / MockReachabilityProbe.namecom-username + namecom-token as separate files, matching every other provider. Legacy combined-line still works.--tenant "foo; rm -rf /" — returns typed validation error, no shell exec. BackendName::new_unchecked deleted with Option<BackendName> widening.CaddyConfigBuilder + CaddyDeployer trait + LocalCaddyDeployer with SSH-driven upload, atomic swap, systemctl reload caddy, and .bak rollback on reload failure. Enforcement enum closed at definition (None renders today; BasicAuth/CfAccess/PomeriumOnVps error cleanly with EnforcementVariantNotImplemented for PLATFORM-34/35/38). 22 caddy tests + live caddy validate when on PATH. 412 → 436 tests.ZoneId validator immediately caught a regression in cf_record_to_dns (CF dropped the zone_id field circa 2025; #[serde(default)] was silently feeding empty strings into ZoneId). PLATFORM-29's wire-shape suite caught it; fix routes the empty case through from_str_unchecked("unknown").hypermemetic-changelog SiteRecord from the early dashboard fail was the first real-world test case for the new teardown.secret_get aliased to secret_describe; bytes-on-wire path is secret_reveal --confirm-reveal=true only.synapse … site_add_from_repo --tenant "foo; rm -rf /" now returns Invalid params: invalid TenantSlug and never reaches a shell.
Checked at build time (2026-04-29T20:15Z) via curl -s -o /dev/null -w "%{http_code}" <url>.
| URL | HTTP status | Result |
|---|---|---|
| https://hypermemetic.ai | 200 | OK |
| https://c2c.hypermemetic.ai | 200 | OK |
| https://www.hypermemetic.ai | 200 | OK |
| https://changelog.hypermemetic.ai | 200 | OK |