# Shipping an Igneum Miner version (packaging/README-ship.md) One command cuts a version for both platforms. 4 October 2026: 0.3.1, 0.3.2 and 0.3.3 each took eight hand steps and an hour; `tools/ship-app.mjs` is those steps in order, each one checked and resumable. node tools/ship-app.mjs 0.3.4 --node vendor/igneum-node-v4 --notes "one line for the changelog" Add `--dry-run` first: it reads everything, prints the plan and writes nothing. ## What it does | Step | What happens | Skips itself when | |---|---|---| | preflight | this tree clean and on master, the fork worktree clean (`--node-commit ` pins it), tools, binaries, secrets present, gh account, what is live | never (reads only) | | bump | the six version files, written by one function and read back | the files already say the version | | inputs | `packaging/windows/push-inputs.sh` with the fork's Windows exes (`IGNEUM_WIN_RELEASE`, `IGNEUM_NODE_SRC`) | the live `payload-inputs.json` carries these exact files from this fork commit | | commit | `Igneum Miner : `, push master (the push starts the Windows build) | committed and on origin/master | | ci | the `windows.yml` run for that commit (dispatched when the push started none), polled every 30 s | a green run for the commit exists | | fetch | `packaging/windows/fetch-ci-artifacts.sh `: installer and payload zip into the downloads folder | the installer from that run is there | | dmg | `packaging/mac/build-dmg.sh` under `tools/lock/with-lock.sh build` | the DMG is newer than the bump (`--rebuild` forces) | | copy | the DMG into the downloads folder | same sha256 already there | | manifest | `packaging/ota/publish-manifest.sh --no-deploy`: signed, signature verified locally | never (cheap) | | deploy | the downloads folder, one Vercel deploy for the files and the manifest together | never | | verify | HEAD and GET of the DMG, the installer and the zip (size and sha256 against the local copies); the live manifest through `igneum-ota-sign verify` | never | | console | one `build` item on the console (version, sizes, hashes, run, commit), then `sync-dl` | never (upsert on `ship:`) | The six version files: `app/igneum-app/Cargo.toml`, `app/igneum-app/Cargo.lock`, `app/windows/version.h`, `app/igneum-app/resources/igneum-app.rc`, `packaging/windows/Igneum-Miner.iss`, `packaging/mac/app/Info.plist`. `node tools/ship-app.mjs --check` says whether they agree; `--self-test` runs the bump on a scratch copy. ## Flags | Flag | Meaning | |---|---| | `--node ` | the igneum-node worktree the node and miner were built from (required); binaries from its `target-integration/`, else `target/` | | `--notes "..."` | the manifest's changelog line and the commit message | | `--dry-run` | reads only, prints the plan (exit 1 when preflight would stop the real run) | | `--from ` | resume at that step (preflight runs again first); the failure message prints this command | | `--skip-windows`, `--skip-mac` | one platform only (the other entry is carried over when the live manifest is the same version) | | `--node-commit ` | the fork must be on this commit | | `--win-release `, `--mac-release ` | other binary folders | | `--min-supported`, `--activation-height`, `--deadline-note`, `--channel` | passed to `publish-manifest.sh` | | `--rebuild` | build the DMG again even when a fresh one exists | | `--branch ` | accept another branch than master (a dry run from a worktree; the Windows build still runs on pushes to master) | | `--public` | the manifest step also publishes the version into `dl/public/` (no token in any URL; its own signed manifest; the `/public/` aliases rewritten), the same deploy carries it, verify checks every public file and alias. The token folders are untouched | ## The public downloads path (5 October 2026, testnet launch) `dl/public/` in the downloads folder holds only the current installers, the HiveOS package and the two signed manifests (app and wallet) with URLs under `https://dl.igneum.network/dl/public/`, plus an unsigned index `igneum-downloads.json` that the site build reads. Four stable aliases are rewrites in the downloads folder's `vercel.json`, written from what the folder holds, so they follow every release by themselves: | Alias | Rewrites to | |---|---| | `https://dl.igneum.network/public/igneum-miner-windows.exe` | `dl/public/Igneum-Miner-Setup-.exe` | | `https://dl.igneum.network/public/igneum-miner-mac.dmg` | `dl/public/Igneum-Miner-.dmg` | | `https://dl.igneum.network/public/igneum-miner-hive.tar.gz` | `dl/public/igneum-hive-.tar.gz` | | `https://dl.igneum.network/public/igneum-wallet-mac.dmg` | `dl/public/Igneum-Wallet-.dmg` | `packaging/ota/publish-public.sh --app | --wallet | --hive [--deploy] [--verify] [--dry-run]` does the work; `publish-manifest.sh --public` and `ship-app.mjs --public` call it, so every future release lands there too. The HiveOS package comes from `packaging/hive/make-hive-package.sh` (Linux node and miner from a PC `build` job, the two GPU workers from `infra/cross/build-workers-linux.sh`) and is published with `--hive`. The site (`site/build.mjs`) reads the index at build time and stamps every download button with the version and size; `TESTNET_OPEN` there removes the "Public testnet: not yet open" line on the go. ## When a step fails The tool stops, prints why and the `--from` command to retry. Nothing is skipped silently. State that is not a secret (commit, run id, bump time, the hashes) is in `~/.cache/igneum/ship/.json`. Secrets come from `~/.config/igneum` (dl-token, dlsite-dir, ota-signing-key, relay token and key, the Vercel login) and are never printed; every output line is scrubbed. `gh auth switch --user igneum-labs` runs before every gh call and before the push. ## What a cut needs before it starts - The node fork built for both targets in `--node`: `target-integration/release/{igneumd,igneum-miner}` and `target-integration/x86_64-pc-windows-gnu/release/{igneumd,igneum-miner}.exe` (`proto-cuda/windows-node/cross-build.sh`). - The prebuilt workers (`proto-cuda/nvrtc/igneum-worker-cuda.exe`, `proto-opencl/igneum-worker-opencl.exe`) and the prover (`proving/igneum-prove/target/release/igneum-prove-{host,export}`); missing ones are noted, not fatal. - The Mac on mains, nothing else building (the DMG step waits for the build lock). ## The site project on Vercel (4 October 2026, checked by hand) Two Vercel projects are called `igneum`, in two teams, and the link file under `site/.vercel` points at the wrong one: | Team | Login (global config) | Project | Holds | Deploys | |---|---|---|---|---| | `igneum` | `igneum-labs` (`~/.config/igneum/vercel`) | `igneum` (`prj_HcNO2NLY4jkhikmLMugpJ8nLcWNW`, root directory `site`, build `node build.mjs`) | `igneum.network`, `igneum.com`; env `DATABASE_URL`, `LOG_INTAKE_KEY` | GitHub integration: every push to master is a production deploy, every branch push a preview (`igneum-git--igneum.vercel.app`) | | `[other-business]` | `[user]-4826` (the default `~/.vercel`) | `igneum` (`prj_y5lQg3C87fXAgoj3vug6iwWU3KwV`, same settings) | the 13 redirect domains (`igneum.app`, `.art`, `.co.uk`, `.email`, `.info`, `.io`, `.net`, `.online`, `.org`, `.pro`, `.shop`, `.store`, `.vip`, `.xyz`) | nothing live | So `vercel env add` from `site/` fails with "Could not retrieve Project Settings" under the igneum login (the link file names the [other-business] project, which that login cannot read), and would silently add the value to the wrong project under the default login. The working commands, from `site/` in the shared checkout (the link step rewrites `site/.vercel/project.json`, which is ignored by git): cd site npx --yes vercel@latest --global-config ~/.config/igneum/vercel link --scope igneum --project igneum --yes npx --yes vercel@latest --global-config ~/.config/igneum/vercel env ls --scope igneum # names only npx --yes vercel@latest --global-config ~/.config/igneum/vercel env add LOG_INTAKE_KEY_NEXT production --scope igneum A production deploy by hand is `npx --yes vercel@latest --global-config ~/.config/igneum/vercel deploy --prod --yes --scope igneum` from the repository root (the project's root directory is `site`), but the normal path is a push to master. The relay (`igneum-relay`) and the downloads folder (`igneum-dl`) are the other two projects in the `igneum` team; both deploy by CLI from their own folders (`relay/README.md`, `packaging/ota/README.md`). CLAUDE.md still says the site sits in the [other-business] team and deploys with `--scope [other-business]` from `site/`: that was true on 3 October and is not now. ## Job scripts (5 October 2026, the lost-quote class) A bash body in a PowerShell job (`relay/playbooks/*.ps1`, `tools/**/*.ps1`) is written to a file and run with `bash `, never inline. That is the 0.3.6 cut's rule. Twice on 5 October a body travelled inside a PowerShell string, a quote was lost on the way through PowerShell, bash refused the whole body, and the job either reported exit 0 having done nothing (`tools/amd-prove/pc1-cpu-prove.ps1`, first version: an apostrophe inside a single-quoted awk program) or failed in 4 s (the 0.3.10 installer job). The file shape keeps the body readable by `bash -n` before it runs; `pc1-cpu-prove.ps1` does that in the job itself. `tools/ci/bash-body-check.sh` fails CI on an inline body that does not parse: it finds every body handed to bash (`bash -c "..."`, `bash -lc $var`, a `+` concatenation, a here-string written to a file and run), unescapes it the way PowerShell would, and runs `bash -n` on it. A body it sees but cannot read fails too. `--self-test` shows it firing on the fixtures under `tools/ci/fixtures/`. A job script is published from a worktree and never passes CI before it runs, so `packaging/ota/publish-jobs.sh add --kind run` runs the same check (and `tools/ci/prover-socket-check.sh`, the root-socket class; `bash -n` for a `.sh` script) on the script before anything is signed, and refuses the publish with the check's output. `packaging/ota/test-publish-jobs.sh` proves the refusals. The wiped-jobs-folder class (5 October 2026, 21:49Z): an app install clears the jobs folder, so a run job that uses a kit fetched by an earlier fetch job tests the kit is there (Test-Path on the kit root or any file under it) before its first use, and the fetch is republished under a new id after any app update. `tools/ci/kit-path-check.sh` fails CI and the publish on a kit path used before that check.