feat(packaging): native OS install packages — .msi / .pkg / .deb (#503)#33
Conversation
…e CI (#503) Add the native OS install packages that ARE the DIG node install architecture: the dig-installer just fetches + runs the right one. Each package installs the binary, registers the OS service net.dignetwork.dig-node, the chia:// scheme handler (-> dig-node open, #389), the restrictive machine-wide state dir (#501), and the dig.local -> 127.0.0.2 hosts entry. - Windows .msi (WiX v5): ServiceInstall/ServiceControl, restrictive DACL on C:\ProgramData\DigNode, chia:// under HKLM\Software\Classes, PATH append, and a deferred ensure-hosts custom action. - macOS .pkg (universal): LaunchDaemon + AppleScript chia:// handler + postinstall state dir/daemon/LaunchServices registration. - Ubuntu .deb: systemd unit + .desktop chia:// handler + postinst state dir/hosts/ enable, apt-correct stable filename + control metadata for apt.dig.net ingest. - New `dig-node ensure-hosts` subcommand (hidden, no-shell, idempotent) that the packages call to register the dig.local hosts entry. - package.yml CI builds all three on PRs (validation) and attaches them to the GitHub Release on v* tags. - SPEC §9.7 documents the packages; bump 0.27.0 -> 0.28.0 (minor: new capability). Co-Authored-By: Claude <noreply@anthropic.com>
219b4c2 to
905ff24
Compare
| name: build .deb (linux-x64) | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| persist-credentials: false | ||
| - name: Install Rust | ||
| run: | | ||
| rustup toolchain install stable | ||
| rustup default stable | ||
| - uses: actions/cache@v4 | ||
| with: | ||
| path: | | ||
| ~/.cargo/registry/index | ||
| ~/.cargo/registry/cache | ||
| ~/.cargo/git/db | ||
| target | ||
| key: ${{ runner.os }}-pkg-deb-${{ hashFiles('**/Cargo.lock') }} | ||
| restore-keys: ${{ runner.os }}-pkg-deb- | ||
| - name: Resolve version | ||
| id: ver | ||
| run: | | ||
| if [ "${GITHUB_REF_TYPE}" = "tag" ]; then V="${GITHUB_REF_NAME#v}"; else | ||
| V="$(grep -m1 '^version' Cargo.toml | sed -E 's/.*"(.*)".*/\1/')"; fi | ||
| echo "v=$V" >> "$GITHUB_OUTPUT" | ||
| - name: Build release binary | ||
| run: cargo build --release --locked --bin dig-node | ||
| - name: Build .deb | ||
| run: bash packaging/linux/build-deb.sh target/release/dig-node "${{ steps.ver.outputs.v }}" amd64 dist | ||
| - name: Validate .deb metadata | ||
| run: | | ||
| dpkg-deb --info dist/*.deb | ||
| dpkg-deb --contents dist/*.deb | ||
| - uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: dig-node-deb | ||
| path: dist/*.deb | ||
| if-no-files-found: error | ||
|
|
||
| # ---- macOS .pkg (universal) ---------------------------------------------- | ||
| pkg: |
| name: build .pkg (macos-universal) | ||
| runs-on: macos-14 | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| persist-credentials: false | ||
| - name: Install Rust + targets | ||
| run: | | ||
| rustup toolchain install stable | ||
| rustup default stable | ||
| rustup target add aarch64-apple-darwin x86_64-apple-darwin | ||
| - uses: actions/cache@v4 | ||
| with: | ||
| path: | | ||
| ~/.cargo/registry/index | ||
| ~/.cargo/registry/cache | ||
| ~/.cargo/git/db | ||
| target | ||
| key: ${{ runner.os }}-pkg-mac-${{ hashFiles('**/Cargo.lock') }} | ||
| restore-keys: ${{ runner.os }}-pkg-mac- | ||
| - name: Resolve version | ||
| id: ver | ||
| run: | | ||
| if [ "${GITHUB_REF_TYPE}" = "tag" ]; then V="${GITHUB_REF_NAME#v}"; else | ||
| V="$(grep -m1 '^version' Cargo.toml | sed -E 's/.*"(.*)".*/\1/')"; fi | ||
| echo "v=$V" >> "$GITHUB_OUTPUT" | ||
| - name: Build both arches + lipo a universal binary | ||
| run: | | ||
| cargo build --release --locked --target aarch64-apple-darwin --bin dig-node | ||
| cargo build --release --locked --target x86_64-apple-darwin --bin dig-node | ||
| mkdir -p dist | ||
| lipo -create -output dist/dig-node \ | ||
| target/aarch64-apple-darwin/release/dig-node \ | ||
| target/x86_64-apple-darwin/release/dig-node | ||
| lipo -info dist/dig-node | ||
| - name: Build .pkg | ||
| run: bash packaging/macos/build-pkg.sh dist/dig-node "${{ steps.ver.outputs.v }}" dist | ||
| - uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: dig-node-pkg | ||
| path: dist/*.pkg | ||
| if-no-files-found: error | ||
|
|
||
| # ---- Windows .msi --------------------------------------------------------- | ||
| msi: |
| name: build .msi (windows-x64) | ||
| runs-on: windows-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| persist-credentials: false | ||
| - name: Install Rust | ||
| shell: bash | ||
| run: | | ||
| rustup toolchain install stable | ||
| rustup default stable | ||
| - uses: actions/cache@v4 | ||
| with: | ||
| path: | | ||
| ~/.cargo/registry/index | ||
| ~/.cargo/registry/cache | ||
| ~/.cargo/git/db | ||
| target | ||
| key: ${{ runner.os }}-pkg-msi-${{ hashFiles('**/Cargo.lock') }} | ||
| restore-keys: ${{ runner.os }}-pkg-msi- | ||
| - name: Resolve version | ||
| id: ver | ||
| shell: bash | ||
| run: | | ||
| if [ "${GITHUB_REF_TYPE}" = "tag" ]; then V="${GITHUB_REF_NAME#v}"; else | ||
| V="$(grep -m1 '^version' Cargo.toml | sed -E 's/.*"(.*)".*/\1/')"; fi | ||
| echo "v=$V" >> "$GITHUB_OUTPUT" | ||
| - name: Build release binary | ||
| run: cargo build --release --locked --bin dig-node | ||
| - name: Install WiX | ||
| shell: bash | ||
| run: | | ||
| # Pin WiX v5.0.2 — the last release BEFORE the v6+ Open Source Maintenance Fee (OSMF) | ||
| # EULA gate (WIX7015), which cannot be accepted non-interactively in CI. The .wxs uses the | ||
| # v4 schema namespace, which v5 supports. Pin the extension to the SAME version so the | ||
| # WixToolset.Util.wixext major matches the toolset (a mismatched ext major fails to load). | ||
| dotnet tool install --global wix --version 5.0.2 | ||
| wix extension add -g WixToolset.Util.wixext/5.0.2 | ||
| - name: Build .msi | ||
| shell: bash | ||
| run: | | ||
| mkdir -p dist | ||
| wix build packaging/windows/dig-node.wxs \ | ||
| -ext WixToolset.Util.wixext \ | ||
| -arch x64 \ | ||
| -d Version="${{ steps.ver.outputs.v }}" \ | ||
| -d BinDir="target/release" \ | ||
| -o "dist/dig-node-${{ steps.ver.outputs.v }}-windows-x64.msi" | ||
| - uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: dig-node-msi | ||
| path: dist/*.msi | ||
| if-no-files-found: error | ||
|
|
||
| # ---- Attach to the GitHub Release (tags only) ----------------------------- | ||
| publish: |
|
Rebuilt this branch cleanly on top of current Why: the branch was cut from an OLD state of the #32 state-dir work (before its token-custody hardening). Since #32 squash-merged to What I did: rebuilt a fresh commit containing ONLY the genuine packaging delta on top of Clean diff — 16 files, +853/-2 (no #32 revert; the #32 src files are byte-identical to
Gates: fmt / clippy / test+coverage / version-increment / commitlint all green; all three package build jobs pass (deb 1m43s, msi 4m32s, pkg 3m22s). Follow-up (non-blocking): the macOS |
TLDR
Implements #503 — the native OS install packages that ARE the install architecture: a Windows .msi, macOS .pkg, and Ubuntu .deb, each installing the binary + registering the OS service + the
chia://scheme handler + the machine-wide state dir, built by CI and attached to the GitHub Release. The dig-installer just fetches + runs the right one.Stacked on #32 (
feat/state-dir-and-open) — it consumesrun-service,dig-node open, and the ProgramData state-dir/ACL from that PR. Review/merge #32 first; this base auto-retargets tomainwhen #32 merges. Diff here is packaging-only.What each package does
All three: install the binary, register the OS service
net.dignetwork.dig-node(display "DIG NETWORK: NODE"), register thechia://handler →dig-node open(#389), create the restrictive machine-wide state dir (#501), and set thedig.local→127.0.0.2hosts entry via the new idempotent, no-shelldig-node ensure-hosts..msi(packaging/windows/dig-node.wxs, WiX v4/v5):ServiceInstall+ServiceControl(run-service, LocalSystem, auto-start, started-on-install, stopped+removed-on-uninstall);C:\ProgramData\DigNodewith a DACL that breaks inheritance and grants only SYSTEM + Administrators (no Users) — the token is not world-readable (#501);chia://underHKLM\Software\Classes\chia→"…\dig-node.exe" open "%1"; system PATH append;dig-node ensure-hostsas a deferred SYSTEM (no-shell) custom action;UpgradeCode+MajorUpgradefor clean upgrades..pkg(packaging/macos/, universal arm64+x86_64): LaunchDaemon (RunAtLoad+KeepAlive); a tiny AppleScript app declaringCFBundleURLTypesforchiathat forwards todig-node open(macOS delivers URL opens via Apple Events, not argv, so a CLI can't be the handler directly);postinstallcreates the state dir +launchctl bootstraps the daemon..deb(packaging/linux/): systemd system unit (Restart=on-failure);.desktopx-scheme-handler/chiaas the system default;postinstcreates/var/lib/dig-node(root0700) + hosts entry + enable/start;prermstop/disable.Package: dig-node,dig-node_<ver>_amd64.deb,Depends: libc6— apt-correct + stable so apt.dig.net ingests the Release asset (repo GPG-signed by apt.dig.net; the .deb needs no code-signing cert).CI (
.github/workflows/package.yml)v*→ builds all three and attaches them to the GitHub Release (appends to release.yml's binary release; does not regenerate notes). The.debis the apt.dig.net-ingested asset.New code
dig-node ensure-hosts(hidden subcommand +hosts.rs): idempotent, cross-platform, no-shelldig.local→127.0.0.2hosts registration reused by all three installers. Purehas_entry/with_entryhelpers unit-tested (4 tests).InvalidInput→USAGEexit already added in #32.Scheme-registration decision (flagged for review)
All three register
chia://only.urn:dig:chia:is accepted bydig-node openbut is NOT registered as a global OS handler — registering theurnscheme would hijack EVERYurn:link on the machine (a real harm). This is the deliberate, non-destructive choice.Verification status (honest)
.deb:build-deb.shuses standarddpkg-deb; CI validatesdpkg-deb --info/--contents. Buildable + inspectable in CI (Linux)..pkg/.msi: authored to spec; the CI jobs build them on real macOS/Windows runners (this PR's run is the first real build — I cannot run WiX/pkgbuild in my environment). The Windows ProgramData DACL and the macOS LaunchServices handler registration should be adversarially verified on real boxes — exactly the surface flagged for your review. Any CI build failures will iterate here.Cross-repo (orchestrator owns SYSTEM.md)
Please add to SYSTEM.md: the native-package contract — package artifact names (
dig-node-<ver>-windows-x64.msi,dig-node-<ver>-macos.pkg,dig-node_<ver>_amd64.deb), the service id/display (net.dignetwork.dig-node/ "DIG NETWORK: NODE"), scheme→dig-node open, and the state-dir/ACL — as the shared contract dig-installer + apt.dig.net consume.Version
0.27.0 → 0.28.0(minor; native packages are a new capability). Stacked on #32's 0.27.0.Do NOT merge yet
Merge #32 first. Then adversarially verify the service-install + scheme-handler + ProgramData-ACL security surface.
🤖 Generated with Claude Code