rcf-lite 0.24.0 → 0.25.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.
Files changed (232) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/blueprints/application-account-settings/docs/topics.md +3 -0
  3. package/blueprints/application-admin-console/docs/topics.md +3 -0
  4. package/blueprints/application-api-rest/docs/topics.md +3 -0
  5. package/blueprints/application-charts/docs/topics.md +3 -0
  6. package/blueprints/application-dashboard/docs/topics.md +3 -0
  7. package/blueprints/application-datatable/docs/topics.md +3 -0
  8. package/blueprints/application-empty-error-states/docs/topics.md +3 -0
  9. package/blueprints/application-error-handling/docs/topics.md +3 -0
  10. package/blueprints/application-file-upload/docs/topics.md +3 -0
  11. package/blueprints/application-forms-wizard/docs/topics.md +3 -0
  12. package/blueprints/application-notifications-in-app/docs/topics.md +3 -0
  13. package/blueprints/application-onboarding-tour/docs/topics.md +3 -0
  14. package/blueprints/application-spa/docs/topics.md +3 -0
  15. package/blueprints/delivery-ci-workflows/docs/topics.md +3 -0
  16. package/blueprints/deploy-cloudflare-workers/docs/topics.md +3 -0
  17. package/blueprints/deploy-hetzner-server/CHANGELOG.md +34 -0
  18. package/blueprints/deploy-hetzner-server/README.md +202 -0
  19. package/blueprints/deploy-hetzner-server/blueprint.json +233 -0
  20. package/blueprints/deploy-hetzner-server/contributions/adrs/adr-3801-deploy-hetzner-server-cloud-host-contract.json +14 -0
  21. package/blueprints/deploy-hetzner-server/contributions/adrs/adr-3802-deploy-hetzner-server-snapshot-cadence.json +14 -0
  22. package/blueprints/deploy-hetzner-server/contributions/adrs/adr-3803-deploy-hetzner-server-provisioning-tool.json +20 -0
  23. package/blueprints/deploy-hetzner-server/contributions/adrs/adr-3804-deploy-hetzner-server-hardening-baseline.json +14 -0
  24. package/blueprints/deploy-hetzner-server/contributions/probes/cloud-init-render-lint.mjs +64 -0
  25. package/blueprints/deploy-hetzner-server/contributions/probes/hcloud-dry-run-mock.mjs +116 -0
  26. package/blueprints/deploy-hetzner-server/contributions/probes/manifest-schema-validate.mjs +180 -0
  27. package/blueprints/deploy-hetzner-server/contributions/probes/probe-utils.mjs +106 -0
  28. package/blueprints/deploy-hetzner-server/contributions/probes/real-account-cloud-init-hardened.mjs +63 -0
  29. package/blueprints/deploy-hetzner-server/contributions/probes/real-account-snapshot-on-demand.mjs +60 -0
  30. package/blueprints/deploy-hetzner-server/contributions/probes/real-account-throwaway-server-provision.mjs +58 -0
  31. package/blueprints/deploy-hetzner-server/contributions/probes/run-cloud-init-render-lint.mjs +9 -0
  32. package/blueprints/deploy-hetzner-server/contributions/probes/run-hcloud-dry-run-mock.mjs +9 -0
  33. package/blueprints/deploy-hetzner-server/contributions/probes/run-manifest-schema-validate.mjs +9 -0
  34. package/blueprints/deploy-hetzner-server/contributions/probes/run-real-account-cloud-init-hardened.mjs +9 -0
  35. package/blueprints/deploy-hetzner-server/contributions/probes/run-real-account-snapshot-on-demand.mjs +9 -0
  36. package/blueprints/deploy-hetzner-server/contributions/probes/run-real-account-throwaway-server-provision.mjs +9 -0
  37. package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-001.json +18 -0
  38. package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-002.json +18 -0
  39. package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-003.json +18 -0
  40. package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-004.json +18 -0
  41. package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-005.json +18 -0
  42. package/blueprints/deploy-hetzner-server/contributions/requirements/deploy-hetzner-server-req-006.json +18 -0
  43. package/blueprints/deploy-hetzner-server/contributions/schemas/hetzner-server.schema.json +108 -0
  44. package/blueprints/deploy-hetzner-server/contributions/tacs/tac-3801-deploy-hetzner-server-provisioner.json +27 -0
  45. package/blueprints/deploy-hetzner-server/contributions/tacs/tac-3802-deploy-hetzner-server-manifest-schema.json +26 -0
  46. package/blueprints/deploy-hetzner-server/contributions/tacs/tac-3803-deploy-hetzner-server-cloud-init-template.json +26 -0
  47. package/blueprints/deploy-hetzner-server/contributions/tacs/tac-3804-deploy-hetzner-server-firewall-shape.json +25 -0
  48. package/blueprints/deploy-hetzner-server/contributions/templates/cloud-init.yaml.tmpl +93 -0
  49. package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37101.json +24 -0
  50. package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37102.json +24 -0
  51. package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37103.json +25 -0
  52. package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37104.json +24 -0
  53. package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37105.json +24 -0
  54. package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37106.json +25 -0
  55. package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37107.json +24 -0
  56. package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37108.json +24 -0
  57. package/blueprints/deploy-hetzner-server/contributions/user-stories/deploy-hetzner-server-us-37109.json +24 -0
  58. package/blueprints/deploy-hetzner-server/docs/topics.md +48 -0
  59. package/blueprints/deploy-hetzner-server/guide/deploy-hetzner-server.md +180 -0
  60. package/blueprints/edge-cloudflare-access/docs/topics.md +3 -0
  61. package/blueprints/edge-cloudflare-rate-limiting/docs/topics.md +3 -0
  62. package/blueprints/edge-cloudflare-tunnel/CHANGELOG.md +13 -0
  63. package/blueprints/edge-cloudflare-tunnel/README.md +257 -0
  64. package/blueprints/edge-cloudflare-tunnel/blueprint.json +168 -0
  65. package/blueprints/edge-cloudflare-tunnel/contributions/adrs/adr-4001-edge-cloudflare-tunnel-ingress-bridge-contract.json +23 -0
  66. package/blueprints/edge-cloudflare-tunnel/contributions/adrs/adr-4002-edge-cloudflare-tunnel-connector-runtime.json +19 -0
  67. package/blueprints/edge-cloudflare-tunnel/contributions/adrs/adr-4003-edge-cloudflare-tunnel-hostname-mode.json +19 -0
  68. package/blueprints/edge-cloudflare-tunnel/contributions/probes/aud-presence-check.mjs +114 -0
  69. package/blueprints/edge-cloudflare-tunnel/contributions/probes/cloudflared-config-lint.mjs +80 -0
  70. package/blueprints/edge-cloudflare-tunnel/contributions/probes/manifest-schema-validate.mjs +234 -0
  71. package/blueprints/edge-cloudflare-tunnel/contributions/probes/probe-utils.mjs +231 -0
  72. package/blueprints/edge-cloudflare-tunnel/contributions/probes/real-account-connector-healthy.mjs +54 -0
  73. package/blueprints/edge-cloudflare-tunnel/contributions/probes/real-account-tunnel-hostname-routes.mjs +73 -0
  74. package/blueprints/edge-cloudflare-tunnel/contributions/probes/run-aud-presence-check.mjs +4 -0
  75. package/blueprints/edge-cloudflare-tunnel/contributions/probes/run-cloudflared-config-lint.mjs +4 -0
  76. package/blueprints/edge-cloudflare-tunnel/contributions/probes/run-manifest-schema-validate.mjs +4 -0
  77. package/blueprints/edge-cloudflare-tunnel/contributions/probes/run-real-account-connector-healthy.mjs +4 -0
  78. package/blueprints/edge-cloudflare-tunnel/contributions/probes/run-real-account-tunnel-hostname-routes.mjs +4 -0
  79. package/blueprints/edge-cloudflare-tunnel/contributions/probes/tunnel-manifest.schema.json +55 -0
  80. package/blueprints/edge-cloudflare-tunnel/contributions/requirements/edge-cloudflare-tunnel-req-001.json +18 -0
  81. package/blueprints/edge-cloudflare-tunnel/contributions/requirements/edge-cloudflare-tunnel-req-002.json +18 -0
  82. package/blueprints/edge-cloudflare-tunnel/contributions/requirements/edge-cloudflare-tunnel-req-003.json +18 -0
  83. package/blueprints/edge-cloudflare-tunnel/contributions/requirements/edge-cloudflare-tunnel-req-004.json +18 -0
  84. package/blueprints/edge-cloudflare-tunnel/contributions/requirements/edge-cloudflare-tunnel-req-005.json +18 -0
  85. package/blueprints/edge-cloudflare-tunnel/contributions/tacs/tac-4001-edge-cloudflare-tunnel-connector-shape.json +26 -0
  86. package/blueprints/edge-cloudflare-tunnel/contributions/tacs/tac-4002-edge-cloudflare-tunnel-manifest-schema.json +26 -0
  87. package/blueprints/edge-cloudflare-tunnel/contributions/tacs/tac-4003-edge-cloudflare-tunnel-access-binding.json +25 -0
  88. package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39101.json +24 -0
  89. package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39102.json +24 -0
  90. package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39103.json +24 -0
  91. package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39104.json +24 -0
  92. package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39105.json +24 -0
  93. package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39106.json +24 -0
  94. package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39107.json +24 -0
  95. package/blueprints/edge-cloudflare-tunnel/contributions/user-stories/edge-cloudflare-tunnel-us-39108.json +24 -0
  96. package/blueprints/edge-cloudflare-tunnel/docs/topics.md +72 -0
  97. package/blueprints/edge-cloudflare-tunnel/guide/edge-cloudflare-tunnel.md +204 -0
  98. package/blueprints/edge-cloudflare-turnstile/docs/topics.md +3 -0
  99. package/blueprints/email-smtp-resend/docs/topics.md +3 -0
  100. package/blueprints/jobs-background/docs/topics.md +3 -0
  101. package/blueprints/messaging-queue-cloudflare/docs/topics.md +3 -0
  102. package/blueprints/object-storage-s3/docs/topics.md +3 -0
  103. package/blueprints/observability-essentials/docs/topics.md +3 -0
  104. package/blueprints/observability-logging/docs/topics.md +3 -0
  105. package/blueprints/observability-probe-endpoints/docs/topics.md +3 -0
  106. package/blueprints/persistence-data-d1/docs/topics.md +3 -0
  107. package/blueprints/persistence-data-postgres/docs/topics.md +3 -0
  108. package/blueprints/persistence-data-sqlite/docs/topics.md +3 -0
  109. package/blueprints/platform-cloudflare-cron-triggers/docs/topics.md +3 -0
  110. package/blueprints/platform-cloudflare-durable-objects/docs/topics.md +3 -0
  111. package/blueprints/platform-cloudflare-kv/docs/topics.md +3 -0
  112. package/blueprints/platform-docker-compose-host/CHANGELOG.md +12 -0
  113. package/blueprints/platform-docker-compose-host/README.md +76 -0
  114. package/blueprints/platform-docker-compose-host/blueprint.json +209 -0
  115. package/blueprints/platform-docker-compose-host/contributions/adrs/adr-3901-platform-docker-compose-host-contract.json +14 -0
  116. package/blueprints/platform-docker-compose-host/contributions/adrs/adr-3902-platform-docker-compose-host-reverse-proxy.json +14 -0
  117. package/blueprints/platform-docker-compose-host/contributions/adrs/adr-3903-platform-docker-compose-host-reject-coolify.json +14 -0
  118. package/blueprints/platform-docker-compose-host/contributions/adrs/adr-3904-platform-docker-compose-host-log-driver.json +14 -0
  119. package/blueprints/platform-docker-compose-host/contributions/probes/caddyfile-validate.mjs +108 -0
  120. package/blueprints/platform-docker-compose-host/contributions/probes/compose-config-lint.mjs +231 -0
  121. package/blueprints/platform-docker-compose-host/contributions/probes/probe-utils.mjs +185 -0
  122. package/blueprints/platform-docker-compose-host/contributions/probes/real-account-minimal-stack-up.mjs +41 -0
  123. package/blueprints/platform-docker-compose-host/contributions/probes/real-account-reload-burst.mjs +36 -0
  124. package/blueprints/platform-docker-compose-host/contributions/probes/run-caddyfile-validate.mjs +5 -0
  125. package/blueprints/platform-docker-compose-host/contributions/probes/run-compose-config-lint.mjs +5 -0
  126. package/blueprints/platform-docker-compose-host/contributions/probes/run-real-account-minimal-stack-up.mjs +5 -0
  127. package/blueprints/platform-docker-compose-host/contributions/probes/run-real-account-reload-burst.mjs +5 -0
  128. package/blueprints/platform-docker-compose-host/contributions/probes/run-secrets-as-files-scan.mjs +5 -0
  129. package/blueprints/platform-docker-compose-host/contributions/probes/secrets-as-files-scan.mjs +116 -0
  130. package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-001.json +18 -0
  131. package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-002.json +18 -0
  132. package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-003.json +18 -0
  133. package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-004.json +18 -0
  134. package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-005.json +18 -0
  135. package/blueprints/platform-docker-compose-host/contributions/requirements/platform-docker-compose-host-req-006.json +18 -0
  136. package/blueprints/platform-docker-compose-host/contributions/tacs/tac-3901-platform-docker-compose-host-compose-layout.json +26 -0
  137. package/blueprints/platform-docker-compose-host/contributions/tacs/tac-3902-platform-docker-compose-host-secrets-mount.json +26 -0
  138. package/blueprints/platform-docker-compose-host/contributions/tacs/tac-3903-platform-docker-compose-host-healthcheck-lint.json +26 -0
  139. package/blueprints/platform-docker-compose-host/contributions/tacs/tac-3904-platform-docker-compose-host-reverse-proxy-artefact.json +26 -0
  140. package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38101.json +24 -0
  141. package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38102.json +24 -0
  142. package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38103.json +24 -0
  143. package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38104.json +24 -0
  144. package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38105.json +25 -0
  145. package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38106.json +24 -0
  146. package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38107.json +24 -0
  147. package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38108.json +24 -0
  148. package/blueprints/platform-docker-compose-host/contributions/user-stories/platform-docker-compose-host-us-38109.json +24 -0
  149. package/blueprints/platform-docker-compose-host/docs/topics.md +71 -0
  150. package/blueprints/platform-docker-compose-host/guide/platform-docker-compose-host.md +94 -0
  151. package/blueprints/security-auth-clerk/docs/topics.md +3 -0
  152. package/blueprints/security-auth-keycloak/docs/topics.md +3 -0
  153. package/blueprints/security-auth-magic-link/docs/topics.md +3 -0
  154. package/blueprints/security-auth-oauth2/docs/topics.md +3 -0
  155. package/blueprints/security-secrets-management/docs/topics.md +3 -0
  156. package/fixtures/canary-manifest.json +9 -9
  157. package/package.json +1 -1
  158. package/rcf/adrs/adr-3801-deploy-hetzner-server-cloud-host-contract.json +13 -0
  159. package/rcf/adrs/adr-3802-deploy-hetzner-server-snapshot-cadence.json +13 -0
  160. package/rcf/adrs/adr-3803-deploy-hetzner-server-provisioning-tool.json +13 -0
  161. package/rcf/adrs/adr-3804-deploy-hetzner-server-hardening-baseline.json +13 -0
  162. package/rcf/adrs/adr-3901-platform-docker-compose-host-contract.json +13 -0
  163. package/rcf/adrs/adr-3902-platform-docker-compose-host-reverse-proxy.json +13 -0
  164. package/rcf/adrs/adr-3903-platform-docker-compose-host-reject-coolify.json +13 -0
  165. package/rcf/adrs/adr-3904-platform-docker-compose-host-log-driver.json +13 -0
  166. package/rcf/adrs/adr-4001-edge-cloudflare-tunnel-ingress-bridge-contract.json +13 -0
  167. package/rcf/adrs/adr-4002-edge-cloudflare-tunnel-connector-runtime.json +13 -0
  168. package/rcf/adrs/adr-4003-edge-cloudflare-tunnel-hostname-mode.json +13 -0
  169. package/rcf/code-nodes/cn-410.json +20 -0
  170. package/rcf/code-nodes/cn-440.json +20 -0
  171. package/rcf/code-nodes/cn-470.json +19 -0
  172. package/rcf/fbs/fbs-130.json +23 -0
  173. package/rcf/fbs/fbs-140.json +23 -0
  174. package/rcf/fbs/fbs-150.json +22 -0
  175. package/rcf/requirements/req-110.json +58 -0
  176. package/rcf/requirements/req-111.json +32 -0
  177. package/rcf/requirements/req-112.json +42 -0
  178. package/rcf/requirements/req-113.json +33 -0
  179. package/rcf/requirements/req-114.json +20 -0
  180. package/rcf/requirements/req-115.json +38 -0
  181. package/rcf/requirements/req-120.json +20 -0
  182. package/rcf/requirements/req-121.json +20 -0
  183. package/rcf/requirements/req-122.json +20 -0
  184. package/rcf/requirements/req-123.json +42 -0
  185. package/rcf/requirements/req-124.json +27 -0
  186. package/rcf/requirements/req-125.json +20 -0
  187. package/rcf/requirements/req-130.json +32 -0
  188. package/rcf/requirements/req-131.json +54 -0
  189. package/rcf/requirements/req-132.json +27 -0
  190. package/rcf/requirements/req-133.json +20 -0
  191. package/rcf/requirements/req-134.json +42 -0
  192. package/rcf/tacs/tac-3801-deploy-hetzner-server-provisioner.json +17 -0
  193. package/rcf/tacs/tac-3802-deploy-hetzner-server-manifest-schema.json +17 -0
  194. package/rcf/tacs/tac-3803-deploy-hetzner-server-cloud-init-template.json +17 -0
  195. package/rcf/tacs/tac-3804-deploy-hetzner-server-firewall-shape.json +16 -0
  196. package/rcf/tacs/tac-3901-platform-docker-compose-host-compose-layout.json +17 -0
  197. package/rcf/tacs/tac-3902-platform-docker-compose-host-secrets-mount.json +17 -0
  198. package/rcf/tacs/tac-3903-platform-docker-compose-host-healthcheck-lint.json +17 -0
  199. package/rcf/tacs/tac-3904-platform-docker-compose-host-reverse-proxy-artefact.json +17 -0
  200. package/rcf/tacs/tac-4001-edge-cloudflare-tunnel-connector-shape.json +17 -0
  201. package/rcf/tacs/tac-4002-edge-cloudflare-tunnel-manifest-schema.json +17 -0
  202. package/rcf/tacs/tac-4003-edge-cloudflare-tunnel-access-binding.json +16 -0
  203. package/rcf/test-suites/ts-140.json +86 -0
  204. package/rcf/test-suites/ts-150.json +86 -0
  205. package/rcf/test-suites/ts-160.json +78 -0
  206. package/rcf/user-stories/us-11001.json +23 -0
  207. package/rcf/user-stories/us-11101.json +23 -0
  208. package/rcf/user-stories/us-11102.json +24 -0
  209. package/rcf/user-stories/us-11201.json +23 -0
  210. package/rcf/user-stories/us-11202.json +23 -0
  211. package/rcf/user-stories/us-11301.json +24 -0
  212. package/rcf/user-stories/us-11401.json +23 -0
  213. package/rcf/user-stories/us-11402.json +23 -0
  214. package/rcf/user-stories/us-11501.json +23 -0
  215. package/rcf/user-stories/us-12001.json +23 -0
  216. package/rcf/user-stories/us-12101.json +23 -0
  217. package/rcf/user-stories/us-12102.json +23 -0
  218. package/rcf/user-stories/us-12201.json +23 -0
  219. package/rcf/user-stories/us-12202.json +24 -0
  220. package/rcf/user-stories/us-12301.json +23 -0
  221. package/rcf/user-stories/us-12401.json +23 -0
  222. package/rcf/user-stories/us-12402.json +23 -0
  223. package/rcf/user-stories/us-12501.json +23 -0
  224. package/rcf/user-stories/us-13001.json +23 -0
  225. package/rcf/user-stories/us-13002.json +23 -0
  226. package/rcf/user-stories/us-13101.json +23 -0
  227. package/rcf/user-stories/us-13102.json +23 -0
  228. package/rcf/user-stories/us-13201.json +23 -0
  229. package/rcf/user-stories/us-13301.json +23 -0
  230. package/rcf/user-stories/us-13302.json +23 -0
  231. package/rcf/user-stories/us-13401.json +23 -0
  232. package/releases/releases.yaml +11 -1
