Skip to content

feat(packaging): native OS install packages — .msi / .pkg / .deb (#503)#33

Merged
MichaelTaylor3d merged 1 commit into
mainfrom
feat/native-install-packages
Jul 13, 2026
Merged

feat(packaging): native OS install packages — .msi / .pkg / .deb (#503)#33
MichaelTaylor3d merged 1 commit into
mainfrom
feat/native-install-packages

Conversation

@MichaelTaylor3d

Copy link
Copy Markdown
Contributor

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 consumes run-service, dig-node open, and the ProgramData state-dir/ACL from that PR. Review/merge #32 first; this base auto-retargets to main when #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 the chia:// handler → dig-node open (#389), create the restrictive machine-wide state dir (#501), and set the dig.local127.0.0.2 hosts entry via the new idempotent, no-shell dig-node ensure-hosts.

  • Windows .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\DigNode with a DACL that breaks inheritance and grants only SYSTEM + Administrators (no Users) — the token is not world-readable (#501); chia:// under HKLM\Software\Classes\chia"…\dig-node.exe" open "%1"; system PATH append; dig-node ensure-hosts as a deferred SYSTEM (no-shell) custom action; UpgradeCode + MajorUpgrade for clean upgrades.
  • macOS .pkg (packaging/macos/, universal arm64+x86_64): LaunchDaemon (RunAtLoad+KeepAlive); a tiny AppleScript app declaring CFBundleURLTypes for chia that forwards to dig-node open (macOS delivers URL opens via Apple Events, not argv, so a CLI can't be the handler directly); postinstall creates the state dir + launchctl bootstraps the daemon.
  • Ubuntu .deb (packaging/linux/): systemd system unit (Restart=on-failure); .desktop x-scheme-handler/chia as the system default; postinst creates /var/lib/dig-node (root 0700) + hosts entry + enable/start; prerm stop/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)

  • PR → builds all three (validation only; release.yml is tag/main-only and doesn't build packages, so this is the PR gate for package definitions). This PR's own run exercises the WiX/pkgbuild/dpkg-deb builds.
  • tag v* → builds all three and attaches them to the GitHub Release (appends to release.yml's binary release; does not regenerate notes). The .deb is the apt.dig.net-ingested asset.

New code

dig-node ensure-hosts (hidden subcommand + hosts.rs): idempotent, cross-platform, no-shell dig.local127.0.0.2 hosts registration reused by all three installers. Pure has_entry/with_entry helpers unit-tested (4 tests). InvalidInputUSAGE exit already added in #32.

Scheme-registration decision (flagged for review)

All three register chia:// only. urn:dig:chia: is accepted by dig-node open but is NOT registered as a global OS handler — registering the urn scheme would hijack EVERY urn: link on the machine (a real harm). This is the deliberate, non-destructive choice.

Verification status (honest)

  • .deb: build-deb.sh uses standard dpkg-deb; CI validates dpkg-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

@MichaelTaylor3d MichaelTaylor3d deleted the branch main July 13, 2026 05:34
…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>
@MichaelTaylor3d MichaelTaylor3d changed the base branch from feat/state-dir-and-open to main July 13, 2026 05:59
@MichaelTaylor3d MichaelTaylor3d force-pushed the feat/native-install-packages branch from 219b4c2 to 905ff24 Compare July 13, 2026 05:59
Comment on lines +35 to +75
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:
Comment on lines +76 to +120
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:
Comment on lines +121 to +176
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:
@MichaelTaylor3d

Copy link
Copy Markdown
Contributor Author

Rebuilt this branch cleanly on top of current main (v0.27.0).

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 main, the branch carried stale duplicates of #32's src (state.rs/control.rs/server.rs/service.rs/open.rs/pairing.rs/meta.rs/SPEC) that conflicted with main. Deleting the #32 base branch on merge also auto-closed this PR.

What I did: rebuilt a fresh commit containing ONLY the genuine packaging delta on top of origin/main, restored + reopened this PR, retargeted its base from the (now-deleted) feat/state-dir-and-open to main, and force-pushed. Discussion preserved.

Clean diff — 16 files, +853/-2 (no #32 revert; the #32 src files are byte-identical to main):

  • packaging/** — Windows .wxs, macOS build-pkg.sh/plist/AppleScript/pre+postinstall, Linux build-deb.sh/.desktop/systemd unit
  • .github/workflows/package.yml — builds .deb/.pkg/.msi on PRs, attaches on v* tags
  • crates/dig-node-service/src/hosts.rs (new) + the ensure-hosts subcommand wiring in lib.rs/main.rs
  • SPEC.md §9.7 (additive), Cargo.toml/Cargo.lock 0.27.0 → 0.28.0

Gates: fmt / clippy / test+coverage / version-increment / commitlint all green; all three package build jobs pass (deb 1m43s, msi 4m32s, pkg 3m22s). mergeStateStatus: CLEAN.

Follow-up (non-blocking): the macOS postinstall doesn't register the dig.local127.0.0.2 hosts entry (Windows uses the ensure-hosts custom action, the .deb postinst writes it inline, macOS does neither), so http://dig.local won't resolve on macOS — the client falls back to localhost. SPEC §9.7 states all three converge on ensure-hosts. Preserved as-authored here to keep this a pure rebase; worth a one-line dig-node ensure-hosts || true in the macOS postinstall as a separate change.

@MichaelTaylor3d MichaelTaylor3d merged commit 1eeafb7 into main Jul 13, 2026
13 checks passed
@MichaelTaylor3d MichaelTaylor3d deleted the feat/native-install-packages branch July 13, 2026 06:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants