nullforge 0.3.0__tar.gz → 0.3.1__tar.gz

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 (228) hide show
  1. {nullforge-0.3.0 → nullforge-0.3.1}/.github/CONTRIBUTING.md +35 -2
  2. {nullforge-0.3.0 → nullforge-0.3.1}/.github/dependabot.yml +6 -3
  3. {nullforge-0.3.0 → nullforge-0.3.1}/.github/workflows/ci-code-quality.yaml +4 -18
  4. {nullforge-0.3.0 → nullforge-0.3.1}/.github/workflows/ci-docs.yaml +2 -2
  5. {nullforge-0.3.0 → nullforge-0.3.1}/.github/workflows/ci-tests.yaml +11 -6
  6. {nullforge-0.3.0 → nullforge-0.3.1}/.github/workflows/deploy-docs.yaml +3 -3
  7. {nullforge-0.3.0 → nullforge-0.3.1}/.github/workflows/release-please.yaml +36 -2
  8. {nullforge-0.3.0 → nullforge-0.3.1}/.pre-commit-config.yaml +3 -3
  9. nullforge-0.3.1/.release-please-manifest.json +3 -0
  10. {nullforge-0.3.0 → nullforge-0.3.1}/CHANGELOG.md +13 -0
  11. {nullforge-0.3.0 → nullforge-0.3.1}/PKG-INFO +5 -3
  12. {nullforge-0.3.0 → nullforge-0.3.1}/README.md +1 -1
  13. {nullforge-0.3.0 → nullforge-0.3.1}/docs/concepts/architecture.md +1 -1
  14. {nullforge-0.3.0 → nullforge-0.3.1}/docs/concepts/inventories.md +2 -15
  15. {nullforge-0.3.0 → nullforge-0.3.1}/docs/concepts/molds.md +2 -19
  16. {nullforge-0.3.0 → nullforge-0.3.1}/docs/concepts/runes.md +0 -39
  17. {nullforge-0.3.0 → nullforge-0.3.1}/docs/contributing/conventions.md +2 -10
  18. nullforge-0.3.1/docs/contributing/development.md +26 -0
  19. {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/base.md +4 -4
  20. {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/containers.md +5 -0
  21. {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/dns.md +0 -1
  22. {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/index.md +0 -2
  23. {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/netsec.md +4 -6
  24. {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/profiles.md +1 -2
  25. {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/telemt.md +0 -1
  26. {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/tor.md +1 -1
  27. {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/users.md +2 -2
  28. {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/warp.md +1 -4
  29. {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/zerotrust.md +0 -1
  30. {nullforge-0.3.0 → nullforge-0.3.1}/docs/getting-started/installation.md +1 -1
  31. {nullforge-0.3.0 → nullforge-0.3.1}/docs/getting-started/quickstart.md +1 -1
  32. {nullforge-0.3.0 → nullforge-0.3.1}/docs/guides/custom-runes.md +2 -2
  33. {nullforge-0.3.0 → nullforge-0.3.1}/docs/guides/version-pinning.md +0 -3
  34. {nullforge-0.3.0 → nullforge-0.3.1}/docs/index.md +0 -17
  35. nullforge-0.3.1/nullforge/__init__.py +1 -0
  36. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/inventories/example.py +3 -3
  37. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/containers.py +73 -2
  38. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/packages.py +1 -0
  39. {nullforge-0.3.0 → nullforge-0.3.1}/pyproject.toml +19 -14
  40. nullforge-0.3.1/uv.lock +1234 -0
  41. nullforge-0.3.0/.release-please-manifest.json +0 -3
  42. nullforge-0.3.0/docs/contributing/development.md +0 -68
  43. nullforge-0.3.0/nullforge/__init__.py +0 -1
  44. nullforge-0.3.0/uv.lock +0 -927
  45. {nullforge-0.3.0 → nullforge-0.3.1}/.gitattributes +0 -0
  46. {nullforge-0.3.0 → nullforge-0.3.1}/.github/CODEOWNERS +0 -0
  47. {nullforge-0.3.0 → nullforge-0.3.1}/.github/ISSUE_TEMPLATE/BUG-REPORT.yaml +0 -0
  48. {nullforge-0.3.0 → nullforge-0.3.1}/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yaml +0 -0
  49. {nullforge-0.3.0 → nullforge-0.3.1}/.github/ISSUE_TEMPLATE/config.yaml +0 -0
  50. {nullforge-0.3.0 → nullforge-0.3.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  51. {nullforge-0.3.0 → nullforge-0.3.1}/.github/labeler.yaml +0 -0
  52. {nullforge-0.3.0 → nullforge-0.3.1}/.github/labels.yaml +0 -0
  53. {nullforge-0.3.0 → nullforge-0.3.1}/.github/release.yaml +0 -0
  54. {nullforge-0.3.0 → nullforge-0.3.1}/.github/workflows/check-commit-message-pr.yaml +0 -0
  55. {nullforge-0.3.0 → nullforge-0.3.1}/.github/workflows/label-prs.yaml +0 -0
  56. {nullforge-0.3.0 → nullforge-0.3.1}/.github/workflows/labels.yaml +0 -0
  57. {nullforge-0.3.0 → nullforge-0.3.1}/.gitignore +0 -0
  58. {nullforge-0.3.0 → nullforge-0.3.1}/.markdownlint-cli2.jsonc +0 -0
  59. {nullforge-0.3.0 → nullforge-0.3.1}/CODE_OF_CONDUCT.md +0 -0
  60. {nullforge-0.3.0 → nullforge-0.3.1}/LICENSE +0 -0
  61. {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/haproxy.md +0 -0
  62. {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/monitoring.md +0 -0
  63. {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/xray.md +0 -0
  64. {nullforge-0.3.0 → nullforge-0.3.1}/docs/getting-started/cli.md +0 -0
  65. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/__init__.py +0 -0
  66. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/__main__.py +0 -0
  67. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/app.py +0 -0
  68. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/__init__.py +0 -0
  69. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/completion/__init__.py +0 -0
  70. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/completion/component.py +0 -0
  71. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/completion/controller.py +0 -0
  72. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/completion/errors.py +0 -0
  73. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/completion/powershell.py +0 -0
  74. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/foundry/__init__.py +0 -0
  75. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/foundry/component.py +0 -0
  76. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/foundry/controller.py +0 -0
  77. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/foundry/errors.py +0 -0
  78. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/runes/__init__.py +0 -0
  79. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/runes/component.py +0 -0
  80. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/runes/controller.py +0 -0
  81. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/core/__init__.py +0 -0
  82. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/core/application.py +0 -0
  83. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/core/component.py +0 -0
  84. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/core/controller.py +0 -0
  85. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/core/errors.py +0 -0
  86. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/core/types.py +0 -0
  87. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/display.py +0 -0
  88. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/types.py +0 -0
  89. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/foundry/README.md +0 -0
  90. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/foundry/__init__.py +0 -0
  91. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/foundry/_pyinfra.py +0 -0
  92. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/foundry/cast.py +0 -0
  93. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/foundry/full_cast.py +0 -0
  94. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/inventories/README.md +0 -0
  95. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/__init__.py +0 -0
  96. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/containers.py +0 -0
  97. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/dns.py +0 -0
  98. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/monitoring/__init__.py +0 -0
  99. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/monitoring/base.py +0 -0
  100. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/monitoring/nezha.py +0 -0
  101. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/netsec.py +0 -0
  102. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/profiles.py +0 -0
  103. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/system.py +0 -0
  104. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/users.py +0 -0
  105. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/warp.py +0 -0
  106. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/zerotrust.py +0 -0
  107. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/__init__.py +0 -0
  108. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/base_mold.py +0 -0
  109. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/containers.py +0 -0
  110. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/defaults.py +0 -0
  111. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/dns.py +0 -0
  112. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/features.py +0 -0
  113. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/haproxy.py +0 -0
  114. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/monitoring/__init__.py +0 -0
  115. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/monitoring/nezha.py +0 -0
  116. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/netsec.py +0 -0
  117. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/profiles.py +0 -0
  118. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/system.py +0 -0
  119. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/telemt.py +0 -0
  120. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/tor.py +0 -0
  121. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/user.py +0 -0
  122. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/utils.py +0 -0
  123. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/warp.py +0 -0
  124. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/xray.py +0 -0
  125. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/zerotrust.py +0 -0
  126. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/__init__.py +0 -0
  127. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/base.py +0 -0
  128. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/dns.py +0 -0
  129. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/haproxy.py +0 -0
  130. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/monitoring.py +0 -0
  131. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/netsec.py +0 -0
  132. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/prepare.py +0 -0
  133. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/profiles.py +0 -0
  134. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/telemt.py +0 -0
  135. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/tor.py +0 -0
  136. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/users.py +0 -0
  137. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/warp.py +0 -0
  138. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/xray.py +0 -0
  139. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/zerotrust.py +0 -0
  140. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/__init__.py +0 -0
  141. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/admin.py +0 -0
  142. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/arch.py +0 -0
  143. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/blocky.py +0 -0
  144. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/cloudflare.py +0 -0
  145. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/github.py +0 -0
  146. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/http.py +0 -0
  147. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/install.py +0 -0
  148. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/monitoring/__init__.py +0 -0
  149. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/monitoring/nezha/__init__.py +0 -0
  150. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/monitoring/nezha/agent.py +0 -0
  151. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/monitoring/nezha/dashboard.py +0 -0
  152. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/monitoring/nezha/deploy.py +0 -0
  153. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/network.py +0 -0
  154. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/service.py +0 -0
  155. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/sni.py +0 -0
  156. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/swap.py +0 -0
  157. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/system.py +0 -0
  158. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/versions.py +0 -0
  159. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/__init__.py +0 -0
  160. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/cloudflared/tunnel.yml.j2 +0 -0
  161. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/dns/blocky.yaml.j2 +0 -0
  162. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/dns/dns.yaml.j2 +0 -0
  163. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/dns/resolv.conf.j2 +0 -0
  164. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/dns/resolved.conf.j2 +0 -0
  165. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/etc/default/zramswap.j2 +0 -0
  166. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/nvim/nvim_patch.lua.j2 +0 -0
  167. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/profiles/direnv.toml +0 -0
  168. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/profiles/starship.toml +0 -0
  169. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/profiles/tmux.conf +0 -0
  170. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/profiles/zshrc.j2 +0 -0
  171. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/scripts/telemt-synfix.sh +0 -0
  172. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/scripts/teleproxy-warp.sh +0 -0
  173. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/scripts/warp-v6-policy.sh +0 -0
  174. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/scripts/zt-tunnel-warp.sh +0 -0
  175. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/systemd/blocky.service.j2 +0 -0
  176. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/systemd/cloudflare-tunnel.service.j2 +0 -0
  177. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/systemd/cloudflare-warp.service.j2 +0 -0
  178. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/systemd/dns-internal.service.j2 +0 -0
  179. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/systemd/telemt.service.j2 +0 -0
  180. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/telemt/telemt.toml.j2 +0 -0
  181. {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/tor/torrc.j2 +0 -0
  182. {nullforge-0.3.0 → nullforge-0.3.1}/release-please-config.json +0 -0
  183. {nullforge-0.3.0 → nullforge-0.3.1}/tests/__init__.py +0 -0
  184. {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/__init__.py +0 -0
  185. {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/components/__init__.py +0 -0
  186. {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/components/completion/__init__.py +0 -0
  187. {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/components/completion/test_controller.py +0 -0
  188. {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/components/completion/test_powershell.py +0 -0
  189. {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/components/foundry/__init__.py +0 -0
  190. {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/components/foundry/test_controller.py +0 -0
  191. {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/conftest.py +0 -0
  192. {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/core/__init__.py +0 -0
  193. {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/core/test_application.py +0 -0
  194. {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/test_types.py +0 -0
  195. {nullforge-0.3.0 → nullforge-0.3.1}/tests/conftest.py +0 -0
  196. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_foundry.py +0 -0
  197. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_foundry_cast.py +0 -0
  198. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_foundry_pyinfra.py +0 -0
  199. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_models_dns.py +0 -0
  200. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_molds_dns.py +0 -0
  201. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_molds_monitoring.py +0 -0
  202. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_molds_netsec.py +0 -0
  203. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_molds_telemt.py +0 -0
  204. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_molds_user.py +0 -0
  205. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_molds_utils.py +0 -0
  206. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_runes_dns.py +0 -0
  207. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_runes_meta.py +0 -0
  208. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_runes_misc.py +0 -0
  209. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_runes_netsec.py +0 -0
  210. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_runes_profiles.py +0 -0
  211. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_runes_telemt.py +0 -0
  212. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_runes_users.py +0 -0
  213. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_arch.py +0 -0
  214. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_github.py +0 -0
  215. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_http.py +0 -0
  216. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_install.py +0 -0
  217. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_network.py +0 -0
  218. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_nezha.py +0 -0
  219. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_nezha_dashboard.py +0 -0
  220. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_packages.py +0 -0
  221. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_service.py +0 -0
  222. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_sni.py +0 -0
  223. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_swap.py +0 -0
  224. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_system.py +0 -0
  225. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_versions.py +0 -0
  226. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_templates.py +0 -0
  227. {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_templates_telemt.py +0 -0
  228. {nullforge-0.3.0 → nullforge-0.3.1}/zensical.toml +0 -0
@@ -85,6 +85,40 @@ If any hooks fail, please fix the issues before committing. You can manually run
85
85
  prek run --all-files
86
86
  ```
87
87
 
88
+ ### Tasks (via poe)
89
+
90
+ Every routine check, build and test step is a [poe](https://poethepoet.natn.io/) task.
91
+ Tasks run through poe's `uv` executor, so they resolve their own environment - no `uv run --group ...` prefix needed.
92
+ The bare name is the variant CI runs; a colon suffix selects a mode (`:fix` for auto-fix, `:dev` for the local loop):
93
+
94
+ ```bash
95
+ uv sync # create the environment
96
+ uv run poe check # format + lint + typecheck + lint-imports + deptry (what CI runs)
97
+ uv run poe format # ruff format --check
98
+ uv run poe format:fix # ruff format
99
+ uv run poe lint # ruff check
100
+ uv run poe lint:fix # ruff check --fix
101
+ uv run poe lint:md # markdownlint over all Markdown (via prek)
102
+ uv run poe typecheck # ty check
103
+ uv run poe lint-imports # import-linter layer contracts
104
+ uv run poe deptry # dependency hygiene
105
+ uv run poe tests # pytest with coverage
106
+ uv run poe docs # strict docs site build (what docs CI runs)
107
+ uv run poe docs:dev # serve the docs site with live reload
108
+ ```
109
+
110
+ Run `uv run poe check` and `uv run poe tests` before pushing - they are the same checks the pull request runs.
111
+ Markdown is not in `poe check` - the prek hook lints it on every commit and in CI; `uv run poe lint:md` runs it on demand.
112
+ `poe check` does not build the documentation site either, so run `uv run poe docs` when the change touches `docs/`.
113
+
114
+ ### Tests
115
+
116
+ `uv run poe tests` runs the suite; CI runs it on Python 3.13, 3.14 and 3.15.
117
+ `tests/conftest.py` patches the pyinfra context so rune and operation calls are no-ops - molds, smithy helpers, runes, and templates unit-test without a real target.
118
+ End-to-end correctness is still validated by deploying to a real host.
119
+
120
+ The suite also enforces the [conventions](https://wlix13.github.io/NullForge/contributing/conventions/).
121
+
88
122
  ## Commits
89
123
 
90
124
  All commits are expected to follow the conventional commits specification.
@@ -113,8 +147,7 @@ message — please replace that with something descriptive. Examples:
113
147
  - `Improve CI/CD caching and test matrix`
114
148
  - `Fix swap sizing on small hosts`
115
149
 
116
- A good title reads cleanly in the PR list and the project history. The individual
117
- **commits** inside the PR still follow Conventional Commits (see [Commits](#commits));
150
+ The individual **commits** inside the PR still follow Conventional Commits (see [Commits](#commits));
118
151
  because PRs are merged with a merge/rebase strategy, those commit messages — not the
119
152
  title — drive the release changelog, so the title itself does not need a `type:` prefix.
120
153
 
@@ -3,7 +3,8 @@ updates:
3
3
  - package-ecosystem: "github-actions"
4
4
  directory: "/"
5
5
  schedule:
6
- interval: "weekly"
6
+ interval: "cron"
7
+ cronjob: "0 6 1,15 * *" # twice a month, closest dependabot gets to every two weeks
7
8
  commit-message:
8
9
  prefix: "chore"
9
10
  include: "scope"
@@ -17,7 +18,8 @@ updates:
17
18
  - package-ecosystem: "uv"
18
19
  directory: "/"
19
20
  schedule:
20
- interval: "weekly"
21
+ interval: "cron"
22
+ cronjob: "0 6 1,15 * *" # twice a month, closest dependabot gets to every two weeks
21
23
  commit-message:
22
24
  prefix: "chore"
23
25
  include: "scope"
@@ -31,7 +33,8 @@ updates:
31
33
  - package-ecosystem: "pre-commit"
32
34
  directory: "/"
33
35
  schedule:
34
- interval: "weekly"
36
+ interval: "cron"
37
+ cronjob: "0 6 1,15 * *" # twice a month, closest dependabot gets to every two weeks
35
38
  commit-message:
36
39
  prefix: "chore"
37
40
  include: "scope"
@@ -25,27 +25,13 @@ jobs:
25
25
  with:
26
26
  persist-credentials: false
27
27
 
28
- - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
28
+ - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
29
29
  with:
30
30
  enable-cache: true
31
31
  cache-dependency-glob: uv.lock
32
32
 
33
- - run: uv sync --all-groups
34
-
35
- - name: Check formatting
36
- run: uv run ruff format --check .
37
-
38
- - name: Lint code
39
- run: uv run ruff check .
40
-
41
- - name: Type check
42
- run: uv run ty check
43
-
44
- - name: Check import contracts
45
- run: uv run lint-imports
46
-
47
- - name: Check dependency hygiene
48
- run: uv run deptry .
33
+ - name: Run quality checks
34
+ run: uv run poe check
49
35
 
50
36
  hooks:
51
37
  name: 🪝 prek hooks
@@ -56,7 +42,7 @@ jobs:
56
42
  with:
57
43
  persist-credentials: false
58
44
 
59
- - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
45
+ - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
60
46
  with:
61
47
  enable-cache: true
62
48
  cache-dependency-glob: uv.lock
@@ -28,10 +28,10 @@ jobs:
28
28
  with:
29
29
  persist-credentials: false
30
30
 
31
- - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
31
+ - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
32
32
  with:
33
33
  enable-cache: true
34
34
  cache-dependency-glob: uv.lock
35
35
 
36
36
  - name: Build documentation
37
- run: uv run --group docs zensical build --clean --strict
37
+ run: uv run poe docs
@@ -17,22 +17,27 @@ concurrency:
17
17
 
18
18
  jobs:
19
19
  tests:
20
- name: 🧪 Tests
20
+ name: 🧪 Tests (py${{ matrix.python-version }})
21
21
  runs-on: ubuntu-latest
22
22
  timeout-minutes: 15
23
+ strategy:
24
+ fail-fast: false
25
+ matrix:
26
+ python-version: ["3.13", "3.14", "3.15"]
23
27
  steps:
24
28
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
25
29
  with:
26
30
  persist-credentials: false
27
31
 
28
- - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
32
+ - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
29
33
  with:
30
34
  enable-cache: true
31
35
  cache-dependency-glob: uv.lock
32
- python-version: "3.13"
36
+ cache-suffix: py${{ matrix.python-version }}
37
+ python-version: ${{ matrix.python-version }}
33
38
 
34
- - name: Run tests with coverage
35
- run: uv run --group tests pytest
39
+ - name: Run tests
40
+ run: uv run poe tests
36
41
 
37
42
  build:
38
43
  name: 📦 Package build check
@@ -43,7 +48,7 @@ jobs:
43
48
  with:
44
49
  persist-credentials: false
45
50
 
46
- - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
51
+ - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
47
52
  with:
48
53
  enable-cache: true
49
54
  cache-dependency-glob: uv.lock
@@ -32,13 +32,13 @@ jobs:
32
32
  with:
33
33
  persist-credentials: false
34
34
 
35
- - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
35
+ - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
36
36
  with:
37
37
  enable-cache: true
38
38
  cache-dependency-glob: uv.lock
39
39
 
40
40
  - name: Build documentation
41
- run: uv run --group docs zensical build --clean --strict
41
+ run: uv run poe docs
42
42
 
43
43
  - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
44
44
 
@@ -59,4 +59,4 @@ jobs:
59
59
  url: ${{ steps.deployment.outputs.page_url }}
60
60
  steps:
61
61
  - id: deployment
62
- uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
62
+ uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
@@ -52,7 +52,7 @@ jobs:
52
52
  with:
53
53
  persist-credentials: false
54
54
 
55
- - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
55
+ - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
56
56
  with:
57
57
  enable-cache: false
58
58
 
@@ -111,7 +111,9 @@ jobs:
111
111
  permissions:
112
112
  id-token: write
113
113
  steps:
114
- - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
114
+ - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
115
+ with:
116
+ enable-cache: false
115
117
 
116
118
  - name: Download distribution artifacts
117
119
  uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
@@ -122,6 +124,38 @@ jobs:
122
124
  - name: Publish to PyPI
123
125
  run: uv publish --trusted-publishing always
124
126
 
127
+ publish-photon:
128
+ name: 📤 Publish to photon-manifest
129
+ needs: [release-please, build, attest-artifacts]
130
+ if: needs.release-please.outputs.release_created == 'true'
131
+ runs-on: ubuntu-latest
132
+ timeout-minutes: 10
133
+ environment:
134
+ name: photon
135
+ url: https://py.wlix13.dev/simple/nullforge/
136
+ permissions:
137
+ contents: read
138
+ steps:
139
+ - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
140
+ with:
141
+ enable-cache: false
142
+
143
+ - name: Download distribution artifacts
144
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
145
+ with:
146
+ name: python-package-distributions
147
+ path: dist/
148
+
149
+ - name: Publish to photon-manifest
150
+ env:
151
+ UV_PUBLISH_TOKEN: ${{ secrets.PHOTON_PUBLISH_TOKEN }}
152
+ run: |
153
+ uv auth login https://py.wlix13.dev/simple/ --token "$UV_PUBLISH_TOKEN"
154
+ uv publish \
155
+ --publish-url https://py.wlix13.dev/legacy/ \
156
+ --check-url https://py.wlix13.dev/simple/ \
157
+ --no-attestations
158
+
125
159
  finalize-release:
126
160
  name: 🚀 Attach assets and publish
127
161
  needs: [release-please, build, attest-artifacts, publish-pypi]
@@ -5,7 +5,7 @@ default_install_hook_types:
5
5
  - post-rewrite
6
6
  repos:
7
7
  - repo: https://github.com/tox-dev/pyproject-fmt
8
- rev: v2.28.0
8
+ rev: v2.29.4
9
9
  hooks:
10
10
  - id: pyproject-fmt
11
11
 
@@ -19,12 +19,12 @@ repos:
19
19
  - id: end-of-file-fixer
20
20
 
21
21
  - repo: https://github.com/astral-sh/uv-pre-commit
22
- rev: 0.12.5
22
+ rev: 0.12.14
23
23
  hooks:
24
24
  - id: uv-lock
25
25
 
26
26
  - repo: https://github.com/astral-sh/ruff-pre-commit
27
- rev: v0.16.3
27
+ rev: v0.16.7
28
28
  hooks:
29
29
  - id: ruff-check
30
30
  args: [--fix]
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.3.1"
3
+ }
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.1](https://github.com/wlix13/NullForge/compare/v0.3.0...v0.3.1) (2026-09-18)
4
+
5
+
6
+ ### Features
7
+
8
+ * **runes:** enhance Podman support with rootless prerequisites and auto-updater ([9c02ab7](https://github.com/wlix13/NullForge/commit/9c02ab7de2196149c500ac6aa3ddd457896af31b))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **docs:** cut repeated and internal detail from docs and contributing guide ([21741ef](https://github.com/wlix13/NullForge/commit/21741efe1a785fc5c3fe3b22b58aaefff43f8e70))
14
+ * **inventories:** keep example presets out of pyinfra group discovery ([73578d6](https://github.com/wlix13/NullForge/commit/73578d65f7ceef892491905cc1c670c2e92f7a04))
15
+
3
16
  ## [0.3.0](https://github.com/wlix13/NullForge/compare/v0.2.0...v0.3.0) (2026-08-28)
4
17
 
5
18
 
@@ -1,13 +1,15 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: nullforge
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Forge the server's baseline from null
5
5
  Author-email: wlix13 <github@wlix13.dev>
6
6
  Maintainer-email: wlix13 <github@wlix13.dev>
7
7
  License-File: LICENSE
8
8
  Classifier: Programming Language :: Python :: 3 :: Only
9
9
  Classifier: Programming Language :: Python :: 3.13
10
- Requires-Python: <3.14,>=3.13
10
+ Classifier: Programming Language :: Python :: 3.14
11
+ Classifier: Programming Language :: Python :: 3.15
12
+ Requires-Python: >=3.13
11
13
  Requires-Dist: click
12
14
  Requires-Dist: pydantic
13
15
  Requires-Dist: pyinfra
@@ -18,7 +20,7 @@ Description-Content-Type: text/markdown
18
20
  # NullForge
19
21
 
20
22
  [![PyPI](https://img.shields.io/pypi/v/nullforge?logo=pypi&logoColor=white)](https://pypi.org/project/nullforge/)
21
- ![Python](https://img.shields.io/badge/python-3.13-blue?logo=python&logoColor=white)
23
+ ![Python](https://img.shields.io/badge/python-3.13%20%7C%203.14%20%7C%203.15-blue?logo=python&logoColor=white)
22
24
  ![Build](https://img.shields.io/github/actions/workflow/status/wlix13/NullForge/ci-tests.yaml?label=build&logo=github)
23
25
  ![Lint](https://img.shields.io/github/actions/workflow/status/wlix13/NullForge/ci-code-quality.yaml?label=lint&logo=github)
24
26
  ![License](https://img.shields.io/badge/license-MIT-green)
@@ -1,7 +1,7 @@
1
1
  # NullForge
2
2
 
3
3
  [![PyPI](https://img.shields.io/pypi/v/nullforge?logo=pypi&logoColor=white)](https://pypi.org/project/nullforge/)
4
- ![Python](https://img.shields.io/badge/python-3.13-blue?logo=python&logoColor=white)
4
+ ![Python](https://img.shields.io/badge/python-3.13%20%7C%203.14%20%7C%203.15-blue?logo=python&logoColor=white)
5
5
  ![Build](https://img.shields.io/github/actions/workflow/status/wlix13/NullForge/ci-tests.yaml?label=build&logo=github)
6
6
  ![Lint](https://img.shields.io/github/actions/workflow/status/wlix13/NullForge/ci-code-quality.yaml?label=lint&logo=github)
7
7
  ![License](https://img.shields.io/badge/license-MIT-green)
@@ -58,4 +58,4 @@ The [conventions](../contributing/conventions.md) around `host.loop` and change
58
58
  ## The CLI wrapper
59
59
 
60
60
  `nullforge cast` is a thin planner around pyinfra: it resolves the [cast stages](../getting-started/cli.md#stages) and hands them to pyinfra.
61
- Everything else - connections, facts, operations, parallelism - is stock pyinfra, which is why unknown CLI options are proxied through verbatim.
61
+ Everything else is stock pyinfra, which is why unknown CLI options are proxied through verbatim.
@@ -44,20 +44,15 @@ common = (
44
44
  features = merge_features(BASE_FEATURES, *common, {"dns": {"ecs": True}})
45
45
  ```
46
46
 
47
- The set of allowed sub-mold types is derived from `FeaturesMold.model_fields`, so a new feature is mergeable without touching the merge logic.
48
-
49
47
  !!! warning "Deep-merge is per-key"
50
48
 
51
49
  Dictionaries merge recursively, but any non-dict value - including lists - replaces the previous value outright.
52
50
  A layer that sets `netsec.firewall_rules` replaces the whole rule list; extend `BASE_FEATURES.netsec.firewall_rules` in Python if you want "default rules plus mine".
53
51
 
54
- Because inventories are Python, you can factor shared preset tuples, per-environment modules, or host loops however you like - the only contract is the final `hosts` list.
55
-
56
52
  ## Validation and coercion
57
53
 
58
- The foundry coerces whatever the inventory provided - `None`, a `dict`, or a mold instance - into validated `SystemMold` / `FeaturesMold` objects before any rune runs (`ensure_system` / `ensure_features`).
54
+ The foundry coerces whatever the inventory provided - `None`, a `dict`, or a mold instance - into validated `SystemMold` / `FeaturesMold` objects before any rune runs.
59
55
  Missing keys fall back to mold defaults; unknown keys are rejected because every mold forbids extra fields.
60
- A typo like `{"warp": {"instal": True}}` fails the cast at validation time, before anything touches a host.
61
56
 
62
57
  ## Scaling to a fleet
63
58
 
@@ -143,20 +138,12 @@ for zone in ZONE_FEATURES:
143
138
  nullforge cast -i inventory/main.py --limit web --dry
144
139
  ```
145
140
 
146
- Details worth stealing:
147
-
148
- - `entry.get("overrides")` is either a dict fragment or `None` - both are valid `merge_features` layers, so per-host overrides cost one line and still go through mold validation.
149
- - Extra data keys (like `zone` above) ride along on `host.data` untouched; [custom runes](../guides/custom-runes.md) can branch on them.
150
- - Adding a host is a YAML edit, reviewable in a PR and scriptable from CI.
151
-
152
- This is the pattern behind the deploy repo of the **Conglomerate** proxy fleet: zone presets over NullForge molds, a YAML host registry edited from CI workflows, and per-zone `--limit` casts.
153
-
154
141
  ## Secrets in inventories
155
142
 
156
143
  Inventories are code; secrets in them (tunnel tokens, proxy user secrets) end up on disk.
157
144
  Keep real inventories out of public repos, or load secrets from the environment.
158
145
 
159
- Mold fields marked sensitive (user password, Zero Trust token, Nezha secrets, Telemt users) are redacted as `***` in pyinfra's `--debug-inventory` output, so inspecting a plan does not leak them.
146
+ Sensitive mold fields are redacted as `***` in pyinfra's `--debug-inventory` output, so inspecting a plan does not leak them.
160
147
 
161
148
  ## Debugging
162
149
 
@@ -9,7 +9,7 @@ Every mold extends `BaseMold` (`nullforge/molds/base_mold.py`), which sets the s
9
9
 
10
10
  - **`extra="forbid"`** - unknown keys are validation errors, so typos fail the cast instead of silently deploying defaults.
11
11
  - **`is_active`** - the activation protocol used for [rune dispatch](runes.md#dispatch); each feature sub-mold implements it.
12
- - **`to_json()`** - JSON-mode serialization for pyinfra's `--debug-inventory`, with `_sensitive_fields` values redacted as `***` (recursively, through nested molds, lists and dicts).
12
+ - **`to_json()`** - JSON-mode serialization for pyinfra's `--debug-inventory`, with `_sensitive_fields` values redacted as `***`.
13
13
 
14
14
  ## The top-level molds
15
15
 
@@ -17,23 +17,7 @@ Two molds cover a host:
17
17
 
18
18
  - **`SystemMold`** - base system state: packages, locales, timezone, hostname, swap, IPv6.
19
19
  Consumed by the always-on [base rune](../features/base.md).
20
- - **`FeaturesMold`** - one field per feature, each a sub-mold with its own defaults:
21
-
22
- ```python
23
- class FeaturesMold(BaseMold):
24
- warp: WarpMold = Field(default_factory=WarpMold)
25
- dns: DnsMold = Field(default_factory=DnsMold)
26
- users: UserMold = Field(default_factory=UserMold)
27
- netsec: NetSecMold = Field(default_factory=NetSecMold)
28
- profiles: ProfilesMold = Field(default_factory=ProfilesMold)
29
- zerotrust: ZeroTrustTunnelMold = Field(default_factory=ZeroTrustTunnelMold)
30
- containers: ContainersMold = Field(default_factory=ContainersMold)
31
- monitoring: MonitoringMold = Field(default_factory=MonitoringMold)
32
- haproxy: HaproxyMold = Field(default_factory=HaproxyMold)
33
- xray: XrayCoreMold = Field(default_factory=XrayCoreMold)
34
- tor: TorMold = Field(default_factory=TorMold)
35
- telemt: TelemtMold = Field(default_factory=TelemtMold)
36
- ```
20
+ - **`FeaturesMold`** - one field per feature, each a sub-mold with its own defaults.
37
21
 
38
22
  Field order is deploy order.
39
23
  Everything else is derived from these fields - the allowed [merge layers](inventories.md), the feature-to-mold mapping, and rune dispatch - so adding a feature means adding a field, not editing plumbing.
@@ -66,4 +50,3 @@ dns = DnsMold(mode=DnsMode.DOT_RESOLVED)
66
50
  ```
67
51
 
68
52
  `nullforge.molds` exports only the molds.
69
- The split is enforced by an import contract: models import nothing from the rest of the package.
@@ -13,43 +13,6 @@ Runes are executed, not imported.
13
13
  The foundry (or the selective `cast.py`) pulls each one in with pyinfra's `local.include`, so the module-level code runs at deploy time with `host` bound to the current target.
14
14
  Every rune file defines a `deploy_*()` function and calls it at the bottom.
15
15
 
16
- A typical rune:
17
-
18
- ```python
19
- """Tor proxy deployment module."""
20
-
21
- from pyinfra.context import host
22
- from pyinfra.operations import files, systemd
23
-
24
- from nullforge.molds import FeaturesMold, TorMold
25
- from nullforge.smithy.packages import get_pm
26
- from nullforge.templates import get_template_path
27
-
28
-
29
- def deploy_tor() -> None:
30
- features: FeaturesMold = host.data.features
31
- tor_opts: TorMold = features.tor
32
-
33
- get_pm().install(name="Install Tor package", packages=["tor"], _sudo=True)
34
-
35
- config = files.template(
36
- name="Deploy Tor proxy configuration",
37
- src=get_template_path("tor/torrc.j2"),
38
- dest="/etc/tor/torrc",
39
- SOCKS_PORT=tor_opts.socks_port,
40
- DNS_PORT=tor_opts.dns_port,
41
- _sudo=True,
42
- )
43
-
44
- systemd.service(service="tor", restarted=True, _sudo=True, _if=config.did_change)
45
- systemd.service(service="tor", running=True, enabled=True, _sudo=True)
46
-
47
-
48
- deploy_tor()
49
- ```
50
-
51
- The pattern to note: configuration comes from `host.data`, distro differences go through the smithy (`get_pm()`), and service restarts are guarded by [change detection](../contributing/conventions.md#change-detection).
52
-
53
16
  ## Dispatch
54
17
 
55
18
  The full cast derives its rune list from `FeaturesMold` itself:
@@ -60,8 +23,6 @@ The full cast derives its rune list from `FeaturesMold` itself:
60
23
  The rune name defaults to the field name (`warp` -> `runes/warp.py`).
61
24
  A sub-mold can override it with `_feature_rune = "other-name"`, or opt out of dispatch with `_feature_rune = None`.
62
25
 
63
- The resulting deploy order is the [feature table](../features/index.md) top to bottom, filtered to active features.
64
-
65
26
  ## Independence
66
27
 
67
28
  No rune imports another rune.
@@ -1,7 +1,6 @@
1
1
  # Conventions
2
2
 
3
3
  The rules below keep casts deterministic and idempotent.
4
- Most are enforced by tests or CI; the reasoning is documented here so violations are recognizable in review.
5
4
 
6
5
  ## host.loop
7
6
 
@@ -34,17 +33,15 @@ systemd.daemon_reload(..., _if=config.did_change)
34
33
  - Prefer a **conditional** "restart on change" plus an **unconditional** "ensure running/enabled".
35
34
  - `_if=` expects callables (or a list of them).
36
35
  `_if=op.will_change` passes a bare boolean and is silently ignored - always `did_change`/`did_succeed` references.
37
- - Plain `if op.will_change:` is for **prepare-time decisions that emit more operations** only (rare - the GRUB regeneration in `base.py` is the canonical case).
36
+ - Plain `if op.will_change:` is for **prepare-time decisions that emit more operations** only.
38
37
 
39
38
  ## Version-pinned installers
40
39
 
41
40
  Installers for pinned tools guard with `is_pinned_version_installed(tool, binary_path)` - not a bare `File` fact - so a pin bump triggers re-install.
42
- Past the guard, call `install_release_binary()` rather than hand-rolling download/extract: it force-downloads, verifies the sha256 when one is passed or resolved from GitHub release metadata (best-effort - when no checksum is available it installs unverified and logs a warning), extracts any archive kind, and returns the install `OperationMeta` for restart chaining.
41
+ Past the guard, call `install_release_binary()` rather than hand-rolling download/extract: it force-downloads, verifies the sha256 when one is passed or resolved from GitHub release metadata, extracts any archive kind, and returns the install `OperationMeta` for restart chaining.
43
42
 
44
43
  Keep the guard at the call site: checksum resolution makes a plan-time API call, so the helper should only be reached when an install is actually pending.
45
44
 
46
- Non-fitting installers stay custom: source builds (tmux), AppImage-to-directory extraction (nvim), and non-GitHub / `latest` / multi-binary downloads (gVisor).
47
-
48
45
  ## Downloads
49
46
 
50
47
  Every `curl` NullForge runs on a target takes its options from `smithy.http.curl_args(url)` - `curl_args_str(url)` for hand-written commands, `Versions.release_curl_args(url)` for GitHub release assets - never the bare `CURL_ARGS` dict.
@@ -60,13 +57,8 @@ Handle return values instead: `FileContents` is `None` for a missing or unreadab
60
57
 
61
58
  Files users are expected to edit (`.zshrc`, nvim's `init.lua`) are written with `files.block` and a `{mark}` marker - never `files.template` - so everything outside the markers survives re-deploys.
62
59
 
63
- `.zshrc` prepends its block (`before=True, after=True`) so user lines come last and win.
64
- A file predating the markers is detected via the `Block` fact returning `[]` and cleared once with `files.line(line=".*", present=False, backup=True)` - otherwise the block would be prepended to a stale copy of itself.
65
-
66
60
  ## Code style
67
61
 
68
62
  - Private helpers within rune/smithy files are `_`-prefixed.
69
63
  - Type annotations everywhere; `ty` is the checker.
70
- - Line length 120; ruff rules `E, F, I, N, S, UP, W, YTT`.
71
64
  - [Import-layer contracts](../concepts/architecture.md#layer-contracts) are part of `poe check` - new modules must respect the spine.
72
- - Docs prose is one sentence per line; markdownlint runs over all Markdown via prek.
@@ -0,0 +1,26 @@
1
+ # Development
2
+
3
+ Tasks, tests, commit conventions and the pull request flow are in [CONTRIBUTING.md](https://github.com/wlix13/NullForge/blob/main/.github/CONTRIBUTING.md); this page covers setup and the documentation site.
4
+
5
+ ## Setup
6
+
7
+ Only [uv](https://docs.astral.sh/uv/) is supported:
8
+
9
+ ```bash
10
+ git clone https://github.com/wlix13/NullForge
11
+ cd NullForge
12
+ uv sync
13
+ uv run prek install # git hooks via prek
14
+ ```
15
+
16
+ ## Documentation
17
+
18
+ The site is built with [zensical](https://zensical.org) from `docs/` and `zensical.toml`:
19
+
20
+ ```bash
21
+ uv run poe docs:dev # http://localhost:8000, live reload
22
+ uv run poe docs # strict build into site/
23
+ ```
24
+
25
+ All docs are Markdown, linted by markdownlint (`.markdownlint-cli2.jsonc`; MD013 is off - prose is written one sentence per line).
26
+ CI builds the site strictly on docs PRs and deploys `main` to GitHub Pages.
@@ -15,10 +15,10 @@ Fresh images without `sudo`: use [`--with-prepare`](../getting-started/cli.md#st
15
15
 
16
16
  Deploys the system baseline, in order:
17
17
 
18
- 1. **IPv6 stack** - when `ensure_ipv6` is set and GRUB carries `ipv6.disable=...`, strips it and regenerates the GRUB config (Debian `update-grub` or RHEL `grub2-mkconfig`).
18
+ 1. **IPv6 stack** - when `ensure_ipv6` is set and GRUB carries `ipv6.disable=...`, strips it and regenerates the GRUB config.
19
19
  2. **Hostname** - sets the hostname, writes it to `/etc/hosts` with the host's primary IP, and pins `preserve_hostname: true` in cloud-init so reboots keep it.
20
20
  3. **Packages** - refreshes and upgrades, then installs `packages_base` (names are [mapped per distro](../concepts/architecture.md)).
21
- 4. **curl** - installs a pinned static `curl` to `/usr/local/bin` (removing the distro package on Debian, symlinking the CA bundle path on RHEL); falls back to the repo package when no static build fits the architecture.
21
+ 4. **curl** - installs a pinned static `curl` to `/usr/local/bin`; falls back to the repo package when no static build fits the architecture.
22
22
  5. **doggo** - a pinned DNS client for debugging resolvers.
23
23
  6. **Locales** - resolves each requested locale against what the target can generate, enables it in `/etc/locale.gen`, and runs `locale-gen` when needed.
24
24
  7. **Timezone** and **NTP** - `systemd-timesyncd` on Debian/Ubuntu, `chrony` (with a forced initial sync) on RHEL.
@@ -35,7 +35,7 @@ Deploys the system baseline, in order:
35
35
  | `swap` | see below | Swap configuration |
36
36
  | `ensure_ipv6` | `true` | Repair a GRUB-disabled IPv6 kernel stack |
37
37
 
38
- `hostname` is validated as a proper FQDN (labels, length, charset) at plan time.
38
+ `hostname` is validated as a proper FQDN at plan time.
39
39
 
40
40
  ### Swap (`system.swap`)
41
41
 
@@ -52,7 +52,7 @@ The two types are mutually exclusive - enabling one dismantles the other, so swi
52
52
  !!! note "swappiness vs netsec"
53
53
 
54
54
  [Network security](netsec.md) also sets `vm.swappiness` (default `10`) in its sysctl group.
55
- With swap enabled, the swap value is the effective one - its sysctl file sorts later; keep the two intentionally aligned.
55
+ With swap enabled, the swap value is the effective one - keep the two intentionally aligned.
56
56
 
57
57
  ## Example
58
58
 
@@ -29,6 +29,11 @@ A container runtime with a sandboxed-by-default configuration.
29
29
 
30
30
  Installs `podman`, `podman-compose`, and the `crun` runtime from distro packages - no scripts, no extra repos.
31
31
 
32
+ Rootless prerequisites come along: `passt` (the `pasta` network backend) and `uidmap` on Debian/Ubuntu (`shadow-utils` on RHEL) for `newuidmap`/`newgidmap`.
33
+
34
+ `podman-auto-update.timer` is enabled system-wide.
35
+ When `users.manage` is on, the [managed user](users.md) also gets lingering enabled (`loginctl enable-linger`) and the same timer enabled in its user session, so rootless containers labelled `io.containers.autoupdate` are refreshed daily.
36
+
32
37
  ## Configuration (`features.containers`)
33
38
 
34
39
  | Field | Default | Description |
@@ -22,7 +22,6 @@ Switching modes converges: `dot_resolved` stops and disables the Blocky services
22
22
  Wildcard and loopback addresses use existing interfaces.
23
23
  - Renders `/etc/blocky/config.yaml` with DoH upstreams from the selected provider (IPv6 upstreams included only when the host has IPv6).
24
24
  - Points `/etc/resolv.conf` directly at the Blocky address, replacing any symlink.
25
- - Restarts Blocky only when the binary, config, or unit changed.
26
25
 
27
26
  ### `dot_resolved`
28
27
 
@@ -25,5 +25,3 @@ Turning a default-on feature off is a one-line layer:
25
25
  ```python
26
26
  merge_features(BASE_FEATURES, {"dns": {"mode": "none"}, "profiles": {"for_root": False}})
27
27
  ```
28
-
29
- Every page in this section documents the feature's behaviour, its configuration fields with defaults, and an inventory fragment to start from.