rcf-lite 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/blueprints/application-account-settings/docs/topics.md +4 -1
- package/blueprints/application-admin-console/docs/topics.md +4 -1
- package/blueprints/application-api-rest/docs/topics.md +4 -1
- package/blueprints/application-charts/docs/topics.md +4 -1
- package/blueprints/application-dashboard/docs/topics.md +4 -1
- package/blueprints/application-datatable/docs/topics.md +4 -1
- package/blueprints/application-empty-error-states/docs/topics.md +4 -1
- package/blueprints/application-error-handling/docs/topics.md +4 -1
- package/blueprints/application-file-upload/docs/topics.md +4 -1
- package/blueprints/application-forms-wizard/docs/topics.md +4 -1
- package/blueprints/application-notifications-in-app/docs/topics.md +4 -1
- package/blueprints/application-onboarding-tour/docs/topics.md +4 -1
- package/blueprints/application-spa/docs/topics.md +4 -1
- package/blueprints/delivery-ci-workflows/docs/topics.md +4 -1
- package/blueprints/deploy-cloudflare-workers/docs/topics.md +4 -1
- package/blueprints/deploy-hetzner-server/CHANGELOG.md +55 -0
- package/blueprints/deploy-hetzner-server/README.md +202 -0
- package/blueprints/deploy-hetzner-server/blueprint.json +233 -0
- package/blueprints/deploy-hetzner-server/contributions/adrs/adr-3801-deploy-hetzner-server-cloud-host-contract.json +14 -0
- package/blueprints/deploy-hetzner-server/contributions/adrs/adr-3802-deploy-hetzner-server-snapshot-cadence.json +14 -0
- package/blueprints/deploy-hetzner-server/contributions/adrs/adr-3803-deploy-hetzner-server-provisioning-tool.json +20 -0
- package/blueprints/deploy-hetzner-server/contributions/adrs/adr-3804-deploy-hetzner-server-hardening-baseline.json +14 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/cloud-init-render-lint.mjs +71 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/hcloud-dry-run-mock.mjs +164 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/manifest-schema-validate.mjs +182 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/probe-utils.mjs +112 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/real-account-cloud-init-hardened.mjs +64 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/real-account-snapshot-on-demand.mjs +60 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/real-account-throwaway-server-provision.mjs +58 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/run-cloud-init-render-lint.mjs +9 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/run-hcloud-dry-run-mock.mjs +9 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/run-manifest-schema-validate.mjs +9 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/run-real-account-cloud-init-hardened.mjs +9 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/run-real-account-snapshot-on-demand.mjs +9 -0
- package/blueprints/deploy-hetzner-server/contributions/probes/run-real-account-throwaway-server-provision.mjs +9 -0
- package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-001.json +18 -0
- package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-002.json +18 -0
- package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-003.json +18 -0
- package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-004.json +18 -0
- package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-005.json +18 -0
- package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-006.json +18 -0
- package/blueprints/deploy-hetzner-server/contributions/schemas/hetzner-server.schema.json +108 -0
- package/blueprints/deploy-hetzner-server/contributions/tacs/tac-3801-deploy-hetzner-server-provisioner.json +27 -0
- package/blueprints/deploy-hetzner-server/contributions/tacs/tac-3802-deploy-hetzner-server-manifest-schema.json +26 -0
- package/blueprints/deploy-hetzner-server/contributions/tacs/tac-3803-deploy-hetzner-server-cloud-init-template.json +26 -0
- package/blueprints/deploy-hetzner-server/contributions/tacs/tac-3804-deploy-hetzner-server-firewall-shape.json +25 -0
- package/blueprints/deploy-hetzner-server/contributions/templates/cloud-init.yaml.tmpl +113 -0
- package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37101.json +24 -0
- package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37102.json +24 -0
- package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37103.json +25 -0
- package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37104.json +24 -0
- package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37105.json +24 -0
- package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37106.json +25 -0
- package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37107.json +24 -0
- package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37108.json +24 -0
- package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37109.json +24 -0
- package/blueprints/deploy-hetzner-server/docs/topics.md +48 -0
- package/blueprints/deploy-hetzner-server/guide/deploy-hetzner-server.md +180 -0
- package/blueprints/edge-cloudflare-access/docs/topics.md +4 -1
- package/blueprints/edge-cloudflare-rate-limiting/docs/topics.md +4 -1
- package/blueprints/edge-cloudflare-tunnel/CHANGELOG.md +23 -0
- package/blueprints/edge-cloudflare-tunnel/README.md +257 -0
- package/blueprints/edge-cloudflare-tunnel/blueprint.json +168 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/adrs/adr-4001-edge-cloudflare-tunnel-ingress-bridge-contract.json +23 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/adrs/adr-4002-edge-cloudflare-tunnel-connector-runtime.json +19 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/adrs/adr-4003-edge-cloudflare-tunnel-hostname-mode.json +19 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/aud-presence-check.mjs +114 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/cloudflared-config-lint.mjs +80 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/manifest-schema-validate.mjs +237 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/probe-utils.mjs +231 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/real-account-connector-healthy.mjs +143 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/real-account-tunnel-hostname-routes.mjs +166 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/run-aud-presence-check.mjs +4 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/run-cloudflared-config-lint.mjs +4 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/run-manifest-schema-validate.mjs +4 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/run-real-account-connector-healthy.mjs +4 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/run-real-account-tunnel-hostname-routes.mjs +4 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/probes/tunnel-manifest.schema.json +55 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/requirements/edge-cloudflare-tunnel-req-001.json +18 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/requirements/edge-cloudflare-tunnel-req-002.json +18 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/requirements/edge-cloudflare-tunnel-req-003.json +18 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/requirements/edge-cloudflare-tunnel-req-004.json +18 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/requirements/edge-cloudflare-tunnel-req-005.json +18 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/tacs/tac-4001-edge-cloudflare-tunnel-connector-shape.json +26 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/tacs/tac-4002-edge-cloudflare-tunnel-manifest-schema.json +26 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/tacs/tac-4003-edge-cloudflare-tunnel-access-binding.json +25 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39101.json +24 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39102.json +24 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39103.json +24 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39104.json +24 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39105.json +24 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39106.json +24 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39107.json +24 -0
- package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39108.json +24 -0
- package/blueprints/edge-cloudflare-tunnel/docs/topics.md +72 -0
- package/blueprints/edge-cloudflare-tunnel/guide/edge-cloudflare-tunnel.md +204 -0
- package/blueprints/edge-cloudflare-turnstile/docs/topics.md +4 -1
- package/blueprints/email-smtp-resend/docs/topics.md +4 -1
- package/blueprints/jobs-background/docs/topics.md +4 -1
- package/blueprints/messaging-queue-cloudflare/CHANGELOG.md +19 -0
- package/blueprints/messaging-queue-cloudflare/README.md +2 -2
- package/blueprints/messaging-queue-cloudflare/blueprint.json +1 -1
- package/blueprints/messaging-queue-cloudflare/contributions/probes/real-account-concurrency-smoke.mjs +192 -37
- package/blueprints/messaging-queue-cloudflare/docs/topics.md +4 -1
- package/blueprints/object-storage-s3/CHANGELOG.md +11 -0
- package/blueprints/object-storage-s3/README.md +14 -1
- package/blueprints/object-storage-s3/blueprint.json +12 -1
- package/blueprints/object-storage-s3/contributions/adrs/adr-2905-object-storage-s3-hetzner-object-storage-provider.json +30 -0
- package/blueprints/object-storage-s3/contributions/probes/hetzner-object-storage-round-trip.mjs +136 -0
- package/blueprints/object-storage-s3/contributions/probes/run-hetzner-object-storage-round-trip.mjs +11 -0
- package/blueprints/object-storage-s3/contributions/requirements/object-storage-s3-req-101.json +18 -0
- package/blueprints/object-storage-s3/contributions/tacs/tac-2904-object-storage-s3-hetzner-endpoint-helper.json +34 -0
- package/blueprints/object-storage-s3/contributions/user-stories/object-storage-s3-us-28110.json +25 -0
- package/blueprints/object-storage-s3/docs/topics.md +4 -1
- package/blueprints/object-storage-s3/guide/object-storage-s3.md +36 -0
- package/blueprints/observability-essentials/docs/topics.md +4 -1
- package/blueprints/observability-logging/docs/topics.md +4 -1
- package/blueprints/observability-probe-endpoints/docs/topics.md +4 -1
- package/blueprints/persistence-data-d1/docs/topics.md +4 -1
- package/blueprints/persistence-data-postgres/docs/topics.md +4 -1
- package/blueprints/persistence-data-sqlite/docs/topics.md +4 -1
- package/blueprints/platform-cloudflare-cron-triggers/docs/topics.md +4 -1
- package/blueprints/platform-cloudflare-durable-objects/CHANGELOG.md +15 -0
- package/blueprints/platform-cloudflare-durable-objects/blueprint.json +1 -1
- package/blueprints/platform-cloudflare-durable-objects/contributions/probes/namespace-facade-ready.mjs +3 -3
- package/blueprints/platform-cloudflare-durable-objects/contributions/probes/probe-utils.mjs +28 -14
- package/blueprints/platform-cloudflare-durable-objects/contributions/probes/real-account-storage-smoke.mjs +132 -26
- package/blueprints/platform-cloudflare-durable-objects/contributions/probes/sole-reader-scan.mjs +40 -39
- package/blueprints/platform-cloudflare-durable-objects/contributions/probes/storage-round-trip.mjs +26 -16
- package/blueprints/platform-cloudflare-durable-objects/contributions/probes/websocket-hub-broadcast.mjs +31 -34
- package/blueprints/platform-cloudflare-durable-objects/docs/topics.md +4 -1
- package/blueprints/platform-cloudflare-kv/CHANGELOG.md +40 -1
- package/blueprints/platform-cloudflare-kv/README.md +41 -13
- package/blueprints/platform-cloudflare-kv/blueprint.json +1 -1
- package/blueprints/platform-cloudflare-kv/contributions/probes/cache-aside-hit-then-miss.mjs +26 -26
- package/blueprints/platform-cloudflare-kv/contributions/probes/event-secrecy.mjs +49 -31
- package/blueprints/platform-cloudflare-kv/contributions/probes/facade-round-trip.mjs +58 -16
- package/blueprints/platform-cloudflare-kv/contributions/probes/list-with-prefix.mjs +6 -5
- package/blueprints/platform-cloudflare-kv/contributions/probes/real-account-eventual-consistency-smoke.mjs +131 -67
- package/blueprints/platform-cloudflare-kv/docs/topics.md +4 -1
- package/blueprints/platform-docker-compose-host/CHANGELOG.md +12 -0
- package/blueprints/platform-docker-compose-host/README.md +76 -0
- package/blueprints/platform-docker-compose-host/blueprint.json +209 -0
- package/blueprints/platform-docker-compose-host/contributions/adrs/adr-3901-platform-docker-compose-host-contract.json +14 -0
- package/blueprints/platform-docker-compose-host/contributions/adrs/adr-3902-platform-docker-compose-host-reverse-proxy.json +14 -0
- package/blueprints/platform-docker-compose-host/contributions/adrs/adr-3903-platform-docker-compose-host-reject-coolify.json +14 -0
- package/blueprints/platform-docker-compose-host/contributions/adrs/adr-3904-platform-docker-compose-host-log-driver.json +14 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/caddyfile-validate.mjs +108 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/compose-config-lint.mjs +231 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/probe-utils.mjs +185 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/real-account-minimal-stack-up.mjs +41 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/real-account-reload-burst.mjs +36 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/run-caddyfile-validate.mjs +5 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/run-compose-config-lint.mjs +5 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/run-real-account-minimal-stack-up.mjs +5 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/run-real-account-reload-burst.mjs +5 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/run-secrets-as-files-scan.mjs +5 -0
- package/blueprints/platform-docker-compose-host/contributions/probes/secrets-as-files-scan.mjs +116 -0
- package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-001.json +18 -0
- package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-002.json +18 -0
- package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-003.json +18 -0
- package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-004.json +18 -0
- package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-005.json +18 -0
- package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-006.json +18 -0
- package/blueprints/platform-docker-compose-host/contributions/tacs/tac-3901-platform-docker-compose-host-compose-layout.json +26 -0
- package/blueprints/platform-docker-compose-host/contributions/tacs/tac-3902-platform-docker-compose-host-secrets-mount.json +26 -0
- package/blueprints/platform-docker-compose-host/contributions/tacs/tac-3903-platform-docker-compose-host-healthcheck-lint.json +26 -0
- package/blueprints/platform-docker-compose-host/contributions/tacs/tac-3904-platform-docker-compose-host-reverse-proxy-artefact.json +26 -0
- package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38101.json +24 -0
- package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38102.json +24 -0
- package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38103.json +24 -0
- package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38104.json +24 -0
- package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38105.json +25 -0
- package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38106.json +24 -0
- package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38107.json +24 -0
- package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38108.json +24 -0
- package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38109.json +24 -0
- package/blueprints/platform-docker-compose-host/docs/topics.md +71 -0
- package/blueprints/platform-docker-compose-host/guide/platform-docker-compose-host.md +94 -0
- package/blueprints/security-auth-clerk/docs/topics.md +4 -1
- package/blueprints/security-auth-keycloak/docs/topics.md +4 -1
- package/blueprints/security-auth-magic-link/docs/topics.md +4 -1
- package/blueprints/security-auth-oauth2/docs/topics.md +4 -1
- package/blueprints/security-secrets-management/docs/topics.md +4 -1
- package/fixtures/canary-manifest.json +9 -9
- package/guidance/harness-template.md +67 -0
- package/guidance/managed/agent-instructions-block.hash +1 -1
- package/guidance/managed/agent-instructions-block.md +67 -0
- package/package.json +2 -2
- package/rcf/adrs/adr-3801-deploy-hetzner-server-cloud-host-contract.json +13 -0
- package/rcf/adrs/adr-3802-deploy-hetzner-server-snapshot-cadence.json +13 -0
- package/rcf/adrs/adr-3803-deploy-hetzner-server-provisioning-tool.json +13 -0
- package/rcf/adrs/adr-3804-deploy-hetzner-server-hardening-baseline.json +13 -0
- package/rcf/adrs/adr-3901-platform-docker-compose-host-contract.json +13 -0
- package/rcf/adrs/adr-3902-platform-docker-compose-host-reverse-proxy.json +13 -0
- package/rcf/adrs/adr-3903-platform-docker-compose-host-reject-coolify.json +13 -0
- package/rcf/adrs/adr-3904-platform-docker-compose-host-log-driver.json +13 -0
- package/rcf/adrs/adr-4001-edge-cloudflare-tunnel-ingress-bridge-contract.json +13 -0
- package/rcf/adrs/adr-4002-edge-cloudflare-tunnel-connector-runtime.json +13 -0
- package/rcf/adrs/adr-4003-edge-cloudflare-tunnel-hostname-mode.json +13 -0
- package/rcf/code-nodes/cn-410.json +20 -0
- package/rcf/code-nodes/cn-440.json +20 -0
- package/rcf/code-nodes/cn-470.json +19 -0
- package/rcf/code-nodes/cn-500.json +12 -0
- package/rcf/code-nodes/cn-510.json +12 -0
- package/rcf/code-nodes/cn-520.json +14 -0
- package/rcf/code-nodes/cn-521.json +12 -0
- package/rcf/code-nodes/cn-522.json +13 -0
- package/rcf/code-nodes/cn-523.json +12 -0
- package/rcf/code-nodes/cn-524.json +14 -0
- package/rcf/code-nodes/cn-525.json +12 -0
- package/rcf/code-nodes/cn-526.json +14 -0
- package/rcf/fbs/fbs-130.json +23 -0
- package/rcf/fbs/fbs-140.json +23 -0
- package/rcf/fbs/fbs-150.json +22 -0
- package/rcf/fbs/fbs-160.json +15 -0
- package/rcf/fbs/fbs-165.json +15 -0
- package/rcf/fbs/fbs-170.json +16 -0
- package/rcf/fbs/fbs-171.json +16 -0
- package/rcf/fbs/fbs-172.json +15 -0
- package/rcf/fbs/fbs-173.json +17 -0
- package/rcf/fbs/fbs-174.json +17 -0
- package/rcf/requirements/req-110.json +58 -0
- package/rcf/requirements/req-111.json +32 -0
- package/rcf/requirements/req-112.json +42 -0
- package/rcf/requirements/req-113.json +33 -0
- package/rcf/requirements/req-114.json +20 -0
- package/rcf/requirements/req-115.json +38 -0
- package/rcf/requirements/req-120.json +20 -0
- package/rcf/requirements/req-121.json +20 -0
- package/rcf/requirements/req-122.json +20 -0
- package/rcf/requirements/req-123.json +42 -0
- package/rcf/requirements/req-124.json +27 -0
- package/rcf/requirements/req-125.json +20 -0
- package/rcf/requirements/req-130.json +32 -0
- package/rcf/requirements/req-131.json +54 -0
- package/rcf/requirements/req-132.json +27 -0
- package/rcf/requirements/req-133.json +20 -0
- package/rcf/requirements/req-134.json +42 -0
- package/rcf/requirements/req-140.json +149 -0
- package/rcf/requirements/req-145.json +53 -0
- package/rcf/requirements/req-150.json +20 -0
- package/rcf/requirements/req-151.json +85 -0
- package/rcf/requirements/req-152.json +20 -0
- package/rcf/requirements/req-153.json +59 -0
- package/rcf/requirements/req-154.json +44 -0
- package/rcf/tacs/tac-3801-deploy-hetzner-server-provisioner.json +17 -0
- package/rcf/tacs/tac-3802-deploy-hetzner-server-manifest-schema.json +17 -0
- package/rcf/tacs/tac-3803-deploy-hetzner-server-cloud-init-template.json +17 -0
- package/rcf/tacs/tac-3804-deploy-hetzner-server-firewall-shape.json +16 -0
- package/rcf/tacs/tac-3901-platform-docker-compose-host-compose-layout.json +17 -0
- package/rcf/tacs/tac-3902-platform-docker-compose-host-secrets-mount.json +17 -0
- package/rcf/tacs/tac-3903-platform-docker-compose-host-healthcheck-lint.json +17 -0
- package/rcf/tacs/tac-3904-platform-docker-compose-host-reverse-proxy-artefact.json +17 -0
- package/rcf/tacs/tac-4001-edge-cloudflare-tunnel-connector-shape.json +17 -0
- package/rcf/tacs/tac-4002-edge-cloudflare-tunnel-manifest-schema.json +17 -0
- package/rcf/tacs/tac-4003-edge-cloudflare-tunnel-access-binding.json +16 -0
- package/rcf/test-suites/ts-071.json +2 -2
- package/rcf/test-suites/ts-086.json +2 -2
- package/rcf/test-suites/ts-140.json +86 -0
- package/rcf/test-suites/ts-150.json +86 -0
- package/rcf/test-suites/ts-160.json +78 -0
- package/rcf/test-suites/ts-170.json +22 -0
- package/rcf/test-suites/ts-175.json +22 -0
- package/rcf/test-suites/ts-180.json +30 -0
- package/rcf/test-suites/ts-181.json +30 -0
- package/rcf/test-suites/ts-182.json +22 -0
- package/rcf/test-suites/ts-183.json +38 -0
- package/rcf/test-suites/ts-184.json +38 -0
- package/rcf/user-stories/us-11001.json +23 -0
- package/rcf/user-stories/us-11101.json +23 -0
- package/rcf/user-stories/us-11102.json +24 -0
- package/rcf/user-stories/us-11201.json +23 -0
- package/rcf/user-stories/us-11202.json +23 -0
- package/rcf/user-stories/us-11301.json +24 -0
- package/rcf/user-stories/us-11401.json +23 -0
- package/rcf/user-stories/us-11402.json +23 -0
- package/rcf/user-stories/us-11501.json +23 -0
- package/rcf/user-stories/us-12001.json +23 -0
- package/rcf/user-stories/us-12101.json +23 -0
- package/rcf/user-stories/us-12102.json +23 -0
- package/rcf/user-stories/us-12201.json +23 -0
- package/rcf/user-stories/us-12202.json +24 -0
- package/rcf/user-stories/us-12301.json +23 -0
- package/rcf/user-stories/us-12401.json +23 -0
- package/rcf/user-stories/us-12402.json +23 -0
- package/rcf/user-stories/us-12501.json +23 -0
- package/rcf/user-stories/us-13001.json +23 -0
- package/rcf/user-stories/us-13002.json +23 -0
- package/rcf/user-stories/us-13101.json +23 -0
- package/rcf/user-stories/us-13102.json +23 -0
- package/rcf/user-stories/us-13201.json +23 -0
- package/rcf/user-stories/us-13301.json +23 -0
- package/rcf/user-stories/us-13302.json +23 -0
- package/rcf/user-stories/us-13401.json +23 -0
- package/rcf/user-stories/us-14001.json +21 -0
- package/rcf/user-stories/us-14501.json +24 -0
- package/rcf/user-stories/us-15001.json +26 -0
- package/rcf/user-stories/us-15101.json +26 -0
- package/rcf/user-stories/us-15201.json +21 -0
- package/rcf/user-stories/us-15301.json +31 -0
- package/rcf/user-stories/us-15401.json +31 -0
- package/releases/releases.yaml +21 -1
- package/src/blueprint/apply.js +64 -0
- package/src/blueprint/consistency-lint.js +432 -0
- package/src/blueprint/disposition-ledger.js +224 -0
- package/src/blueprint/dispositions.js +44 -0
- package/src/blueprint/index.js +19 -0
- package/src/browser-verify/manifest-writer.js +53 -2
- package/src/browser-verify/pack-runner.js +18 -0
- package/src/cli/blueprint.js +103 -0
- package/src/setup/identity-seed.js +18 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# deploy-hetzner-server guide
|
|
2
|
+
|
|
3
|
+
Practical guide for the operator applying `deploy-hetzner-server`
|
|
4
|
+
v1.0.0. Covers manifest authoring, the two provisioner paths, the
|
|
5
|
+
hardening baseline, snapshot posture, elicitation shortcuts, and
|
|
6
|
+
the runtime probe rig.
|
|
7
|
+
|
|
8
|
+
## Prerequisites
|
|
9
|
+
|
|
10
|
+
- A Hetzner Cloud project and API token. The token binds to one
|
|
11
|
+
project; a fresh throwaway project keeps the blast radius small
|
|
12
|
+
during onboarding.
|
|
13
|
+
- `hcloud` on `PATH` when `provisioning-tool` is `hcloud`. Install:
|
|
14
|
+
Homebrew `brew install hcloud` on macOS, or download the OS/arch
|
|
15
|
+
tarball from https://github.com/hetznercloud/cli/releases/latest,
|
|
16
|
+
verify against `checksums.txt`, drop the binary on `PATH`. Skip
|
|
17
|
+
the install when `provisioning-tool` is `raw-api`; the provisioner
|
|
18
|
+
reaches the REST API directly.
|
|
19
|
+
- `security-secrets-management` applied so the facade reads
|
|
20
|
+
`HETZNER_ACCOUNT_API_KEY` from the operator's vault of choice.
|
|
21
|
+
|
|
22
|
+
## Authoring `hetzner/servers/<name>.json`
|
|
23
|
+
|
|
24
|
+
Every server declaration lives at
|
|
25
|
+
`hetzner/servers/<server-name>.json` and validates against the
|
|
26
|
+
shipped `hetzner-server.schema.json`. The fixture manifest at
|
|
27
|
+
`packages/rcf-lite/test/fixtures/hetzner-throwaway-server/hetzner/servers/ci-throwaway.json`
|
|
28
|
+
is the canonical example. Copy it and edit:
|
|
29
|
+
|
|
30
|
+
```jsonc
|
|
31
|
+
{
|
|
32
|
+
"name": "ops-01",
|
|
33
|
+
"serverType": "cx33",
|
|
34
|
+
"location": "fsn1",
|
|
35
|
+
"image": "ubuntu-24.04",
|
|
36
|
+
"sshKeyIds": ["dave-ed25519", "baz-yubikey"],
|
|
37
|
+
"networkId": null,
|
|
38
|
+
"firewallId": null,
|
|
39
|
+
"cloudInitPath": "hetzner/servers/rendered/ops-01.cloud-init.yaml",
|
|
40
|
+
"labels": {
|
|
41
|
+
"role": "ops-host",
|
|
42
|
+
"blueprint": "deploy-hetzner-server"
|
|
43
|
+
},
|
|
44
|
+
"firewallRules": [
|
|
45
|
+
{ "name": "ssh", "protocol": "tcp", "port": 22, "direction": "in", "sourceIps": ["203.0.113.0/24"] },
|
|
46
|
+
{ "name": "http", "protocol": "tcp", "port": 80, "direction": "in", "sourceIps": ["0.0.0.0/0", "::/0"] },
|
|
47
|
+
{ "name": "https", "protocol": "tcp", "port": 443, "direction": "in", "sourceIps": ["0.0.0.0/0", "::/0"] }
|
|
48
|
+
],
|
|
49
|
+
"snapshotCadence": "weekly",
|
|
50
|
+
"hetznerManagedBackups": false
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The manifest-schema-validate probe refuses on missing required
|
|
55
|
+
fields and on a rule set that opens 22 to `0.0.0.0/0`.
|
|
56
|
+
|
|
57
|
+
## Rendering cloud-init
|
|
58
|
+
|
|
59
|
+
Point `cloudInitPath` at a rendered file the provisioner uploads
|
|
60
|
+
with `--user-data-from-file`. The fixture renderer at
|
|
61
|
+
`packages/rcf-lite/test/fixtures/hetzner-throwaway-server/src/cloud-init-renderer.mjs`
|
|
62
|
+
consumes the shipped template with two placeholders:
|
|
63
|
+
|
|
64
|
+
- `{{name}}` (the manifest name)
|
|
65
|
+
- `{{sshAuthorizedKeysBlock}}` (the ssh-authorized_keys entries per
|
|
66
|
+
`sshKeyIds`)
|
|
67
|
+
|
|
68
|
+
An applying project runs the renderer at manifest edit time (a git
|
|
69
|
+
pre-commit hook, or a `pnpm build:cloud-init` script) and commits
|
|
70
|
+
the rendered YAML alongside the manifest.
|
|
71
|
+
|
|
72
|
+
## Six hardening baseline blocks (ADR-3804)
|
|
73
|
+
|
|
74
|
+
The rendered YAML lands the six blocks per the estate baseline at
|
|
75
|
+
`operator/knowledge/systems/environments/hetzner-cloud/overview.md`
|
|
76
|
+
Security posture baseline and the Debian unattended-upgrades wiki
|
|
77
|
+
at https://wiki.debian.org/UnattendedUpgrades.
|
|
78
|
+
|
|
79
|
+
1. SSH key-only. `PasswordAuthentication no`.
|
|
80
|
+
2. Root disabled. `PermitRootLogin no`.
|
|
81
|
+
3. UFW default-deny incoming. `ufw allow 22/tcp 80/tcp 443/tcp`.
|
|
82
|
+
4. DOCKER-USER iptables chain. Drops non-conforming egress from
|
|
83
|
+
containers before Docker rules apply.
|
|
84
|
+
5. fail2ban SSH jail on port 22. `maxretry 5`, `bantime 3600`.
|
|
85
|
+
6. `unattended-upgrades` on for security-only updates
|
|
86
|
+
(`Unattended-Upgrade::Automatic-Reboot "false"`).
|
|
87
|
+
|
|
88
|
+
Deviation costs an ADR at the applying project level; the shipped
|
|
89
|
+
lint refuses on drift.
|
|
90
|
+
|
|
91
|
+
## When to reach for hcloud vs raw-api
|
|
92
|
+
|
|
93
|
+
- `hcloud` (default). Choose it when the operator laptop and CI
|
|
94
|
+
runner can install the binary. Small, self-documenting, JSON
|
|
95
|
+
output on `--output json`; the vendor updates it in step with the
|
|
96
|
+
API. Docs: https://github.com/hetznercloud/cli.
|
|
97
|
+
- `raw-api`. Choose it when a container base image cannot install
|
|
98
|
+
`hcloud` (Alpine minimal builds, a locked-down runtime). The
|
|
99
|
+
provisioner reaches the REST endpoints directly per
|
|
100
|
+
https://docs.hetzner.cloud/. Vendor SLAs cover the API; the local
|
|
101
|
+
CLI is a convenience layer.
|
|
102
|
+
|
|
103
|
+
The facade shape is identical on both paths so switching is a one
|
|
104
|
+
elicit change.
|
|
105
|
+
|
|
106
|
+
## When to reach for `cx23` vs `cx33`
|
|
107
|
+
|
|
108
|
+
- `cx23` (default). Two vCPU, 4 GB RAM, 40 GB SSD. Approximately
|
|
109
|
+
EUR 4.15/month. Fine for the docker-compose stack that ops-01
|
|
110
|
+
runs (fewer than ten containers, each below 300 MB RSS).
|
|
111
|
+
- `cx33`. Two vCPU, 8 GB RAM, 80 GB SSD. Approximately EUR
|
|
112
|
+
7.99/month. Reach when the compose stack holds a Postgres, a
|
|
113
|
+
Grafana, or any container that regularly touches 1 GB RSS.
|
|
114
|
+
- `cx43`. Four vCPU, 16 GB RAM. Reach for a home for the
|
|
115
|
+
`platform-cloudflare-durable-objects` self-hosted mode or a
|
|
116
|
+
Postgres + Grafana + Loki triad.
|
|
117
|
+
- `cpx*`. AMD EPYC. Faster per vCPU; slightly more expensive.
|
|
118
|
+
- `cax*`. ARM64. Choose when every runtime component has an ARM
|
|
119
|
+
build (Node, Postgres, cloudflared, most caddy modules do).
|
|
120
|
+
|
|
121
|
+
Vendor pricing:
|
|
122
|
+
https://docs.hetzner.com/cloud/servers/overview.
|
|
123
|
+
|
|
124
|
+
## Snapshot cadence choices (ADR-3802)
|
|
125
|
+
|
|
126
|
+
- `weekly` (default). One snapshot every Sunday at 02:00 local. On
|
|
127
|
+
a `cx23`, adds approximately EUR 0.005/month in snapshot storage.
|
|
128
|
+
- `daily`. Higher recovery-point objective. On a `cx23`, adds
|
|
129
|
+
approximately EUR 0.035/month.
|
|
130
|
+
- `off`. No automatic snapshot. Choose only when Hetzner-managed
|
|
131
|
+
backups are opted-in AND the application layer takes its own
|
|
132
|
+
logical backups on cadence.
|
|
133
|
+
|
|
134
|
+
Hetzner-managed backups are a separately elicited paid opt-in at
|
|
135
|
+
approximately 20 percent of monthly server price (on a `cx23`,
|
|
136
|
+
approximately EUR 0.80/month). They compose with the scheduled
|
|
137
|
+
snapshot runner; both fire independently.
|
|
138
|
+
Vendor pricing:
|
|
139
|
+
https://docs.hetzner.com/cloud/servers/backups-snapshots/overview.
|
|
140
|
+
|
|
141
|
+
## Snapshot-on-demand
|
|
142
|
+
|
|
143
|
+
Fire a snapshot manually from the applying project's CLI:
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
node ./scripts/snapshot.mjs --server ops-01
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The script imports the provisioner facade, calls `takeSnapshot`,
|
|
150
|
+
and emits `hetznerSnapshotTaken` on the injected sink. The account-
|
|
151
|
+
bound probe `real-account-snapshot-on-demand` covers the same shape.
|
|
152
|
+
|
|
153
|
+
## The shared throwaway-server fixture
|
|
154
|
+
|
|
155
|
+
Every real-account probe runs against a throwaway `cx23` in `fsn1`
|
|
156
|
+
provisioned from the fixture manifest. Fixture location:
|
|
157
|
+
`packages/rcf-lite/test/fixtures/hetzner-throwaway-server/`. Cost
|
|
158
|
+
ceiling: EUR 0.006/hour per run; destroy runs in `always()`; nightly
|
|
159
|
+
`sweep-orphans` cron collects any leak on a 60-minute label-age
|
|
160
|
+
cutoff.
|
|
161
|
+
|
|
162
|
+
Round-7 T-2 (`platform-docker-compose-host`) and T-3
|
|
163
|
+
(`edge-cloudflare-tunnel`) EXTEND this fixture rather than shipping
|
|
164
|
+
a second copy. T-2 mounts the docker + compose verbs on the same
|
|
165
|
+
throwaway server after cloud-init seals the baseline; T-3 mounts
|
|
166
|
+
`cloudflared` on the runtime T-2 stands up.
|
|
167
|
+
|
|
168
|
+
## Standards trace
|
|
169
|
+
|
|
170
|
+
Every URL fetched 200 in the ratifying pass before the T-1 PR.
|
|
171
|
+
|
|
172
|
+
- Hetzner Cloud API https://docs.hetzner.cloud/
|
|
173
|
+
- Hetzner Cloud CLI https://github.com/hetznercloud/cli
|
|
174
|
+
- Hetzner Cloud servers overview https://docs.hetzner.com/cloud/servers/overview
|
|
175
|
+
- Hetzner Cloud firewalls overview https://docs.hetzner.com/cloud/firewalls/overview/
|
|
176
|
+
- Hetzner Cloud backups-and-snapshots overview https://docs.hetzner.com/cloud/servers/backups-snapshots/overview
|
|
177
|
+
- Hetzner Cloud networks overview https://docs.hetzner.com/cloud/networks/overview/
|
|
178
|
+
- Hetzner community basic-cloud-config tutorial https://community.hetzner.com/tutorials/basic-cloud-config
|
|
179
|
+
- cloud-init https://cloudinit.readthedocs.io/en/latest/
|
|
180
|
+
- Debian unattended-upgrades wiki https://wiki.debian.org/UnattendedUpgrades
|
|
@@ -46,7 +46,7 @@ This table is maintained shelf-wide across every blueprint's `docs/topics.md`. R
|
|
|
46
46
|
| application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
|
|
47
47
|
| application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
|
|
48
48
|
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
49
|
-
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.
|
|
49
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
|
|
50
50
|
| messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
|
|
51
51
|
| jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
|
|
52
52
|
| platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
|
|
@@ -55,6 +55,9 @@ This table is maintained shelf-wide across every blueprint's `docs/topics.md`. R
|
|
|
55
55
|
| edge-cloudflare-access | 34101-34899 | 35xx | shipped v1.0.0 | `edgeAuthenticationGate` |
|
|
56
56
|
| edge-cloudflare-turnstile | 35101-35899 | 36xx | shipped v1.0.0 | `humanVerificationGate` |
|
|
57
57
|
| edge-cloudflare-rate-limiting | 36101-36899 | 37xx | shipped v1.0.0 | `edgeThrottleContract` |
|
|
58
|
+
| deploy-hetzner-server | 37101-37899 | 38xx | shipped v1.0.0 | `linuxCloudHostContract`, `snapshotAndBackupCadence` |
|
|
59
|
+
| platform-docker-compose-host | 38101-38899 | 39xx | shipped v1.0.0 | `containerHostContract` |
|
|
60
|
+
| edge-cloudflare-tunnel | 39101-39899 | 40xx | shipped v1.0.0 | `edgeIngressBridge` |
|
|
58
61
|
|
|
59
62
|
US 34101-34109 sit at the LOW end of the 34101-34899 band on purpose (watchpost run-4 lesson). ADR/TAC suffixes for this blueprint use the 3501-3599 block, continuing the shelf pattern.
|
|
60
63
|
|
|
@@ -5,6 +5,9 @@ This file is the edge-cloudflare-rate-limiting half of the cross-blueprint contr
|
|
|
5
5
|
## Global ADR topics this blueprint contributes (exact strings)
|
|
6
6
|
|
|
7
7
|
| Topic string | edge-cloudflare-rate-limiting contribution | Origin | Composition note |
|
|
8
|
+
| deploy-hetzner-server | 37101-37899 | 38xx | shipped v1.0.0 | `linuxCloudHostContract`, `snapshotAndBackupCadence` |
|
|
9
|
+
| platform-docker-compose-host | 38101-38899 | 39xx | shipped v1.0.0 | `containerHostContract` |
|
|
10
|
+
| edge-cloudflare-tunnel | 39101-39899 | 40xx | shipped v1.0.0 | `edgeIngressBridge` |
|
|
8
11
|
|---|---|---|---|
|
|
9
12
|
| `edgeThrottleContract` | ADR-3701-edge-cloudflare-rate-limiting-throttle-contract | Minted by this blueprint. Names the wire-shape contract for the project-wide edge throttle; the shipped answer is Cloudflare WAF rate-limiting rules per `https://developers.cloudflare.com/waf/rate-limiting-rules/`. | The one project-wide decision on the edge throttle at the shipped-shape boundary. A future non-Cloudflare sibling (Fastly rate-limits, Vercel Edge Config throttles) would contribute the same topic string with a different answer, forcing a DELIBERATE conflict the operator resolves with a project-level ADR. |
|
|
10
13
|
|
|
@@ -46,7 +49,7 @@ This table is maintained shelf-wide across every blueprint's `docs/topics.md`. R
|
|
|
46
49
|
| application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
|
|
47
50
|
| application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
|
|
48
51
|
| persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
|
|
49
|
-
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.
|
|
52
|
+
| object-storage-s3 | 28101-28899 | 29xx | shipped v1.1.0 | `objectStorageContract` |
|
|
50
53
|
| messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
|
|
51
54
|
| jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
|
|
52
55
|
| platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# edge-cloudflare-tunnel CHANGELOG
|
|
2
|
+
|
|
3
|
+
## 1.0.1 - 2026-09-08
|
|
4
|
+
|
|
5
|
+
H-2 hardening train (`h2-cf-platform-probe-integrity`): probe-integrity patch. No capability change.
|
|
6
|
+
|
|
7
|
+
- Replaced the warn-on-env-set stub in `real-account-connector-healthy.mjs` with a real cloudflared connector-health driver via the new cf-edge shim (`packages/rcf-lite/test/fixtures/cf-edge/h2-cf-tunnel-shim.mjs`). With both `CI_HAS_CLOUDFLARE_ACCOUNT` and `CI_HAS_HETZNER_ACCOUNT` set, the driver provisions the throwaway server through the hetzner fixture surface (H-1 territory, consumed as-is), runs `cloudflared tunnel info <name>` via the shim, asserts healthy connector count, captures the `tunnelConnectorUp` event, and always tears the server down. Env-absent branch keeps pass-with-skip. Env-present-with-fixture-failure yields FAIL with a pointer to the fixture step, never warn.
|
|
8
|
+
- Replaced both warn-on-env-set stubs in `real-account-tunnel-hostname-routes.mjs` with real drivers. Public-hostname sub-case: undici fetch against the scratch subdomain via the shim's `fetchTunnelHostname` seam, activated on `CI_HAS_CLOUDFLARE_ACCOUNT`. Access-gated sub-case: two-identity HS256 JWT check, activated only when `CI_HAS_CLOUDFLARE_ACCESS` is ALSO set (amendment 4). Env-absent branch keeps pass-with-skip. A pass is no longer reachable from `CI_HAS_CLOUDFLARE_ACCOUNT` presence alone.
|
|
9
|
+
- Converted the credentials-placeholder-read-failed branch in `manifest-schema-validate.mjs::eventSecrecyScan` from warn to fail with a pointer to the exact fixture path (`packages/rcf-lite/test/fixtures/hetzner-throwaway-server/cloudflared/*/credentials/probe.json.example`) and the setup requirement (must exist and parse as JSON).
|
|
10
|
+
- Added the cf-edge tunnel shim (`packages/rcf-lite/test/fixtures/cf-edge/h2-cf-tunnel-shim.mjs`) with five seams: `provisionScratchServer`, `destroyScratchServer`, `cloudflaredTunnelInfo`, `fetchTunnelHostname`, `mintScratchIdentity`. The shim reads `H2_CF_TUNNEL_SHIM_MODE` and `H2_CF_TUNNEL_HOSTNAME_SHIM_MODE` for synthetic-mode branches; the probe bodies hold zero `SIMULATE_` token references.
|
|
11
|
+
- Tunnel symbolic-anchor ruling (task C4): the `AC-tunnel-*` symbolic ids on the five tunnel probes ARE the shipped AC ids in the `US-39101..US-39108` contribution band, so no re-anchor was performed. Full mapping in the H-2 PR body.
|
|
12
|
+
|
|
13
|
+
## 1.0.0 - 2026-09-08
|
|
14
|
+
|
|
15
|
+
Initial release. Round-7 T-3 of the Hetzner spec at `projects/blueprint-library/specs/hetzner-round-7-spec-2026-09-07.md`.
|
|
16
|
+
|
|
17
|
+
- Mints capability `tunnelBridge` and global topic `edgeIngressBridge`.
|
|
18
|
+
- Five REQs, eight USs, three TACs, three ADRs; five Node-only probes.
|
|
19
|
+
- Extends the shared throwaway-Hetzner-server fixture with a cloudflared connector in both runtime shapes (compose-service alongside the T-2 web and caddy services when `containerHost` is applied; systemd-unit for a bare `cloudHost`) and both hostname modes (access-gated when `zeroTrustGate` is applied; public-hostname when absent) plus five `run-<probe>.mjs` delegate shims.
|
|
20
|
+
- manifest-schema-validate carries five fixture-side mutation switches (`SIMULATE_MANIFEST_INVALID_TUNNEL_ID`, `SIMULATE_MANIFEST_CREDENTIALS_INLINE`, `SIMULATE_MANIFEST_MISSING_CATCHALL`, `SIMULATE_ORIGIN_PORT_OPEN`, `SIMULATE_EVENT_SECRECY_LEAK`); cloudflared-config-lint carries `SIMULATE_INGRESS_INVALID` and runs `cloudflared tunnel ingress validate` via the `cloudflare/cloudflared:2026.8.3` container when a local binary is not on PATH; aud-presence-check carries `SIMULATE_AUD_DROP`.
|
|
21
|
+
- Mutation-discipline (T-2 gate ruling carried forward): every `SIMULATE_*` switch lives in the fixture-side delegate shim; the probe modules read only `RCF_LITE_T3_FIXTURE_ROOT` and never branch on being under mutation.
|
|
22
|
+
- Connector-runtime choice elicited via `connector-runtime` (compose-service default when `containerHost` applied; systemd-unit alternative for bare `cloudHost`, per the vendor cloudflared as-a-service local-configuration-file docs); hostname-mode discovered (not elicited) from `appliedCapabilities` via the sidecar per ADR-4003.
|
|
23
|
+
- Consumers: `w-2026-09-06-dave-010` follow-on (make the librarian API and workspace viewer reachable without Mullvad) is the estate consumer; blueprint acceptance never depends on that consumer, and the ops-01 migration mints as its own work item.
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# edge-cloudflare-tunnel
|
|
2
|
+
|
|
3
|
+
Round-7 T-3 core blueprint on the rcf-lite shelf. Bridges a service on a
|
|
4
|
+
`cloudHost` (bare, systemd shape) or a `containerHost` (compose-service
|
|
5
|
+
shape) to the Cloudflare edge through a Cloudflare Tunnel, with no
|
|
6
|
+
public origin ports. When applied alongside `edge-cloudflare-access`
|
|
7
|
+
(via the `zeroTrustGate` capability) the tunnel binds an Access AUD;
|
|
8
|
+
when applied without it the tunnel serves in public-hostname mode.
|
|
9
|
+
|
|
10
|
+
- Slug: `edge-cloudflare-tunnel`
|
|
11
|
+
- Version: `1.0.0`
|
|
12
|
+
- Category: `edge`
|
|
13
|
+
- Capabilities: `["tunnelBridge"]`
|
|
14
|
+
- Mints global topic: `edgeIngressBridge`
|
|
15
|
+
- Contribution count: 5 REQs, 8 USs, 3 TACs, 3 ADRs, 5 probes
|
|
16
|
+
- Consumers: ops-01 has no consumer today (SSH is the only inbound path). Estate consumer is the follow-on make-the-librarian-API-and-workspace-viewer-reachable-without-Mullvad work that `w-2026-09-06-dave-010` is expected to green-light. Blueprint acceptance never depends on that consumer; the ops-01 migration mints as a separate work item at that point (`w-2026-09-07-dave-004`).
|
|
17
|
+
|
|
18
|
+
## Five REQs
|
|
19
|
+
|
|
20
|
+
| Id | Contract |
|
|
21
|
+
|---|---|
|
|
22
|
+
| edge-cloudflare-tunnel-REQ-001 | Connector install shape switches on applied `containerHost` (compose-service default; systemd-unit for bare `cloudHost`); `cloudflaredReady` fires on boot with connector id, region and version metadata. |
|
|
23
|
+
| edge-cloudflare-tunnel-REQ-002 | Tunnel manifest schema at `cloudflare/tunnels/<name>.yaml` (uuid tunnel id, `credentialsFile.secretRef` reference, ingress rules with catch-all `http_status:404`). |
|
|
24
|
+
| edge-cloudflare-tunnel-REQ-003 | No public origin ports on the host; T-1 firewall stays closed on 80/443 unless a reverse proxy is applied; manifest refuses ingress rules whose service URL binds to a host public interface. |
|
|
25
|
+
| edge-cloudflare-tunnel-REQ-004 | Access-gated composition: `zeroTrustGate` applied attaches `originRequest.access.aud`; absent omits the block and serves in public-hostname mode. |
|
|
26
|
+
| edge-cloudflare-tunnel-REQ-005 | Credentials-file discipline (host mode 0o400, referenced via `secretRef`, not committed, grep-refuse, no leak in event bodies). |
|
|
27
|
+
|
|
28
|
+
## Connector runtime shapes
|
|
29
|
+
|
|
30
|
+
The connector runtime is elicited via `connector-runtime` (ADR-4002) with
|
|
31
|
+
`recommendedDefault: compose-service`.
|
|
32
|
+
|
|
33
|
+
- **compose-service** (default when `containerHost` is applied). The
|
|
34
|
+
cloudflared container ships alongside the applying project services on
|
|
35
|
+
the same web-net network. No host ports; restart `unless-stopped`;
|
|
36
|
+
logging driver aligned with the T-2 compose-host log-driver elicit.
|
|
37
|
+
Fixture: `packages/rcf-lite/test/fixtures/hetzner-throwaway-server/cloudflared/compose-service/`.
|
|
38
|
+
- **systemd-unit** (alternative for a bare `cloudHost`). The cloudflared
|
|
39
|
+
binary is installed via the vendor package per the cloudflared
|
|
40
|
+
as-a-service documented systemd unit and runs as a systemd service
|
|
41
|
+
reading the local configuration file. Fixture:
|
|
42
|
+
`packages/rcf-lite/test/fixtures/hetzner-throwaway-server/cloudflared/systemd-unit/`.
|
|
43
|
+
|
|
44
|
+
When to reach for which: pick compose-service when the project already
|
|
45
|
+
runs docker compose on the host (natural home, same log driver, same
|
|
46
|
+
restart discipline); pick systemd-unit when the host runs bare and there
|
|
47
|
+
is no compose stack to add a service to (or when the project deliberately
|
|
48
|
+
keeps cloudflared outside the compose lifecycle for boot ordering).
|
|
49
|
+
|
|
50
|
+
## Tunnel manifest shape
|
|
51
|
+
|
|
52
|
+
The manifest lives at `cloudflare/tunnels/<name>.yaml` under the
|
|
53
|
+
applying project. The shipped JSON schema
|
|
54
|
+
(`contributions/probes/tunnel-manifest.schema.json`) requires:
|
|
55
|
+
|
|
56
|
+
- `tunnel`: uuid-shaped Cloudflare Tunnel id.
|
|
57
|
+
- `credentialsFile.secretRef`: reference into `security-secrets-management`
|
|
58
|
+
(never a literal path or JSON body inline).
|
|
59
|
+
- `ingress`: array of `{ hostname?, service, originRequest? }`. The last
|
|
60
|
+
entry MUST be a catch-all `service: http_status:404`. When the
|
|
61
|
+
Access-gated shape is applied every non-catch-all rule attaches
|
|
62
|
+
`originRequest.access.aud` with the AUD from the sidecar.
|
|
63
|
+
|
|
64
|
+
Example (compose-service, public-hostname mode):
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
tunnel: 00000000-0000-4000-8000-0000000012ab
|
|
68
|
+
credentialsFile:
|
|
69
|
+
secretRef: hq-estate/CLOUDFLARE_TUNNEL_CREDENTIALS_MY_PROJECT
|
|
70
|
+
ingress:
|
|
71
|
+
- hostname: my-app.example.com
|
|
72
|
+
service: http://web:8080
|
|
73
|
+
- service: http_status:404
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Example (Access-gated shape adds one block per non-catch-all rule):
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
ingress:
|
|
80
|
+
- hostname: my-app.example.com
|
|
81
|
+
service: http://web:8080
|
|
82
|
+
originRequest:
|
|
83
|
+
access:
|
|
84
|
+
aud: AUD-example-cloudflare-team
|
|
85
|
+
teamName: my-team
|
|
86
|
+
required: true
|
|
87
|
+
- service: http_status:404
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Credentials-file discipline
|
|
91
|
+
|
|
92
|
+
The tunnel credentials JSON file lands on the host at mode 0o400 (owner
|
|
93
|
+
read only). The manifest references it as a `credentialsFile.secretRef`;
|
|
94
|
+
the fixture placeholder at
|
|
95
|
+
`packages/rcf-lite/test/fixtures/hetzner-throwaway-server/cloudflared/*/credentials/probe.json.example`
|
|
96
|
+
documents the pattern. The credentials file MUST NOT be committed to
|
|
97
|
+
the applying project tree; a grep across the working tree for the
|
|
98
|
+
tunnel id and account tag literals refuses on any tracked file. Every
|
|
99
|
+
`cloudflaredReady`, `tunnelConnectorUp` and `ingressRuleReloaded` event
|
|
100
|
+
body carries metadata only; the event-secrecy scan refuses on a body
|
|
101
|
+
that mentions the credentials literal.
|
|
102
|
+
|
|
103
|
+
Rotate credentials by rewriting the secret pointed to by `secretRef`
|
|
104
|
+
and re-applying; the connector reloads on the next boot cycle.
|
|
105
|
+
|
|
106
|
+
## Composition point (Access-gated vs public-hostname)
|
|
107
|
+
|
|
108
|
+
The hostname mode is discovered from the applied capability set (not
|
|
109
|
+
elicited; see ADR-4003). The blueprint reads the sidecar at
|
|
110
|
+
`rcf/blueprints/edge-cloudflare-tunnel.applied.json` and inspects
|
|
111
|
+
`appliedCapabilities`.
|
|
112
|
+
|
|
113
|
+
- When `zeroTrustGate` is present (i.e. `edge-cloudflare-access` v1.0.0
|
|
114
|
+
is applied on the same project), every ingress rule attaches
|
|
115
|
+
`originRequest.access.aud` and cloudflared refuses at the edge any
|
|
116
|
+
request without a valid Access JWT for that AUD.
|
|
117
|
+
- When `zeroTrustGate` is absent, the block is omitted and the tunnel
|
|
118
|
+
serves in public-hostname mode (open ingress on the public hostname;
|
|
119
|
+
authorization is whatever the origin service itself carries).
|
|
120
|
+
|
|
121
|
+
The `aud-presence-check` probe refuses when the gated variant drifts to
|
|
122
|
+
omit the AUD block (defensive against silent degradation).
|
|
123
|
+
|
|
124
|
+
Graceful degrade: when Access is not applied (T-4 absent from
|
|
125
|
+
`origin/main` or the applying project simply does not want the gate),
|
|
126
|
+
the blueprint still ships in public-hostname mode. The probe assertion
|
|
127
|
+
runs on both fixture variants so the shape flip is proven on every
|
|
128
|
+
reviewer boot.
|
|
129
|
+
|
|
130
|
+
## No-public-ports rule
|
|
131
|
+
|
|
132
|
+
The T-1 firewall on a `cloudHost` allows 22 unconditionally and 80/443
|
|
133
|
+
only when a reverse proxy is applied. A project applying only T-1 plus
|
|
134
|
+
T-3 (no reverse proxy) declares zero 80/443 rules; the tunnel is the
|
|
135
|
+
sole path from the edge to the origin. The manifest schema refuses
|
|
136
|
+
ingress rules whose service URL binds to `0.0.0.0`, a non-loopback
|
|
137
|
+
numeric IP, or an unresolvable shape.
|
|
138
|
+
|
|
139
|
+
## Elicited parameters
|
|
140
|
+
|
|
141
|
+
- `tunnel-name` (string; default `probe`): the Cloudflare Tunnel name
|
|
142
|
+
and the manifest filename under `cloudflare/tunnels/<name>.yaml`.
|
|
143
|
+
- `cloudflare-zone-secret` (string; default `hq-estate/CLOUDFLARE_ZONE_ID`):
|
|
144
|
+
`security-secrets-management` reference to the Cloudflare zone the
|
|
145
|
+
tunnel serves under.
|
|
146
|
+
- `public-hostname-template` (string; default `<service>.<zone>`):
|
|
147
|
+
template for the public hostname, expanded per ingress rule.
|
|
148
|
+
- `ingress-rules` (string; default `[]`): ingress rules array (hostname
|
|
149
|
+
+ service URL pairs).
|
|
150
|
+
- `access-aud` (string; default empty): Access AUD tag for the gated
|
|
151
|
+
shape. Presence toggles the gated shape when `zeroTrustGate` is
|
|
152
|
+
applied.
|
|
153
|
+
- `connector-runtime` (enum: `compose-service`, `systemd-unit`; default
|
|
154
|
+
`compose-service`): the connector runtime per ADR-4002.
|
|
155
|
+
|
|
156
|
+
## Companions
|
|
157
|
+
|
|
158
|
+
- `suggestedCompanions[logging]`: every `cloudflaredReady`,
|
|
159
|
+
`tunnelConnectorUp`, `tunnelConnectorDown`, `ingressRuleReloaded` and
|
|
160
|
+
`accessGateRefused` event writes through the applied logger.
|
|
161
|
+
- `suggestedCompanions[errorHandling]`: a cloudflared exit, a JWT-check
|
|
162
|
+
failure, an ingress-rule schema violation, a hostname-DNS-route API
|
|
163
|
+
refusal constructs an internal error record.
|
|
164
|
+
|
|
165
|
+
`providesRoles: []`.
|
|
166
|
+
|
|
167
|
+
## Five probes
|
|
168
|
+
|
|
169
|
+
Every probe writes its report envelope to
|
|
170
|
+
`.rcf/reports/blueprints/edge-cloudflare-tunnel/<probe-name>.json`
|
|
171
|
+
under the repo root.
|
|
172
|
+
|
|
173
|
+
- **manifest-schema-validate** (local). Anchors AC-tunnel-manifestSchema,
|
|
174
|
+
AC-tunnel-noPublicOrigin, AC-tunnel-credentialsDiscipline. `accountBound: false`.
|
|
175
|
+
Ajv-free walker over the shipped JSON schema; runs on all four
|
|
176
|
+
variants; refuses on `SIMULATE_MANIFEST_INVALID_TUNNEL_ID`,
|
|
177
|
+
`SIMULATE_MANIFEST_CREDENTIALS_INLINE`, `SIMULATE_MANIFEST_MISSING_CATCHALL`,
|
|
178
|
+
`SIMULATE_ORIGIN_PORT_OPEN`, `SIMULATE_EVENT_SECRECY_LEAK`.
|
|
179
|
+
- **cloudflared-config-lint** (local). Anchors AC-tunnel-hostnameRoutes.
|
|
180
|
+
`accountBound: false`. Runs `cloudflared tunnel --config <path> ingress validate`
|
|
181
|
+
via a local binary or the vendor container image
|
|
182
|
+
`cloudflare/cloudflared:2026.8.3`; refuses on `SIMULATE_INGRESS_INVALID`.
|
|
183
|
+
- **aud-presence-check** (local). Anchors AC-tunnel-accessGated,
|
|
184
|
+
AC-tunnel-accessGatedRefuse. `accountBound: false`. Reads the sidecar
|
|
185
|
+
and the paired manifests; asserts the shape flips correctly; refuses
|
|
186
|
+
on `SIMULATE_AUD_DROP` when the gated fixture drifts to open ingress.
|
|
187
|
+
- **real-account-connector-healthy** (account-bound). Anchors
|
|
188
|
+
AC-tunnel-connectorHealthy. `accountBound: true` on
|
|
189
|
+
`CI_HAS_CLOUDFLARE_ACCOUNT` AND `CI_HAS_HETZNER_ACCOUNT`. Without
|
|
190
|
+
both, records `accountBoundSkipped: true` and the aggregate flips to
|
|
191
|
+
`pass`.
|
|
192
|
+
- **real-account-tunnel-hostname-routes** (account-bound). Anchors
|
|
193
|
+
AC-tunnel-hostnameRoutes (public sub-case) and AC-tunnel-accessGated
|
|
194
|
+
(gated sub-case). `accountBound: true`. The public-hostname sub-case
|
|
195
|
+
runs on `CI_HAS_CLOUDFLARE_ACCOUNT` + `CI_HAS_HETZNER_ACCOUNT`; the
|
|
196
|
+
access-gated sub-case adds `CI_HAS_CLOUDFLARE_ACCESS` per the round-7
|
|
197
|
+
Q4 ratification. Sub-cases skip independently.
|
|
198
|
+
|
|
199
|
+
The delegate shims at
|
|
200
|
+
`packages/rcf-lite/test/fixtures/hetzner-throwaway-server/run-<probe>.mjs`
|
|
201
|
+
read the `SIMULATE_*` env vars and prepare mutated inputs in a scratch
|
|
202
|
+
dir; the probe modules never read a `SIMULATE_` env var and never
|
|
203
|
+
branch on being under mutation.
|
|
204
|
+
|
|
205
|
+
## Two fixture variants times two hostname modes
|
|
206
|
+
|
|
207
|
+
The fixture lives under
|
|
208
|
+
`packages/rcf-lite/test/fixtures/hetzner-throwaway-server/cloudflared/`:
|
|
209
|
+
|
|
210
|
+
- `compose-service/compose-fragment.yaml`: the cloudflared compose
|
|
211
|
+
service alongside the T-2 web and caddy services.
|
|
212
|
+
- `systemd-unit/cloudflared.service`: the systemd unit per the vendor
|
|
213
|
+
as-a-service docs.
|
|
214
|
+
- `<runtime>/cloudflare/tunnels/public-hostname.yaml`: manifest without
|
|
215
|
+
the AUD block.
|
|
216
|
+
- `<runtime>/cloudflare/tunnels/access-gated.yaml`: manifest with the
|
|
217
|
+
AUD block on every non-catch-all rule.
|
|
218
|
+
- `<runtime>/credentials/probe.json.example`: placeholder credentials
|
|
219
|
+
documenting the 0o400 mode discipline.
|
|
220
|
+
- `sidecars/access-gated.applied.json`: `appliedCapabilities` includes
|
|
221
|
+
`zeroTrustGate`.
|
|
222
|
+
- `sidecars/public-hostname.applied.json`: `appliedCapabilities`
|
|
223
|
+
excludes `zeroTrustGate`.
|
|
224
|
+
|
|
225
|
+
Reviewer boot (mocked, no account) from the fixture directory:
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
cd packages/rcf-lite/test/fixtures/hetzner-throwaway-server
|
|
229
|
+
node ./run-tunnel-manifest-schema-validate.mjs && node ./run-cloudflared-config-lint.mjs && node ./run-aud-presence-check.mjs
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Reviewer boot (real account, throwaway server): set
|
|
233
|
+
`CI_HAS_CLOUDFLARE_ACCOUNT=true` and `CI_HAS_HETZNER_ACCOUNT=true`
|
|
234
|
+
(and `CI_HAS_CLOUDFLARE_ACCESS=true` for the gated sub-case) with the
|
|
235
|
+
account tokens loaded via `security-secrets-management`, then invoke the
|
|
236
|
+
two `run-real-account-*.mjs` scripts.
|
|
237
|
+
|
|
238
|
+
## Composition on the shelf
|
|
239
|
+
|
|
240
|
+
Consumes:
|
|
241
|
+
- `platform-docker-compose-host` v1.0.0 (`containerHost` capability) for
|
|
242
|
+
the compose-service runtime path.
|
|
243
|
+
- `deploy-hetzner-server` v1.0.0 (`cloudHost` capability) for the bare
|
|
244
|
+
systemd-unit runtime path.
|
|
245
|
+
- `security-secrets-management` v1.0.1 (`secretsProvider` capability) for
|
|
246
|
+
the tunnel credentials `secretRef`.
|
|
247
|
+
- Optionally `edge-cloudflare-access` v1.0.0 (`zeroTrustGate` capability)
|
|
248
|
+
for the Access-gated shape.
|
|
249
|
+
|
|
250
|
+
Provides:
|
|
251
|
+
- `tunnelBridge` (declared under `capabilities` on `blueprint.json`).
|
|
252
|
+
- New global topic `edgeIngressBridge` on ADR-4001 (scope global).
|
|
253
|
+
|
|
254
|
+
Conflict-by-design with future non-Cloudflare siblings on the
|
|
255
|
+
`edgeIngressBridge` topic: Tailscale Funnel, Fly.io proxy, etc.
|
|
256
|
+
would contribute the same topic string with a different mechanism.
|
|
257
|
+
Round 7 ships only the Cloudflare Tunnel provider.
|