@@ -0,0 +1,26 @@
1
+ {
2
+ "tacId": "TAC-3803-deploy-hetzner-server-cloud-init-template",
3
+ "prdId": "PRD-001",
4
+ "tadId": "TAD-001",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "name": "Cloud-init template and render lint",
8
+ "purpose": "One cloud-init user-data template that renders YAML landing the six hardening baseline blocks per ADR-3804 on every provisioned Hetzner server: SSH key-only, root disabled, UFW default-deny incoming, DOCKER-USER iptables chain, fail2ban SSH jail, unattended-upgrades. A local render-lint probe asserts every block appears in the rendered YAML; the SIMULATE_HARDENING_DRIFT mutation removes the unattended-upgrades block and the probe FAILS naming the missing line.",
9
+ "responsibilities": [
10
+ "Ship a cloud-init template at contributions/templates/cloud-init.yaml.tmpl that renders the six baseline blocks (AC-37104-1).",
11
+ "Render deterministically against a manifest via the fixture's cloud-init-renderer.mjs (AC-37104-1).",
12
+ "Cover the SIMULATE_HARDENING_DRIFT mutation with a probe FAILURE that names the missing line (AC-37104-1).",
13
+ "Document the runtime ssh baseline check that the account-bound probe drives against the throwaway server (AC-37105-1)."
14
+ ],
15
+ "internalStructure": "One template file at blueprints/deploy-hetzner-server/contributions/templates/cloud-init.yaml.tmpl using {{token}} placeholders substituted by the renderer; one renderer module at packages/rcf-lite/test/fixtures/hetzner-throwaway-server/src/cloud-init-renderer.mjs; one probe cloud-init-render-lint.mjs asserts baseline lines.",
16
+ "interfaces": [
17
+ {
18
+ "name": "cloud-init.yaml.tmpl",
19
+ "kind": "template",
20
+ "summary": "Cloud-init user-data template with the six baseline blocks and templated substitutions."
21
+ }
22
+ ],
23
+ "dependencies": [],
24
+ "createdAt": "2026-09-08T00:00:00.000Z",
25
+ "updatedAt": "2026-09-08T00:00:00.000Z"
26
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "tacId": "TAC-3804-deploy-hetzner-server-firewall-shape",
3
+ "prdId": "PRD-001",
4
+ "tadId": "TAD-001",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "name": "Hetzner Cloud firewall rule shape",
8
+ "purpose": "One canonical firewall rule shape carried inside the server manifest. Rule set: 22 permitted only from a nonempty list of source-prefix ranges (the operator-nominated set; the schema refuses on 0.0.0.0/0 in this list); 80 and 443 permitted from 0.0.0.0/0 for the reverse proxy; all other inbound denied. The provisioner applies the rule set to the server at provision time via hcloud firewall create + apply-to-resource (or the raw-api equivalent).",
9
+ "responsibilities": [
10
+ "Define the firewall rule shape as a sub-schema in hetzner-server.schema.json (AC-37106-1).",
11
+ "Refuse via manifest-schema-validate on a manifest that opens 22 to 0.0.0.0/0 or drops 80/443 (AC-37106-1).",
12
+ "Enforce that the ssh rule's sourceIps is a nonempty array (AC-37106-1)."
13
+ ],
14
+ "internalStructure": "Rule shape lives inside the same hetzner-server.schema.json used by TAC-3802 (properties.firewallRules array items with protocol, port, direction, sourceIps). The applyFirewall verb on the provisioner facade consumes the parsed rule set.",
15
+ "interfaces": [
16
+ {
17
+ "name": "firewallRules sub-schema",
18
+ "kind": "jsonSchemaFragment",
19
+ "summary": "Rule-shape invariants inside hetzner-server.schema.json."
20
+ }
21
+ ],
22
+ "dependencies": [],
23
+ "createdAt": "2026-09-08T00:00:00.000Z",
24
+ "updatedAt": "2026-09-08T00:00:00.000Z"
25
+ }
@@ -0,0 +1,93 @@
1
+ #cloud-config
2
+ # deploy-hetzner-server v1.0.0 cloud-init user-data template.
3
+ # Six-block hardening baseline per ADR-3804 (Debian unattended-upgrades wiki
4
+ # and the estate baseline at
5
+ # operator/knowledge/systems/environments/hetzner-cloud/overview.md
6
+ # Security posture baseline). Placeholders in {{double-brace}} form are
7
+ # substituted by the fixture's cloud-init-renderer.mjs against the applying
8
+ # manifest.
9
+
10
+ hostname: {{name}}
11
+
12
+ # Block 1: SSH key-only. Password auth disabled at the sshd level too.
13
+ users:
14
+ - name: deploy
15
+ groups: [sudo]
16
+ shell: /bin/bash
17
+ lock_passwd: true
18
+ ssh_authorized_keys:
19
+ {{sshAuthorizedKeysBlock}}
20
+
21
+ # Block 2: root disabled (PermitRootLogin no).
22
+ # Block 1 continued: PasswordAuthentication no.
23
+ write_files:
24
+ - path: /etc/ssh/sshd_config.d/hardening.conf
25
+ permissions: '0644'
26
+ content: |
27
+ # deploy-hetzner-server v1.0.0 hardening (baseline blocks 1 and 2).
28
+ PermitRootLogin no
29
+ PasswordAuthentication no
30
+ KbdInteractiveAuthentication no
31
+ ChallengeResponseAuthentication no
32
+ # Block 4: DOCKER-USER iptables chain drops non-conforming egress.
33
+ - path: /etc/iptables/rules.v4.d/docker-user.rules
34
+ permissions: '0644'
35
+ content: |
36
+ # deploy-hetzner-server v1.0.0 hardening baseline block 4:
37
+ # DOCKER-USER iptables chain wired to drop non-conforming egress.
38
+ *filter
39
+ :DOCKER-USER - [0:0]
40
+ -A DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
41
+ -A DOCKER-USER -j DROP
42
+ COMMIT
43
+ # Block 5: fail2ban SSH jail.
44
+ - path: /etc/fail2ban/jail.d/sshd.conf
45
+ permissions: '0644'
46
+ content: |
47
+ # deploy-hetzner-server v1.0.0 hardening baseline block 5:
48
+ # fail2ban SSH jail on port 22.
49
+ [sshd]
50
+ enabled = true
51
+ port = 22
52
+ maxretry = 5
53
+ findtime = 600
54
+ bantime = 3600
55
+ # Block 6: unattended-upgrades on (security updates).
56
+ - path: /etc/apt/apt.conf.d/50unattended-upgrades
57
+ permissions: '0644'
58
+ content: |
59
+ // deploy-hetzner-server v1.0.0 hardening baseline block 6:
60
+ // unattended-upgrades on for security updates
61
+ // per https://wiki.debian.org/UnattendedUpgrades
62
+ Unattended-Upgrade::Origins-Pattern {
63
+ "origin=Debian,codename=${distro_codename},label=Debian-Security";
64
+ "origin=Ubuntu,archive=${distro_codename}-security";
65
+ };
66
+ Unattended-Upgrade::Automatic-Reboot "false";
67
+ - path: /etc/apt/apt.conf.d/20auto-upgrades
68
+ permissions: '0644'
69
+ content: |
70
+ APT::Periodic::Update-Package-Lists "1";
71
+ APT::Periodic::Unattended-Upgrade "1";
72
+
73
+ # Package install: ufw for block 3, fail2ban for block 5,
74
+ # unattended-upgrades for block 6, iptables-persistent for block 4.
75
+ packages:
76
+ - ufw
77
+ - fail2ban
78
+ - unattended-upgrades
79
+ - iptables-persistent
80
+
81
+ runcmd:
82
+ # Block 3: UFW default-deny incoming, allow 22 80 443.
83
+ - ufw default deny incoming
84
+ - ufw default allow outgoing
85
+ - ufw allow 22/tcp
86
+ - ufw allow 80/tcp
87
+ - ufw allow 443/tcp
88
+ - ufw --force enable
89
+ - systemctl reload ssh || systemctl restart ssh
90
+ - systemctl enable --now fail2ban
91
+ - systemctl enable --now unattended-upgrades
92
+ # Load the DOCKER-USER iptables rules on boot.
93
+ - iptables-restore -n < /etc/iptables/rules.v4.d/docker-user.rules
@@ -0,0 +1,24 @@
1
+ {
2
+ "usId": "deploy-hetzner-server-US-37101",
3
+ "prdId": "PRD-001",
4
+ "reqId": "deploy-hetzner-server-REQ-001",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "title": "The provisioner opens on boot as the sole token reader and emits provisionerReady",
8
+ "asA": "developer whose project provisions a Hetzner Cloud VM as its cloudHost",
9
+ "iWant": "one provisioner facade module to be the sole reader of HETZNER_ACCOUNT_API_KEY and to emit provisionerReady on boot with a metadata-only payload",
10
+ "soThat": "the token has one auditable seam and the observability sink surfaces boot ordering without ever carrying the secret",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-37101-1",
14
+ "description": "On process boot the provisioner facade opens against the token, and (a) only the provisioner module reads HETZNER_ACCOUNT_API_KEY (grep across the fixture source refuses any other reader), (b) provisionerReady fires on the injected event sink with a payload of exactly {tool, apiHost}.",
15
+ "testable": true,
16
+ "scope": "runtime"
17
+ }
18
+ ],
19
+ "tacIds": [
20
+ "TAC-3801-deploy-hetzner-server-provisioner"
21
+ ],
22
+ "createdAt": "2026-09-08T00:00:00.000Z",
23
+ "updatedAt": "2026-09-08T00:00:00.000Z"
24
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "usId": "deploy-hetzner-server-US-37102",
3
+ "prdId": "PRD-001",
4
+ "reqId": "deploy-hetzner-server-REQ-002",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "title": "hetzner/servers/name.json manifests validate against the shipped JSON Schema",
8
+ "asA": "developer whose project holds Hetzner server declarations in git",
9
+ "iWant": "every hetzner/servers/name.json file to validate against a shipped draft-07 JSON Schema naming type, location, image, sshKeyIds, firewallId, networkId, cloudInitPath, labels, firewallRules and snapshotCadence",
10
+ "soThat": "the operator has a stable declarative source of truth and manifest defects surface locally before they reach the API",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-37102-1",
14
+ "description": "manifest-schema-validate.mjs asserts every hetzner/servers/*.json under the shared throwaway-server fixture validates against blueprints/deploy-hetzner-server/contributions/schemas/hetzner-server.schema.json and carries every required field; a mutation SIMULATE_MANIFEST_INVALID=true replaces location with an unknown token and the probe FAILS naming the offending field.",
15
+ "testable": true,
16
+ "scope": "runtime"
17
+ }
18
+ ],
19
+ "tacIds": [
20
+ "TAC-3802-deploy-hetzner-server-manifest-schema"
21
+ ],
22
+ "createdAt": "2026-09-08T00:00:00.000Z",
23
+ "updatedAt": "2026-09-08T00:00:00.000Z"
24
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "usId": "deploy-hetzner-server-US-37103",
3
+ "prdId": "PRD-001",
4
+ "reqId": "deploy-hetzner-server-REQ-002",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "title": "Manifest applies: the provisioner creates a matching live server and fires hetznerServerProvisioned",
8
+ "asA": "developer whose project provisions a Hetzner Cloud VM from git-managed manifests",
9
+ "iWant": "applying the provisioner to read each manifest under hetzner/servers/, create a matching live Hetzner Cloud server, and emit hetznerServerProvisioned with id, primaryIpv4, location and serverType",
10
+ "soThat": "the manifest is the source of truth for what actually exists in the project",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-37103-1",
14
+ "description": "The real-account-throwaway-server-provision probe (accountBound: true, skipped without CI_HAS_HETZNER_ACCOUNT) applies the ci-throwaway.json manifest via provision.mjs, asserts (a) hcloud server list --output json shows the new server whose id is written to the fixture scratch file, (b) hetznerServerProvisioned fired with id, primaryIpv4, location=fsn1, serverType=cx23; teardown via destroy.mjs runs regardless of probe verdict.",
15
+ "testable": true,
16
+ "scope": "runtime"
17
+ }
18
+ ],
19
+ "tacIds": [
20
+ "TAC-3801-deploy-hetzner-server-provisioner",
21
+ "TAC-3802-deploy-hetzner-server-manifest-schema"
22
+ ],
23
+ "createdAt": "2026-09-08T00:00:00.000Z",
24
+ "updatedAt": "2026-09-08T00:00:00.000Z"
25
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "usId": "deploy-hetzner-server-US-37104",
3
+ "prdId": "PRD-001",
4
+ "reqId": "deploy-hetzner-server-REQ-003",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "title": "Cloud-init template renders the six hardening baseline blocks",
8
+ "asA": "developer whose project provisions Hetzner servers that face the public internet",
9
+ "iWant": "the shipped cloud-init template to render YAML that lands SSH key-only, root disabled, UFW default-deny incoming, DOCKER-USER iptables chain, fail2ban SSH jail and unattended-upgrades on every server",
10
+ "soThat": "I inherit the six-block hardening baseline declaratively without hand-authoring per host",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-37104-1",
14
+ "description": "cloud-init-render-lint.mjs renders the template with the fixture manifest and asserts each of the six baseline blocks (SSH key-only, root disabled, UFW default-deny incoming, DOCKER-USER iptables chain, fail2ban SSH jail, unattended-upgrades) appears in the rendered YAML by grep; a mutation SIMULATE_HARDENING_DRIFT=true removes the unattended-upgrades block and the probe FAILS with a message naming the missing line.",
15
+ "testable": true,
16
+ "scope": "runtime"
17
+ }
18
+ ],
19
+ "tacIds": [
20
+ "TAC-3803-deploy-hetzner-server-cloud-init-template"
21
+ ],
22
+ "createdAt": "2026-09-08T00:00:00.000Z",
23
+ "updatedAt": "2026-09-08T00:00:00.000Z"
24
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "usId": "deploy-hetzner-server-US-37105",
3
+ "prdId": "PRD-001",
4
+ "reqId": "deploy-hetzner-server-REQ-003",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "title": "Provisioned server is hardened at runtime as the rendered cloud-init promised",
8
+ "asA": "operator running Hetzner servers provisioned by this blueprint",
9
+ "iWant": "the running server to refuse root ssh, refuse password auth, run UFW default-deny incoming, hold the DOCKER-USER chain and run unattended-upgrades",
10
+ "soThat": "I can trust the cloud-init contract without ssh-in-and-check on every server",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-37105-1",
14
+ "description": "real-account-cloud-init-hardened.mjs (accountBound: true, skipped without CI_HAS_HETZNER_ACCOUNT) waits for cloud-init status --wait on the throwaway server, then ssh-checks the six baseline blocks and asserts each check passes; failure names the missing block. Teardown via destroy.mjs runs regardless.",
15
+ "testable": true,
16
+ "scope": "runtime"
17
+ }
18
+ ],
19
+ "tacIds": [
20
+ "TAC-3803-deploy-hetzner-server-cloud-init-template"
21
+ ],
22
+ "createdAt": "2026-09-08T00:00:00.000Z",
23
+ "updatedAt": "2026-09-08T00:00:00.000Z"
24
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "usId": "deploy-hetzner-server-US-37106",
3
+ "prdId": "PRD-001",
4
+ "reqId": "deploy-hetzner-server-REQ-004",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "title": "Firewall rule shape restricts SSH to nominated prefixes, permits 80/443, denies all else",
8
+ "asA": "operator running Hetzner servers that face the public internet",
9
+ "iWant": "the firewall rule set carried in hetzner/servers/name.json to permit 22 only from an operator-nominated set of source-prefix ranges, permit 80 and 443 from any source for the reverse proxy, and deny all other inbound; the provisioner applies the rules at provision time",
10
+ "soThat": "I inherit a documented least-privilege firewall shape on every server without opening ssh to the world",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-37106-1",
14
+ "description": "manifest-schema-validate.mjs asserts every fixture manifest carries a firewallRules array matching the shape (22 permitted only from a nonempty list of source-prefix ranges with no 0.0.0.0/0; 80 and 443 permitted from 0.0.0.0/0; no other inbound rule); an induced defect (a manifest that opens 22 to 0.0.0.0/0) FAILS schema validation with a message naming the offending rule.",
15
+ "testable": true,
16
+ "scope": "runtime"
17
+ }
18
+ ],
19
+ "tacIds": [
20
+ "TAC-3802-deploy-hetzner-server-manifest-schema",
21
+ "TAC-3804-deploy-hetzner-server-firewall-shape"
22
+ ],
23
+ "createdAt": "2026-09-08T00:00:00.000Z",
24
+ "updatedAt": "2026-09-08T00:00:00.000Z"
25
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "usId": "deploy-hetzner-server-US-37107",
3
+ "prdId": "PRD-001",
4
+ "reqId": "deploy-hetzner-server-REQ-005",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "title": "Snapshot cadence is elicited (weekly default, daily and off alternatives) and validated on the manifest",
8
+ "asA": "operator whose project takes recovery-point snapshots on Hetzner Cloud",
9
+ "iWant": "the elicited snapshot cadence value on hetzner/servers/name.json to match the shipped enum {weekly, daily, off}, with weekly the recommended default per ADR-3802",
10
+ "soThat": "I have one project-wide answer for how often each server snapshots",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-37107-1",
14
+ "description": "manifest-schema-validate.mjs asserts every fixture manifest carries snapshotCadence with a value in the shipped enum {weekly, daily, off}; a manifest missing the field FAILS schema validation naming the missing field.",
15
+ "testable": true,
16
+ "scope": "runtime"
17
+ }
18
+ ],
19
+ "tacIds": [
20
+ "TAC-3802-deploy-hetzner-server-manifest-schema"
21
+ ],
22
+ "createdAt": "2026-09-08T00:00:00.000Z",
23
+ "updatedAt": "2026-09-08T00:00:00.000Z"
24
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "usId": "deploy-hetzner-server-US-37108",
3
+ "prdId": "PRD-001",
4
+ "reqId": "deploy-hetzner-server-REQ-005",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "title": "Snapshot-on-demand fires a tagged snapshot and emits hetznerSnapshotTaken",
8
+ "asA": "operator whose project needs an on-demand recovery point at any moment",
9
+ "iWant": "the snapshot-on-demand verb on the provisioner to shell to hcloud image create-image (or the raw-api equivalent) and tag the snapshot with server name and wall-clock time; hetznerSnapshotTaken carries snapshot id and time",
10
+ "soThat": "I have a documented on-demand recovery point at any moment without a manual dashboard visit",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-37108-1",
14
+ "description": "real-account-snapshot-on-demand.mjs (accountBound: true, skipped without CI_HAS_HETZNER_ACCOUNT) fires the snapshot verb on the throwaway server, then asserts hcloud image list --type=snapshot --output json shows a snapshot tagged with the server name and current wall-clock time; hetznerSnapshotTaken event carries snapshot id and time. Teardown via destroy.mjs.",
15
+ "testable": true,
16
+ "scope": "runtime"
17
+ }
18
+ ],
19
+ "tacIds": [
20
+ "TAC-3801-deploy-hetzner-server-provisioner"
21
+ ],
22
+ "createdAt": "2026-09-08T00:00:00.000Z",
23
+ "updatedAt": "2026-09-08T00:00:00.000Z"
24
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "usId": "deploy-hetzner-server-US-37109",
3
+ "prdId": "PRD-001",
4
+ "reqId": "deploy-hetzner-server-REQ-006",
5
+ "version": "1.0.0",
6
+ "status": "approved",
7
+ "title": "Four lifecycle events fire with metadata-only payloads and no event body carries a secret",
8
+ "asA": "operator whose observability sink carries the Hetzner lifecycle trail",
9
+ "iWant": "the provisioner facade to fire the four lifecycle events (provisionerReady, hetznerServerProvisioned, hetznerSnapshotTaken, hetznerServerDestroyed) with metadata-only payloads and no secret content",
10
+ "soThat": "the observability sink carries the operational trail without ever leaking a token or an ssh key",
11
+ "acceptanceCriteria": [
12
+ {
13
+ "id": "AC-37109-1",
14
+ "description": "hcloud-dry-run-mock.mjs drives every lifecycle event through the facade against a mocked hcloud shim; the accumulated event stream carries only the metadata fields named on REQ-006; a mutation SIMULATE_EVENT_SECRECY_LEAK=true injects the token into the hetznerServerProvisioned payload and the probe FAILS with a defensive-fake finding naming the leaked field.",
15
+ "testable": true,
16
+ "scope": "runtime"
17
+ }
18
+ ],
19
+ "tacIds": [
20
+ "TAC-3801-deploy-hetzner-server-provisioner"
21
+ ],
22
+ "createdAt": "2026-09-08T00:00:00.000Z",
23
+ "updatedAt": "2026-09-08T00:00:00.000Z"
24
+ }
@@ -0,0 +1,48 @@
1
+ # deploy-hetzner-server topics
2
+
3
+ Shelf registry of global ADR topics and shelf-band anchors relevant
4
+ to `deploy-hetzner-server` v1.0.0.
5
+
6
+ ## Global topics minted by this blueprint
7
+
8
+ | Topic | ADR | Meaning |
9
+ |---|---|---|
10
+ | `linuxCloudHostContract` | `ADR-3801` | The Hetzner Cloud VM as the shipped `cloudHost` shape. A future non-Hetzner sibling (Vultr, DigitalOcean, Fly.io) contributes the same topic string with a different provider and forces a deliberate conflict per the round-2 pattern. |
11
+ | `snapshotAndBackupCadence` | `ADR-3802` | Weekly snapshot as the shipped default, daily and off alternatives, Hetzner-managed backups as an elicited paid opt-in at approximately 20 percent of monthly server price. |
12
+
13
+ ## Shelf id band and suffix block
14
+
15
+ | Blueprint | US band | ADR/TAC suffix block | Status | Global topics |
16
+ |---|---|---|---|---|
17
+ | application-datatable | 17101-17899 | 18xx | shipped v1.0.0 | none |
18
+ | application-charts | 18101-18899 | 19xx | shipped v1.0.0 | none |
19
+ | application-dashboard | 19101-19899 | 20xx | shipped v1.0.0 | none |
20
+ | application-notifications-in-app | 20101-20899 | 21xx | shipped v1.0.0 | none |
21
+ | application-admin-console | 21101-21899 | 22xx | shipped v1.1.0 | `adminConsoleSignInSurface` |
22
+ | application-empty-error-states | 22101-22899 | 23xx | shipped v1.0.0 | none |
23
+ | application-file-upload | 23101-23899 | 24xx | shipped v1.0.0 | none |
24
+ | application-forms-wizard | 24101-24899 | 25xx | shipped v1.0.0 | none |
25
+ | application-account-settings | 25101-25899 | 26xx | shipped v1.0.0 | none |
26
+ | application-onboarding-tour | 26101-26899 | 27xx | shipped v1.0.0 | none |
27
+ | persistence-data-postgres | 27101-27899 | 28xx | shipped v1.0.0 | `persistenceStore`, `migrationDiscipline` |
28
+ | object-storage-s3 | 28101-28899 | 29xx | shipped v1.0.0 | `objectStorageContract` |
29
+ | messaging-queue-cloudflare | 29101-29899 | 30xx | shipped v1.0.0 | `deliverySemantics` |
30
+ | jobs-background | 30101-30899 | 31xx | shipped v1.0.0 | `backgroundJobModel` |
31
+ | platform-cloudflare-kv | 31101-31899 | 32xx | shipped v1.0.0 | `keyValueStoreContract` |
32
+ | platform-cloudflare-cron-triggers | 32101-32899 | 33xx | shipped v1.0.0 | `scheduledTriggerContract` |
33
+ | platform-cloudflare-durable-objects | 33101-33899 | 34xx | shipped v1.0.0 | `strongConsistencyCellContract`, `websocketHubContract` |
34
+ | edge-cloudflare-access | 34101-34899 | 35xx | shipped v1.0.0 | `edgeAuthenticationGate` |
35
+ | edge-cloudflare-turnstile | 35101-35899 | 36xx | shipped v1.0.0 | `humanVerificationGate` |
36
+ | edge-cloudflare-rate-limiting | 36101-36899 | 37xx | shipped v1.0.0 | `edgeThrottleContract` |
37
+ | deploy-hetzner-server | 37101-37899 | 38xx | shipped v1.0.0 | `linuxCloudHostContract`, `snapshotAndBackupCadence` |
38
+ | platform-docker-compose-host | 38101-38899 | 39xx | shipped v1.0.0 | `containerHostContract` |
39
+ | edge-cloudflare-tunnel | 39101-39899 | 40xx | shipped v1.0.0 | `edgeIngressBridge` |
40
+
41
+ The shelf-wide band registry lives in `packages/rcf-lite/docs/blueprint-authoring.md` section 5.
42
+
43
+ ## Cross-references
44
+
45
+ - `security-secrets-management` (v1.0.1): the provisioner facade reads `HETZNER_ACCOUNT_API_KEY` through the applied secrets facade. The facade is the sole reader; every other module in the applying project imports the facade and never dereferences the token.
46
+ - `deploy-cloudflare-workers` (v1.2.0): sibling in the `deploy-*` family. The family enforces one-target-per-project at apply time (a project applying both refuses with `deploy-family-multiple-targets`).
47
+ - Round-7 T-2 `platform-docker-compose-host`: composes on `capabilities: [cloudHost]`; ships the compose runtime on the Hetzner host this blueprint provisions.
48
+ - Round-7 T-3 `edge-cloudflare-tunnel`: composes on `cloudHost` (systemd unit shape) or on `containerHost` (compose service shape); either shape bridges the Hetzner host to the Cloudflare edge without opening origin ports.
@@ -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
@@ -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