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.
- {nullforge-0.3.0 → nullforge-0.3.1}/.github/CONTRIBUTING.md +35 -2
- {nullforge-0.3.0 → nullforge-0.3.1}/.github/dependabot.yml +6 -3
- {nullforge-0.3.0 → nullforge-0.3.1}/.github/workflows/ci-code-quality.yaml +4 -18
- {nullforge-0.3.0 → nullforge-0.3.1}/.github/workflows/ci-docs.yaml +2 -2
- {nullforge-0.3.0 → nullforge-0.3.1}/.github/workflows/ci-tests.yaml +11 -6
- {nullforge-0.3.0 → nullforge-0.3.1}/.github/workflows/deploy-docs.yaml +3 -3
- {nullforge-0.3.0 → nullforge-0.3.1}/.github/workflows/release-please.yaml +36 -2
- {nullforge-0.3.0 → nullforge-0.3.1}/.pre-commit-config.yaml +3 -3
- nullforge-0.3.1/.release-please-manifest.json +3 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/CHANGELOG.md +13 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/PKG-INFO +5 -3
- {nullforge-0.3.0 → nullforge-0.3.1}/README.md +1 -1
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/concepts/architecture.md +1 -1
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/concepts/inventories.md +2 -15
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/concepts/molds.md +2 -19
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/concepts/runes.md +0 -39
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/contributing/conventions.md +2 -10
- nullforge-0.3.1/docs/contributing/development.md +26 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/base.md +4 -4
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/containers.md +5 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/dns.md +0 -1
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/index.md +0 -2
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/netsec.md +4 -6
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/profiles.md +1 -2
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/telemt.md +0 -1
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/tor.md +1 -1
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/users.md +2 -2
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/warp.md +1 -4
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/zerotrust.md +0 -1
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/getting-started/installation.md +1 -1
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/getting-started/quickstart.md +1 -1
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/guides/custom-runes.md +2 -2
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/guides/version-pinning.md +0 -3
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/index.md +0 -17
- nullforge-0.3.1/nullforge/__init__.py +1 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/inventories/example.py +3 -3
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/containers.py +73 -2
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/packages.py +1 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/pyproject.toml +19 -14
- nullforge-0.3.1/uv.lock +1234 -0
- nullforge-0.3.0/.release-please-manifest.json +0 -3
- nullforge-0.3.0/docs/contributing/development.md +0 -68
- nullforge-0.3.0/nullforge/__init__.py +0 -1
- nullforge-0.3.0/uv.lock +0 -927
- {nullforge-0.3.0 → nullforge-0.3.1}/.gitattributes +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/.github/CODEOWNERS +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/.github/ISSUE_TEMPLATE/BUG-REPORT.yaml +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yaml +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/.github/ISSUE_TEMPLATE/config.yaml +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/.github/labeler.yaml +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/.github/labels.yaml +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/.github/release.yaml +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/.github/workflows/check-commit-message-pr.yaml +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/.github/workflows/label-prs.yaml +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/.github/workflows/labels.yaml +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/.gitignore +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/.markdownlint-cli2.jsonc +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/CODE_OF_CONDUCT.md +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/LICENSE +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/haproxy.md +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/monitoring.md +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/features/xray.md +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/docs/getting-started/cli.md +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/__main__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/app.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/completion/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/completion/component.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/completion/controller.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/completion/errors.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/completion/powershell.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/foundry/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/foundry/component.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/foundry/controller.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/foundry/errors.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/runes/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/runes/component.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/components/runes/controller.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/core/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/core/application.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/core/component.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/core/controller.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/core/errors.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/core/types.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/display.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/cli/types.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/foundry/README.md +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/foundry/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/foundry/_pyinfra.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/foundry/cast.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/foundry/full_cast.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/inventories/README.md +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/containers.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/dns.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/monitoring/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/monitoring/base.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/monitoring/nezha.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/netsec.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/profiles.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/system.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/users.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/warp.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/models/zerotrust.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/base_mold.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/containers.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/defaults.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/dns.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/features.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/haproxy.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/monitoring/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/monitoring/nezha.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/netsec.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/profiles.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/system.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/telemt.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/tor.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/user.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/utils.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/warp.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/xray.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/molds/zerotrust.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/base.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/dns.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/haproxy.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/monitoring.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/netsec.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/prepare.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/profiles.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/telemt.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/tor.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/users.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/warp.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/xray.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/runes/zerotrust.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/admin.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/arch.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/blocky.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/cloudflare.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/github.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/http.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/install.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/monitoring/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/monitoring/nezha/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/monitoring/nezha/agent.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/monitoring/nezha/dashboard.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/monitoring/nezha/deploy.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/network.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/service.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/sni.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/swap.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/system.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/smithy/versions.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/cloudflared/tunnel.yml.j2 +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/dns/blocky.yaml.j2 +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/dns/dns.yaml.j2 +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/dns/resolv.conf.j2 +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/dns/resolved.conf.j2 +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/etc/default/zramswap.j2 +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/nvim/nvim_patch.lua.j2 +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/profiles/direnv.toml +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/profiles/starship.toml +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/profiles/tmux.conf +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/profiles/zshrc.j2 +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/scripts/telemt-synfix.sh +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/scripts/teleproxy-warp.sh +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/scripts/warp-v6-policy.sh +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/scripts/zt-tunnel-warp.sh +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/systemd/blocky.service.j2 +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/systemd/cloudflare-tunnel.service.j2 +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/systemd/cloudflare-warp.service.j2 +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/systemd/dns-internal.service.j2 +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/systemd/telemt.service.j2 +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/telemt/telemt.toml.j2 +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/nullforge/templates/tor/torrc.j2 +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/release-please-config.json +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/components/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/components/completion/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/components/completion/test_controller.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/components/completion/test_powershell.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/components/foundry/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/components/foundry/test_controller.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/conftest.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/core/__init__.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/core/test_application.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/cli/test_types.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/conftest.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_foundry.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_foundry_cast.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_foundry_pyinfra.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_models_dns.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_molds_dns.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_molds_monitoring.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_molds_netsec.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_molds_telemt.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_molds_user.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_molds_utils.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_runes_dns.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_runes_meta.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_runes_misc.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_runes_netsec.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_runes_profiles.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_runes_telemt.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_runes_users.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_arch.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_github.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_http.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_install.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_network.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_nezha.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_nezha_dashboard.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_packages.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_service.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_sni.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_swap.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_system.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_smithy_versions.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_templates.py +0 -0
- {nullforge-0.3.0 → nullforge-0.3.1}/tests/test_templates_telemt.py +0 -0
- {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
|
-
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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@
|
|
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
|
-
-
|
|
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@
|
|
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@
|
|
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
|
|
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@
|
|
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
|
|
36
|
+
cache-suffix: py${{ matrix.python-version }}
|
|
37
|
+
python-version: ${{ matrix.python-version }}
|
|
33
38
|
|
|
34
|
-
- name: Run tests
|
|
35
|
-
run: uv run
|
|
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@
|
|
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@
|
|
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
|
|
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@
|
|
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@
|
|
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@
|
|
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.
|
|
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.
|
|
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.
|
|
27
|
+
rev: v0.16.7
|
|
28
28
|
hooks:
|
|
29
29
|
- id: ruff-check
|
|
30
30
|
args: [--fix]
|
|
@@ -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.
|
|
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
|
-
|
|
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
|
[](https://pypi.org/project/nullforge/)
|
|
21
|
-

|
|
23
|
+

|
|
22
24
|

|
|
23
25
|

|
|
24
26
|

|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# NullForge
|
|
2
2
|
|
|
3
3
|
[](https://pypi.org/project/nullforge/)
|
|
4
|
-

|
|
4
|
+

|
|
5
5
|

|
|
6
6
|

|
|
7
7
|

|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 -
|
|
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.
|