Migrate to npm
Applies to
10.0.0-rc.11and earlier. Thedkg migrate-to-npmcommand was removed in10.0.0-rc.17. On current releases the git-checkout → npm migration runs automatically on the firstdkg start, so this manual procedure is no longer needed. Keep this page only for operators running a node pinned to rc.11 or older.
Migrate a git-checkout install to the npm-pinned auto-update path
This guide is for operators currently running a DKG node from a git cloned checkout (typical layout: ~/dkg/ with .git, packages/, node_modules/, pnpm-lock.yaml, package.json). It walks through converting that install to use the npm-pinned auto-update path without re-installing.
The end-state: the daemon's auto-updater fetches pre-built artifacts of a specific @origintrail-official/dkg version from npm into ~/.dkg/releases/{a,b}/, instead of building from source against the tracked git branch on every update cycle.
Why migrate
The build-from-source auto-update path (active when the CLI detects a monorepo checkout) is fragile for long-running production daemons:
It runs
tsc+ dependent build steps on every update, exposing the daemon to any regression that lands on the tracked branch between two polling cycles. A previous release retrospective for beacon-01 traced a shutdown deadlock to exactly this pattern: a regression merged tomain, the next auto-update cycle built it, and the deadlock fired on the worker's first SIGTERM.It is harder to pin to a known-good version. With the npm path, the operator can set
autoUpdate.npmVersion = "10.0.0-rc.11"(or just tracklatest) and know exactly what they'll get. With the git path, "latest" means "whatevermainhappens to look like at polling time".It pulls a much larger dependency surface.
pnpm install --frozen-lockfilefor the full monorepo runs hardhat, the EVM module's solidity tooling, and all dev dependencies. The npm path installs only theproductiondependency set.
What the migration actually does
The dkg migrate-to-npm subcommand performs two changes:
Renames
<repoRoot>/package.jsonto<repoRoot>/package.json.pre-npm-migration-<timestamp>. This is the load-bearing change. The auto-updater callsfindPackageRepoDir()(inpackages/core/src/blue-green.ts), which walks the directory tree looking for the marker pairpackage.json + packages/. Renamingpackage.jsonis sufficient to break that walker; the walker then returnsnull,isStandaloneInstall()returnstrue, and the auto-updater routes throughperformNpmUpdateon the next polling cycle.Renames
<repoRoot>/.gitto<repoRoot>/.git.pre-npm-migration-<timestamp>. This is cosmetic — the walker doesn't look at.git. We rename it anyway because (a) it eliminates the operator-confusion failure mode (the original incident driver: operator sees.git, runsgit pullthinking that's how updates work, fork happens), and (b) it prevents stray cron jobs or CI that watch for.gitfrom doing anything unexpected.Writes
autoUpdate.source = "npm"into~/.dkg/config.json. Forward-compat with theautoUpdate.sourceconfig flag (PR #659). Until that flag merges this is silently ignored. Once it merges, this becomes the explicit pin and the renames in steps 1–2 become belt-and-braces.
What the migration does NOT touch
packages/andpackages/cli/dist/. The operator'sdkgPATH symlink typically points into this tree. Renaming would break the ability to invokedkgat all until the operator runsnpm install -g @origintrail-official/dkg. The runbook recommends that as a follow-up step (see Optional cleanup below) once the operator has confirmed the npm-pinned daemon is healthy.node_modules/andpnpm-lock.yaml. Same reason — the currentpackages/cli/dist/cli.jsruntime resolves modules from thisnode_modules/. Until the operator detaches thedkginvocation path, both must stay in place.The blue-green slot tree under
~/.dkg/releases/{a,b}/. That is state, not source-tree. The auto-updater manages those slots itself.
Pre-migration checklist
Before running dkg migrate-to-npm --apply:
Step-by-step
1. Dry-run
Always start with a dry run. No flags = no mutation:
The output lists each action prefixed by [LOAD-BEARING], [cosmetic], or [CONFIG] along with the reason. Read through them. If there are blockers (✗ lines), resolve them before proceeding.
2. Apply
When you're satisfied with the dry-run output:
The command performs the renames in order, then the config write, then prints exact next-step commands.
3. Restart and verify
Verify the daemon picked up the new install mode:
Wait one auto-update polling cycle (checkIntervalMinutes, default 30 min). On the first npm-path cycle, the daemon will install a fresh artifact into the inactive blue-green slot:
You should see one of a/ or b/ containing the npm-style layout (node_modules/@origintrail-official/dkg/) rather than the old git-style layout (packages/cli/dist/).
Rollback
If something goes wrong before the first auto-update cycle has swapped slots:
The next polling cycle will resume the git-path auto-update.
If the rollback is post-swap (the daemon already wrote an npm-style artifact to a blue-green slot), the rollback above will succeed but the daemon will need one more cycle to re-build the git-style artifact in the OTHER slot. That extra cycle is normal and harmless.
Orphan state-directory handling
DKG resolves the active state directory (dkgDir()) by checking whether the running CLI is inside a monorepo:
In a monorepo checkout AND
~/.dkg/config.jsondoes not exist → use~/.dkg-dev/Otherwise → use
~/.dkg/
After this migration, findPackageRepoDir returns null (we renamed package.json), so the CLI looks at ~/.dkg/. If your state was in ~/.dkg-dev/, the migration script detects this and hard-refuses (no --force override; this case is genuinely unsafe — proceeding would silently make the CLI read from an empty ~/.dkg/ and behave like a fresh install).
The script's blocker message tells you the exact remediation. The two options:
Option A: Move state.
Option B: Pin DKG_HOME.
If you prefer to keep state at ~/.dkg-dev/:
DKG_HOME overrides the auto-resolution unconditionally (rule 1 in resolveDkgConfigHome), so the CLI continues to use ~/.dkg-dev/ after migration.
Daemon-alive bypass (--force)
--force)The migration script refuses to mutate the source tree while the daemon is alive, because:
The worker process holds file descriptors open against
~/dkg/packages/cli/dist/. A rename mid-flight on POSIX systems is generally safe (the inode survives), but any in-flight write topackage.json(highly unlikely but possible from a worker auto-update routine) would be lost.The next worker spawn would see the renamed tree and behave unexpectedly — the supervisor would either crash or boot from a half-migrated state.
If the daemon is wedged and dkg stop doesn't work:
--force downgrades the alive-check blocker to a warning. It does NOT bypass the state-directory orphan blocker (that's genuinely unsafe and has no escape hatch).
Optional cleanup (after soak)
After the migrated daemon has been running healthily for at least a week (long enough to confirm one or two auto-update cycles completed cleanly), you can decouple from the old source tree entirely:
At that point ~/dkg/ contains only the renamed backup files (.git.pre-npm-migration-..., package.json.pre-npm-migration-...) and is safe to delete entirely.
Log rotation (cross-cutting recommendation)
Unrelated to this migration but discovered by the same incident retrospective: beacon-01 accumulated 134 MB of daemon.log in 24 hours during a shutdown deadlock, dominated by chain-provider polling spam. Independent of whether you migrate, set up log rotation:
/etc/logrotate.d/dkg:
copytruncate is required — the daemon does not respond to SIGHUP for log reopening, and renaming the file out from under it would silently lose subsequent writes. A future core-stability fix should reduce chain-provider log spam at the source.
Last updated
Was this helpful?