nullforge 0.2.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.2.0 → nullforge-0.3.1}/.github/CONTRIBUTING.md +35 -2
- {nullforge-0.2.0 → nullforge-0.3.1}/.github/dependabot.yml +6 -3
- {nullforge-0.2.0 → nullforge-0.3.1}/.github/labeler.yaml +2 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/.github/workflows/ci-code-quality.yaml +4 -12
- nullforge-0.3.1/.github/workflows/ci-docs.yaml +37 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/.github/workflows/ci-tests.yaml +11 -6
- nullforge-0.3.1/.github/workflows/deploy-docs.yaml +62 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/.github/workflows/release-please.yaml +37 -3
- nullforge-0.3.1/.markdownlint-cli2.jsonc +25 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/.pre-commit-config.yaml +8 -3
- nullforge-0.3.1/.release-please-manifest.json +3 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/CHANGELOG.md +32 -0
- nullforge-0.3.1/PKG-INFO +48 -0
- nullforge-0.3.1/README.md +29 -0
- nullforge-0.3.1/docs/concepts/architecture.md +61 -0
- nullforge-0.3.1/docs/concepts/inventories.md +152 -0
- nullforge-0.3.1/docs/concepts/molds.md +52 -0
- nullforge-0.3.1/docs/concepts/runes.md +35 -0
- nullforge-0.3.1/docs/contributing/conventions.md +64 -0
- nullforge-0.3.1/docs/contributing/development.md +26 -0
- nullforge-0.3.1/docs/features/base.md +71 -0
- nullforge-0.3.1/docs/features/containers.md +51 -0
- nullforge-0.3.1/docs/features/dns.md +57 -0
- nullforge-0.3.1/docs/features/haproxy.md +32 -0
- nullforge-0.3.1/docs/features/index.md +27 -0
- nullforge-0.3.1/docs/features/monitoring.md +60 -0
- nullforge-0.3.1/docs/features/netsec.md +95 -0
- nullforge-0.3.1/docs/features/profiles.md +55 -0
- nullforge-0.3.1/docs/features/telemt.md +61 -0
- nullforge-0.3.1/docs/features/tor.md +29 -0
- nullforge-0.3.1/docs/features/users.md +54 -0
- nullforge-0.3.1/docs/features/warp.md +53 -0
- nullforge-0.3.1/docs/features/xray.md +30 -0
- nullforge-0.3.1/docs/features/zerotrust.md +40 -0
- nullforge-0.3.1/docs/getting-started/cli.md +85 -0
- nullforge-0.3.1/docs/getting-started/installation.md +40 -0
- nullforge-0.3.1/docs/getting-started/quickstart.md +75 -0
- nullforge-0.3.1/docs/guides/custom-runes.md +79 -0
- nullforge-0.3.1/docs/guides/version-pinning.md +57 -0
- nullforge-0.3.1/docs/index.md +55 -0
- nullforge-0.3.1/nullforge/__init__.py +1 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/inventories/example.py +3 -3
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/runes/containers.py +81 -8
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/runes/profiles.py +11 -9
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/runes/xray.py +12 -5
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/smithy/cloudflare.py +3 -2
- nullforge-0.3.1/nullforge/smithy/http.py +186 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/smithy/install.py +2 -3
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/smithy/monitoring/nezha/deploy.py +2 -2
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/smithy/packages.py +1 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/smithy/versions.py +3 -3
- nullforge-0.3.1/pyproject.toml +141 -0
- nullforge-0.3.1/tests/__init__.py +0 -0
- nullforge-0.3.1/tests/cli/__init__.py +0 -0
- nullforge-0.3.1/tests/cli/components/__init__.py +0 -0
- nullforge-0.3.1/tests/cli/components/completion/__init__.py +0 -0
- nullforge-0.3.1/tests/cli/components/completion/test_controller.py +77 -0
- nullforge-0.3.1/tests/cli/components/completion/test_powershell.py +97 -0
- nullforge-0.3.1/tests/cli/components/foundry/__init__.py +0 -0
- nullforge-0.3.1/tests/cli/components/foundry/test_controller.py +141 -0
- nullforge-0.3.1/tests/cli/conftest.py +20 -0
- nullforge-0.3.1/tests/cli/core/__init__.py +0 -0
- nullforge-0.3.1/tests/cli/core/test_application.py +120 -0
- nullforge-0.3.1/tests/cli/test_types.py +127 -0
- nullforge-0.3.1/tests/conftest.py +50 -0
- nullforge-0.3.1/tests/test_foundry.py +56 -0
- nullforge-0.3.1/tests/test_foundry_cast.py +60 -0
- nullforge-0.3.1/tests/test_foundry_pyinfra.py +94 -0
- nullforge-0.3.1/tests/test_models_dns.py +42 -0
- nullforge-0.3.1/tests/test_molds_dns.py +50 -0
- nullforge-0.3.1/tests/test_molds_monitoring.py +78 -0
- nullforge-0.3.1/tests/test_molds_netsec.py +44 -0
- nullforge-0.3.1/tests/test_molds_telemt.py +65 -0
- nullforge-0.3.1/tests/test_molds_user.py +21 -0
- nullforge-0.3.1/tests/test_molds_utils.py +177 -0
- nullforge-0.3.1/tests/test_runes_dns.py +19 -0
- nullforge-0.3.1/tests/test_runes_meta.py +163 -0
- nullforge-0.3.1/tests/test_runes_misc.py +33 -0
- nullforge-0.3.1/tests/test_runes_netsec.py +204 -0
- nullforge-0.3.1/tests/test_runes_profiles.py +114 -0
- nullforge-0.3.1/tests/test_runes_telemt.py +105 -0
- nullforge-0.3.1/tests/test_runes_users.py +62 -0
- nullforge-0.3.1/tests/test_smithy_arch.py +53 -0
- nullforge-0.3.1/tests/test_smithy_github.py +153 -0
- nullforge-0.3.1/tests/test_smithy_http.py +312 -0
- nullforge-0.3.1/tests/test_smithy_install.py +119 -0
- nullforge-0.3.1/tests/test_smithy_network.py +128 -0
- nullforge-0.3.1/tests/test_smithy_nezha.py +44 -0
- nullforge-0.3.1/tests/test_smithy_nezha_dashboard.py +84 -0
- nullforge-0.3.1/tests/test_smithy_packages.py +44 -0
- nullforge-0.3.1/tests/test_smithy_service.py +61 -0
- nullforge-0.3.1/tests/test_smithy_sni.py +224 -0
- nullforge-0.3.1/tests/test_smithy_swap.py +54 -0
- nullforge-0.3.1/tests/test_smithy_system.py +101 -0
- nullforge-0.3.1/tests/test_smithy_versions.py +178 -0
- nullforge-0.3.1/tests/test_templates.py +19 -0
- nullforge-0.3.1/tests/test_templates_telemt.py +108 -0
- nullforge-0.3.1/uv.lock +1234 -0
- nullforge-0.3.1/zensical.toml +110 -0
- nullforge-0.2.0/.release-please-manifest.json +0 -3
- nullforge-0.2.0/PKG-INFO +0 -68
- nullforge-0.2.0/README.md +0 -52
- nullforge-0.2.0/nullforge/__init__.py +0 -1
- nullforge-0.2.0/nullforge/smithy/http.py +0 -85
- nullforge-0.2.0/pyproject.toml +0 -45
- nullforge-0.2.0/uv.lock +0 -662
- {nullforge-0.2.0 → nullforge-0.3.1}/.gitattributes +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/.github/CODEOWNERS +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/.github/ISSUE_TEMPLATE/BUG-REPORT.yaml +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yaml +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/.github/ISSUE_TEMPLATE/config.yaml +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/.github/labels.yaml +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/.github/release.yaml +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/.github/workflows/check-commit-message-pr.yaml +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/.github/workflows/label-prs.yaml +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/.github/workflows/labels.yaml +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/.gitignore +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/CODE_OF_CONDUCT.md +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/LICENSE +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/__init__.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/__main__.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/app.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/components/__init__.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/components/completion/__init__.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/components/completion/component.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/components/completion/controller.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/components/completion/errors.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/components/completion/powershell.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/components/foundry/__init__.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/components/foundry/component.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/components/foundry/controller.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/components/foundry/errors.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/components/runes/__init__.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/components/runes/component.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/components/runes/controller.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/core/__init__.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/core/application.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/core/component.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/core/controller.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/core/errors.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/core/types.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/display.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/cli/types.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/foundry/README.md +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/foundry/__init__.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/foundry/_pyinfra.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/foundry/cast.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/foundry/full_cast.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/inventories/README.md +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/models/__init__.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/models/containers.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/models/dns.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/models/monitoring/__init__.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/models/monitoring/base.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/models/monitoring/nezha.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/models/netsec.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/models/profiles.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/models/system.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/models/users.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/models/warp.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/models/zerotrust.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/molds/__init__.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/molds/base_mold.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/molds/containers.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/molds/defaults.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/molds/dns.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/molds/features.py +1 -1
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/molds/haproxy.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/molds/monitoring/__init__.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/molds/monitoring/nezha.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/molds/netsec.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/molds/profiles.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/molds/system.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/molds/telemt.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/molds/tor.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/molds/user.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/molds/utils.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/molds/warp.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/molds/xray.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/molds/zerotrust.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/runes/__init__.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/runes/base.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/runes/dns.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/runes/haproxy.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/runes/monitoring.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/runes/netsec.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/runes/prepare.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/runes/telemt.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/runes/tor.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/runes/users.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/runes/warp.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/runes/zerotrust.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/smithy/__init__.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/smithy/admin.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/smithy/arch.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/smithy/blocky.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/smithy/github.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/smithy/monitoring/__init__.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/smithy/monitoring/nezha/__init__.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/smithy/monitoring/nezha/agent.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/smithy/monitoring/nezha/dashboard.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/smithy/network.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/smithy/service.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/smithy/sni.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/smithy/swap.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/smithy/system.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/__init__.py +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/cloudflared/tunnel.yml.j2 +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/dns/blocky.yaml.j2 +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/dns/dns.yaml.j2 +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/dns/resolv.conf.j2 +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/dns/resolved.conf.j2 +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/etc/default/zramswap.j2 +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/nvim/nvim_patch.lua.j2 +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/profiles/direnv.toml +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/profiles/starship.toml +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/profiles/tmux.conf +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/profiles/zshrc.j2 +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/scripts/telemt-synfix.sh +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/scripts/teleproxy-warp.sh +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/scripts/warp-v6-policy.sh +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/scripts/zt-tunnel-warp.sh +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/systemd/blocky.service.j2 +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/systemd/cloudflare-tunnel.service.j2 +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/systemd/cloudflare-warp.service.j2 +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/systemd/dns-internal.service.j2 +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/systemd/telemt.service.j2 +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/telemt/telemt.toml.j2 +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/nullforge/templates/tor/torrc.j2 +0 -0
- {nullforge-0.2.0 → nullforge-0.3.1}/release-please-config.json +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,21 +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
|
|
33
|
+
- name: Run quality checks
|
|
34
|
+
run: uv run poe check
|
|
43
35
|
|
|
44
36
|
hooks:
|
|
45
37
|
name: 🪝 prek hooks
|
|
@@ -50,7 +42,7 @@ jobs:
|
|
|
50
42
|
with:
|
|
51
43
|
persist-credentials: false
|
|
52
44
|
|
|
53
|
-
- uses: astral-sh/setup-uv@
|
|
45
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
54
46
|
with:
|
|
55
47
|
enable-cache: true
|
|
56
48
|
cache-dependency-glob: uv.lock
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: 📚 Docs build check
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
paths:
|
|
8
|
+
- "docs/**"
|
|
9
|
+
- "zensical.toml"
|
|
10
|
+
- "pyproject.toml"
|
|
11
|
+
- "uv.lock"
|
|
12
|
+
- ".github/workflows/ci-docs.yaml"
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
concurrency:
|
|
18
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
19
|
+
cancel-in-progress: true
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
build:
|
|
23
|
+
name: 📚 Build documentation
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
timeout-minutes: 10
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
28
|
+
with:
|
|
29
|
+
persist-credentials: false
|
|
30
|
+
|
|
31
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
32
|
+
with:
|
|
33
|
+
enable-cache: true
|
|
34
|
+
cache-dependency-glob: uv.lock
|
|
35
|
+
|
|
36
|
+
- name: Build documentation
|
|
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
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
name: 📚 Deploy documentation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
paths:
|
|
8
|
+
- "docs/**"
|
|
9
|
+
- "zensical.toml"
|
|
10
|
+
- "pyproject.toml"
|
|
11
|
+
- "uv.lock"
|
|
12
|
+
- ".github/workflows/deploy-docs.yaml"
|
|
13
|
+
workflow_dispatch:
|
|
14
|
+
|
|
15
|
+
concurrency:
|
|
16
|
+
group: pages
|
|
17
|
+
cancel-in-progress: false
|
|
18
|
+
|
|
19
|
+
permissions:
|
|
20
|
+
contents: read
|
|
21
|
+
|
|
22
|
+
jobs:
|
|
23
|
+
build:
|
|
24
|
+
name: 📚 Build documentation
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
timeout-minutes: 10
|
|
27
|
+
permissions:
|
|
28
|
+
contents: read
|
|
29
|
+
pages: read
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
32
|
+
with:
|
|
33
|
+
persist-credentials: false
|
|
34
|
+
|
|
35
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
36
|
+
with:
|
|
37
|
+
enable-cache: true
|
|
38
|
+
cache-dependency-glob: uv.lock
|
|
39
|
+
|
|
40
|
+
- name: Build documentation
|
|
41
|
+
run: uv run poe docs
|
|
42
|
+
|
|
43
|
+
- uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
|
|
44
|
+
|
|
45
|
+
- uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
|
|
46
|
+
with:
|
|
47
|
+
path: site
|
|
48
|
+
|
|
49
|
+
deploy:
|
|
50
|
+
name: 🚀 Deploy to GitHub Pages
|
|
51
|
+
needs: build
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
timeout-minutes: 10
|
|
54
|
+
permissions:
|
|
55
|
+
pages: write
|
|
56
|
+
id-token: write
|
|
57
|
+
environment:
|
|
58
|
+
name: github-pages
|
|
59
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
60
|
+
steps:
|
|
61
|
+
- id: deployment
|
|
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
|
|
|
@@ -95,7 +95,7 @@ jobs:
|
|
|
95
95
|
path: dist/
|
|
96
96
|
|
|
97
97
|
- name: Generate artifact attestations
|
|
98
|
-
uses: actions/attest-build-provenance@
|
|
98
|
+
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
|
99
99
|
with:
|
|
100
100
|
subject-path: dist/*
|
|
101
101
|
|
|
@@ -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]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"gitignore": true,
|
|
3
|
+
|
|
4
|
+
// Generated, boilerplate and vendored - not authored to this style.
|
|
5
|
+
"ignores": [
|
|
6
|
+
"CHANGELOG.md",
|
|
7
|
+
".github/PULL_REQUEST_TEMPLATE.md",
|
|
8
|
+
".github/ISSUE_TEMPLATE/**",
|
|
9
|
+
],
|
|
10
|
+
|
|
11
|
+
"config": {
|
|
12
|
+
"default": true,
|
|
13
|
+
|
|
14
|
+
// Prose is one sentence per line; tables pad cells but not the delimiter row.
|
|
15
|
+
"MD013": false,
|
|
16
|
+
"MD060": false,
|
|
17
|
+
|
|
18
|
+
"MD046": false,
|
|
19
|
+
|
|
20
|
+
// mkdocs admonition blocks
|
|
21
|
+
"MD033": {
|
|
22
|
+
"allowed_elements": ["div"],
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
}
|
|
@@ -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.
|
|
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]
|
|
@@ -40,3 +40,8 @@ repos:
|
|
|
40
40
|
- id: python-no-log-warn
|
|
41
41
|
- id: python-use-type-annotations
|
|
42
42
|
- id: text-unicode-replacement-char
|
|
43
|
+
|
|
44
|
+
- repo: https://github.com/DavidAnson/markdownlint-cli2
|
|
45
|
+
rev: v0.23.2
|
|
46
|
+
hooks:
|
|
47
|
+
- id: markdownlint-cli2
|
|
@@ -1,5 +1,37 @@
|
|
|
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
|
+
|
|
16
|
+
## [0.3.0](https://github.com/wlix13/NullForge/compare/v0.2.0...v0.3.0) (2026-08-28)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### ⚠ BREAKING CHANGES
|
|
20
|
+
|
|
21
|
+
* **molds:** deploy warp before other features
|
|
22
|
+
* **smithy:** fall back to WARP interface for filtered downloads
|
|
23
|
+
|
|
24
|
+
### Features
|
|
25
|
+
|
|
26
|
+
* **molds:** deploy warp before other features ([a97cbfe](https://github.com/wlix13/NullForge/commit/a97cbfe9020bf0708acec4adf370cfa7113ea964))
|
|
27
|
+
* **smithy:** fall back to WARP interface for filtered downloads ([25f0b28](https://github.com/wlix13/NullForge/commit/25f0b28cb7d0efb89cf7710aa922d52a9651c985))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Documentation
|
|
31
|
+
|
|
32
|
+
* describe WARP download fallback ([8b57441](https://github.com/wlix13/NullForge/commit/8b57441c8ecc234db4f9131e5ab9a603e3384b4c))
|
|
33
|
+
* **site:** add documentation ([d6b8575](https://github.com/wlix13/NullForge/commit/d6b857541d8ed35a5fe8fa3a32cdef95d8d8bf8c))
|
|
34
|
+
|
|
3
35
|
## [0.2.0](https://github.com/wlix13/NullForge/compare/v0.1.0...v0.2.0) (2026-07-25)
|
|
4
36
|
|
|
5
37
|
|
nullforge-0.3.1/PKG-INFO
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: nullforge
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: Forge the server's baseline from null
|
|
5
|
+
Author-email: wlix13 <github@wlix13.dev>
|
|
6
|
+
Maintainer-email: wlix13 <github@wlix13.dev>
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
12
|
+
Requires-Python: >=3.13
|
|
13
|
+
Requires-Dist: click
|
|
14
|
+
Requires-Dist: pydantic
|
|
15
|
+
Requires-Dist: pyinfra
|
|
16
|
+
Requires-Dist: rich
|
|
17
|
+
Requires-Dist: rich-click
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# NullForge
|
|
21
|
+
|
|
22
|
+
[](https://pypi.org/project/nullforge/)
|
|
23
|
+

|
|
24
|
+

|
|
25
|
+

|
|
26
|
+

|
|
27
|
+

|
|
28
|
+

|
|
29
|
+
[](https://wlix13.github.io/NullForge/)
|
|
30
|
+
|
|
31
|
+
Forge the server's baseline from null - an infrastructure-as-code framework built on [pyinfra](https://pyinfra.com), themed around a blacksmith's forge: **inventories** define hosts, **molds** shape the configuration, **runes** are idempotent operation sets, the **foundry** casts them onto targets, and the **smithy** holds cross-distro helpers.
|
|
32
|
+
|
|
33
|
+
> [!WARNING]
|
|
34
|
+
> **NullForge is in active development.** Until the `v1.0.0` release, the CLI, mold schemas and deploy behaviour may change at any time - breaking changes can land in **any** release, including patch versions. Pin an exact version (e.g. `nullforge==0.2.0`) and check the [release notes](https://github.com/wlix13/NullForge/releases) before upgrading.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uv tool install nullforge
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Documentation
|
|
43
|
+
|
|
44
|
+
Everything else - getting started, concepts, the full feature reference, and guides - lives at **[wlix13.github.io/NullForge](https://wlix13.github.io/NullForge/)**.
|
|
45
|
+
|
|
46
|
+
## Contributing
|
|
47
|
+
|
|
48
|
+
See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for the full setup, commit conventions and pull request flow.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# NullForge
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/nullforge/)
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
[](https://wlix13.github.io/NullForge/)
|
|
11
|
+
|
|
12
|
+
Forge the server's baseline from null - an infrastructure-as-code framework built on [pyinfra](https://pyinfra.com), themed around a blacksmith's forge: **inventories** define hosts, **molds** shape the configuration, **runes** are idempotent operation sets, the **foundry** casts them onto targets, and the **smithy** holds cross-distro helpers.
|
|
13
|
+
|
|
14
|
+
> [!WARNING]
|
|
15
|
+
> **NullForge is in active development.** Until the `v1.0.0` release, the CLI, mold schemas and deploy behaviour may change at any time - breaking changes can land in **any** release, including patch versions. Pin an exact version (e.g. `nullforge==0.2.0`) and check the [release notes](https://github.com/wlix13/NullForge/releases) before upgrading.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv tool install nullforge
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Documentation
|
|
24
|
+
|
|
25
|
+
Everything else - getting started, concepts, the full feature reference, and guides - lives at **[wlix13.github.io/NullForge](https://wlix13.github.io/NullForge/)**.
|
|
26
|
+
|
|
27
|
+
## Contributing
|
|
28
|
+
|
|
29
|
+
See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for the full setup, commit conventions and pull request flow.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
NullForge is a thin, opinionated layer over pyinfra.
|
|
4
|
+
Every layer has one job, and imports only flow downward.
|
|
5
|
+
|
|
6
|
+
## Data flow
|
|
7
|
+
|
|
8
|
+
```mermaid
|
|
9
|
+
flowchart TB
|
|
10
|
+
subgraph plan [Control node]
|
|
11
|
+
CLI[cli - nullforge cast] --> FY["foundry - full_cast.py / cast.py"]
|
|
12
|
+
INV[inventories] --> FY
|
|
13
|
+
FY --> RN["runes/*.py"]
|
|
14
|
+
RN --> SM[smithy]
|
|
15
|
+
RN --> TP["templates (Jinja2)"]
|
|
16
|
+
MD["molds (Pydantic)"] --> INV
|
|
17
|
+
MD --> RN
|
|
18
|
+
ML["models (domain types)"] --> MD
|
|
19
|
+
end
|
|
20
|
+
RN -->|pyinfra operations over SSH| HOSTS[(Target hosts)]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
1. **Inventories** define hosts and attach `system` and `features` data, built by [merging layers](inventories.md) over the defaults.
|
|
24
|
+
2. The **foundry** is the deploy entry point.
|
|
25
|
+
`full_cast.py` coerces inventory data through the molds, always includes `prepare` then `base`, and then includes the rune of every active feature.
|
|
26
|
+
`cast.py` is the selective variant used by `nullforge cast -r ...`.
|
|
27
|
+
3. **Runes** are self-contained pyinfra operation sets - one file per concern.
|
|
28
|
+
They read validated configuration from `host.data` and emit idempotent operations.
|
|
29
|
+
4. **Molds** are Pydantic schemas for all configuration; [`FeaturesMold`](molds.md) composes the per-feature sub-molds.
|
|
30
|
+
5. **Models** hold pure domain types and constants consumed by molds.
|
|
31
|
+
6. The **smithy** provides cross-distro abstractions: package-name mapping (apt/dnf), release-binary installs with checksum verification, version pinning, networking facts, swap, service users.
|
|
32
|
+
7. **Templates** are Jinja2 files for systemd units, service configs, and shell profiles.
|
|
33
|
+
|
|
34
|
+
## Layer contracts
|
|
35
|
+
|
|
36
|
+
The layering is enforced by import contracts:
|
|
37
|
+
|
|
38
|
+
| Contract | Meaning |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| Deploy spine | `cli` -> `foundry` -> `runes` -> `smithy` -> `molds`/`templates` -> `models`; a layer may import only layers below it |
|
|
41
|
+
| Models are pure | `models` imports no other NullForge package |
|
|
42
|
+
| Templates are a leaf | `templates` imports no other NullForge package, `models` included - its spine position only says which layers may import *it* |
|
|
43
|
+
| Molds describe, never provision | `molds` cannot import runes, smithy, or templates |
|
|
44
|
+
| Runes are independent | no rune imports another rune |
|
|
45
|
+
|
|
46
|
+
Why rune independence matters - and how runes coordinate without it - is covered in [Runes](runes.md#independence).
|
|
47
|
+
|
|
48
|
+
## Execution model
|
|
49
|
+
|
|
50
|
+
pyinfra runs in two phases:
|
|
51
|
+
|
|
52
|
+
1. **Plan** - fact gathering and operation collection on the control node.
|
|
53
|
+
Python-level branching (`if host.get_fact(...)`) happens here.
|
|
54
|
+
2. **Execute** - the collected operations run against each host, in a deterministic order shared by all hosts.
|
|
55
|
+
|
|
56
|
+
The [conventions](../contributing/conventions.md) around `host.loop` and change detection exist to keep that shared ordering stable.
|
|
57
|
+
|
|
58
|
+
## The CLI wrapper
|
|
59
|
+
|
|
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 is stock pyinfra, which is why unknown CLI options are proxied through verbatim.
|