# NVM for Windows — Administrator & Developer Guide

This guide documents the architecture, configuration, operations, security model, and internals of **NVM for Windows** (`nvm`) — the Node.js version manager for Windows. It is intended for system administrators deploying NVM4W across machines, support engineers diagnosing failures, and developers who want to understand or contribute to the codebase. End-user task instructions live in the companion User Guide.

NVM for Windows is a single, self-contained Go executable. It has no runtime dependency on Node.js, no service, no database, and no background daemon of its own. Its entire job is to manage a set of on-disk Node.js installations and a single Windows symlink that selects the active one.

---

## 1. Executive Summary

| Attribute | Detail |
|-----------|--------|
| Purpose | Install, switch, and manage multiple Node.js versions on Windows |
| Language | Go (module `nvm`, `go 1.18`) |
| Distribution | Single `nvm.exe` + companion `author-nvm.exe`, packaged with an Inno Setup installer |
| Runtime dependencies | None (no Node.js required); relies on Windows APIs |
| State model | Filesystem only — version folders + a single symlink + a plain-text `settings.txt` |
| Privilege requirement | Administrator **or** Windows Developer Mode (to manage the symlink) |
| Network endpoints | `nodejs.org` (Node + version index), `github.com/npm/cli` (npm), GitHub API (self-update), `author.io` (alerts) |
| Configuration | `settings.txt` in `NVM_HOME` + two environment variables (`NVM_HOME`, `NVM_SYMLINK`) |
| Supported consoles | Command Prompt, PowerShell (officially) |

**Operating principle:** every Node.js version lives in its own directory under `NVM_HOME` (e.g. `C:\nvm\v22.14.0`). The path `NVM_SYMLINK` is added to the system `PATH` exactly once at install time. Switching versions re-points that symlink — so the change is global, instant across all shells, and persists across reboots, without ever re-editing `PATH`.

---

## 2. Architecture

### 2.1 Component overview

```
                          ┌─────────────────────────────────────────┐
   User terminal  ──────▶ │  nvm.exe  (package main, src/nvm.go)     │
   (Admin shell)          │  os.Args switch-dispatch CLI             │
                          └───────────────┬─────────────────────────┘
                                          │ calls internal packages
        ┌─────────────┬──────────────┬────┴───────┬─────────────┬───────────────┐
        ▼             ▼              ▼            ▼             ▼               ▼
   nvm/web        nvm/node       nvm/arch     nvm/file     nvm/utility     nvm/upgrade
   downloads,     version        PE-header    unzip,       debug logs,     self-update,
   mirrors,       discovery,     arch detect  settings I/O cross-vol move  notifications,
   proxy, SSL     install state                                            scheduled tasks
        │             │                                                         │
        ▼             ▼                                                         ▼
   nodejs.org    %NVM_HOME%\vX.Y.Z\                                       nvm/author ──▶ author-nvm.exe
   github npm    (per-version node.exe + npm)                            (companion: notifications)
        │
        ▼
   NVM_SYMLINK ──(on system PATH)──▶ re-targeted on `nvm use`
```

### 2.2 Package inventory

| Package | File(s) | Responsibility |
|---------|---------|----------------|
| `main` | `src/nvm.go` | CLI entry point, command dispatch, install/use/uninstall orchestration, settings persistence, `debug` troubleshooter, elevation helpers |
| `nvm/web` | `src/web/web.go` | HTTP client, proxy + SSL config, mirror resolution, Node/npm downloads, redirects, connectivity ping, zip extraction with ZipSlip guard |
| `nvm/node` | `src/node/node.go` | Detect current version/arch, enumerate installed versions, fetch & classify the remote version index (LTS/current/stable/unstable), map Node→npm versions |
| `nvm/arch` | `src/arch/arch.go` | Architecture detection by scanning the PE header bytes of `node.exe`; validate/normalize `32`/`64`/`arm64` |
| `nvm/file` | `src/file/file.go` | `Unzip`, `ReadLines`, `Exists` helpers |
| `nvm/semver` | `src/semver/semver.go` | Vendored semantic-version parsing/validation/comparison |
| `nvm/upgrade` | `src/upgrade/{check,notification,register,upgrade}.go` | Self-update pipeline, release/alert checks, toast notifications, Windows Task Scheduler registration |
| `nvm/utility` | `src/utility/{logging,rename}.go` | Debug logging (ANSI), cross-volume `Rename` (move-or-copy) |
| `nvm/encoding` | `src/encoding/encoding.go` | Charset detection and UTF-8 conversion for settings values |
| `nvm/author` | `src/author/bridge.go` | Bridge to the external `author-nvm.exe` companion (notifications, subscribe, upgrade hooks) |

### 2.3 Command dispatch model

There is **no CLI framework** (no Cobra, no flag package for subcommands). `main()` reads `os.Args` and routes on `args[1]` with a `switch`. The implication for operators and contributors: the authoritative list of commands is the `switch` block in `src/nvm.go:main()` plus the hard-coded usage text in `help()`. Positional arguments are interpreted as: `args[1]` = command, `args[2]` = version/detail, `args[3]` = architecture (`32`/`64`/`arm64`) when applicable.

The `init()` function additionally registers a custom `nvm://` URL-protocol handler. When `nvm.exe` is launched with a `nvm://launch?action=...` argument (from a toast-notification button), it rewrites `os.Args` into the equivalent command (`install`, `use`, `upgrade`) before `main()` runs.

> [!NOTE]
> Any argument equal to `verbose` (dashes stripped) anywhere on the command line enables debug logging via `utility.EnableDebugLogs()`. This is parsed in `init()`, independent of the command.

### 2.4 The symlink switching mechanism

This is the core design decision (chosen over the `.bat`-shim and `PATH`-rewriting approaches used by older Windows version managers):

1. At install time, `NVM_SYMLINK` (e.g. `C:\nvm\node`) is added to the **system** `PATH`.
2. `nvm use X` validates the target, removes the existing symlink (`rmdir` via elevation), and creates a new directory symlink (`mklink /D`) pointing at `%NVM_HOME%\vX`.
3. Because the symlink — not the version folders — is on `PATH`, the active `node`/`npm` change everywhere at once and survive reboots.

Architecture selection is handled by renaming executables inside the active version folder: `node32.exe`/`node64.exe` are swapped to/from `node.exe` so the on-`PATH` `node` matches the requested bitness.

---

## 3. System Requirements

| Requirement | Detail |
|-------------|--------|
| OS | Windows (10/11 and Server editions). Uses `kernel32.dll`/`user32.dll` and `golang.org/x/sys/windows` |
| Privileges | Administrator, **or** Windows Developer Mode enabled (`AllowDevelopmentWithoutDevLicense` registry value > 0) |
| Console | Command Prompt or PowerShell (officially supported) |
| Network | Outbound HTTPS to `nodejs.org`, `github.com`, `api.github.com`, and `author.io` (or configured mirrors/proxy) |
| Disk | Each Node.js version is a full install (tens to hundreds of MB); plan capacity per version retained |
| Go toolchain (build only) | Go ≥ 1.18 to compile from source |

> [!IMPORTANT]
> `NVM_HOME` and `NVM_SYMLINK` **must be different paths**, and `NVM_SYMLINK` must point at a location that does not already exist as a physical directory. The `debug` command enforces both invariants and warns if violated.

---

## 4. Configuration Reference

### 4.1 Environment variables

These are read directly from the process environment (`os.Getenv`). The installer sets the first two.

| Variable | Source | Purpose |
|----------|--------|---------|
| `NVM_HOME` | `src/nvm.go` | Root directory for installed versions and `settings.txt`. `settings.txt` is resolved as `%NVM_HOME%\settings.txt`. |
| `NVM_SYMLINK` | `src/nvm.go` | The symlink path placed on system `PATH`; its target is swapped on `nvm use`. |
| `PROCESSOR_ARCHITECTURE` | `src/nvm.go` | Seeds the default architecture when none is configured. |
| `PATH` | `src/nvm.go` | Inspected by `nvm debug` to detect conflicting Node installs and confirm the symlink is present. |
| `APPDATA` | `src/upgrade/notification.go` | Location of notification state: `%APPDATA%\.nvm\.updates.json` (hidden). |

### 4.2 `settings.txt`

The persistent configuration file, stored at `%NVM_HOME%\settings.txt`. It is a plain-text, colon-delimited key/value file (one pair per line) — **not** YAML or JSON. It is written by `saveSettings()` and parsed by `setup()`. Values are passed through `os.ExpandEnv`, so environment-variable references like `%APPDATA%` are expanded on read.

Example (`examples/settings.txt`):

```text
root: C:\Users\Corey\AppData\Roaming\nvm
path: C:\Program Files\nodejs
arch: 64
proxy: none
```

| Key | Written by | Meaning |
|-----|-----------|---------|
| `root` | `nvm root`, install/use | Storage root for version folders (mirrors `env.root`). |
| `path` | installer / `setup()` | The original Node.js path captured at install (`originalpath`). |
| `arch` | `nvm arch <32\|64>` | Default architecture (`32`/`64`/`arm64`). |
| `proxy` | `nvm proxy <url>` | Download proxy URL, or `none`. |
| `node_mirror` | `nvm node_mirror <url>` | Override for the Node download base URL. |
| `npm_mirror` | `nvm npm_mirror <url>` | Override for the npm download base URL. |
| `originalversion` | installer / `setup()` | Version of the pre-existing Node.js captured at install. |

> [!CAUTION]
> Do not hand-edit `settings.txt` to point `root`/symlink at a real Node.js directory. `validSymlink()` deliberately refuses to delete a path that is a physical file/directory rather than a symlink — an anti-footgun added after users corrupted installs by manual edits. Use the `nvm` commands instead.

### 4.3 Download endpoints and defaults

Defined in `src/web/web.go`:

| Setting | Default | Override |
|---------|---------|----------|
| Node base address | `https://nodejs.org/dist/` | `nvm node_mirror <url>` |
| npm base address | `https://github.com/npm/cli/archive/` | `nvm npm_mirror <url>` |
| Version index | `<node_base>/index.json` | follows the node mirror |
| Proxy | none | `nvm proxy <url>` |
| SSL verification | on | `--insecure` flag on `install` (per-invocation only) |

Mirror URLs are normalized: a missing scheme gets `http://` prepended, and a trailing `/` is enforced.

---

## 5. Operations

### 5.1 Installation and provisioning

Deploy via the Inno Setup installer (`nvm-setup.exe`) built from `nvm.iss`. The installer:

- Writes `nvm.exe` and `author-nvm.exe` to the install directory.
- Sets `NVM_HOME` and `NVM_SYMLINK` environment variables.
- Adds `NVM_SYMLINK` to the system `PATH`.
- Optionally absorbs an existing Node.js installation (copying its files and global modules into the NVM root, since Windows forbids cross-vendor uninstalls).

For fleet deployment, standardize `NVM_HOME`/`NVM_SYMLINK` paths and pre-remove any baseline Node.js image to avoid `PATH` conflicts (the #1 support issue).

### 5.2 Health check — `nvm debug`

`nvm debug` (`checkLocalEnvironment()`) is the primary operational diagnostic. It reports and validates:

- Administrator vs. elevated vs. standard rights, and Windows Developer Mode status.
- `PATH` conflicts — another `node.exe` ahead of the symlink.
- Console type (warns if not Command Prompt/PowerShell).
- Windows build number (via `RtlGetNtVersionNumbers`).
- NVM4W version, author-bridge version, resolved binary path, settings path.
- `NVM_HOME` / `NVM_SYMLINK` values, and that they differ and are not nested.
- Whether the symlink exists and actually *is* a symlink pointing at a directory.
- IPv6 status (flagged as a possible download-slowness cause).
- Reachability of the Node version index (accounting for mirror/proxy).
- Installed versions missing `node.exe` (invalid) or `npm.cmd` (Node-only).
- Presence of `settings.txt`.
- Availability of an NVM4W self-update.

A clean machine prints `No problems detected.`; otherwise a `PROBLEMS DETECTED` block lists each issue with a remediation hint. Use this as the first step in any support ticket.

### 5.3 Logs

- **Fatal errors** are appended to `error.log` next to `nvm.exe` (`writeToErrorLog`), and some upgrade/scheduling paths write `error.log` in the working directory.
- **Verbose/debug logging** is opt-in: add `verbose` to any command. Output is ANSI-colored (`utility.DebugLogf`) and includes the resolved command, architecture, download URLs, and file moves.

> [!TIP]
> When collecting diagnostics for a bug report, re-run the failing command with `verbose` appended and attach both the console output and any `error.log` next to `nvm.exe`.

### 5.4 Self-update lifecycle

`nvm upgrade` (`upgrade.Run` → `run`) performs:

1. Query the GitHub releases API (`api.github.com/repos/coreybutler/nvm-windows/releases/latest`) and the alerts feed (`author.io/nvm4w/feed/alerts`).
2. Compare the running version against the latest; abort early if already current.
3. Download `nvm-noinstall.zip` plus its `.checksum.txt`; verify with an **MD5** comparison (abort on mismatch).
4. Extract to a temp dir; download any extra assets (e.g. `update.exe`).
5. Zip the current install as a backup and copy it to a hidden `.update\nvm4w-backup.zip`.
6. Copy new files over the install directory; stage the new `nvm.exe` under `.update\`.
7. Write and launch `updater.bat`, which waits for the current PID to exit, swaps in the new `nvm.exe`, schedules a `RemoveNVM4WBackup` cleanup task (~7 days out), and relaunches via the `nvm://launch?action=upgrade_notify` protocol.

> [!WARNING]
> **Negative knowledge — rollback is not wired up in this build.** The user-facing help text advertises "Manual rollback available for 7 days after upgrade," and the backup *is* created and retained for 7 days. However, the `rollback` argument handler inside `upgrade.run()` is entirely commented out in this commit. To revert, reinstall the prior release from the GitHub releases page; do not rely on an `nvm upgrade rollback`-style command — it does not exist here.

### 5.5 Notifications and scheduled checks

`nvm subscribe <topic>` (topics: `lts`, `current`, `nvm4w`, `author`) registers Windows scheduled tasks via `schtasks` (`src/upgrade/register.go`), each running `nvm checkForUpdates <topic>` hourly at a staggered minute:

| Topic | Scheduled task name | Cadence |
|-------|---------------------|---------|
| `lts` | NVM for Windows Node.js LTS Update Check | hourly @ :30 |
| `current` | NVM for Windows Node.js Current Update Check | hourly @ :25 |
| `nvm4w` | NVM for Windows Update Check | hourly @ :15 |
| `author` | NVM for Windows Author Update Check | hourly @ :45 |

Notification de-duplication state lives in `%APPDATA%\.nvm\.updates.json` (hidden). Toast notifications and the actual notification delivery route through the external `author-nvm.exe` via `nvm/author.Bridge`. `nvm unsubscribe <topic>` removes the corresponding scheduled task.

### 5.6 Backup and recovery

- **Node versions** are independent directories under `NVM_HOME`; backing up that tree backs up all installed runtimes.
- **Configuration** is a single `settings.txt`.
- **NVM4W binary backups** are produced automatically on upgrade (`.update\nvm4w-backup.zip`, 7-day retention).
- To recover a corrupted version: `nvm reinstall <version>`. To recover NVM4W itself: re-run the installer for the desired release.

---

## 6. Security Analysis

| Area | Finding |
|------|---------|
| **Privilege model** | Requires Administrator or Developer Mode to manage the symlink. Elevation is performed via `elevatedRun` → `elevate.cmd`/`elevate.vbs` helpers, with a fallback that retries through UAC. |
| **TLS** | HTTPS by default with certificate verification on. `--insecure` sets `InsecureSkipVerify` for a single `install` invocation and prints an explicit warning; it does not persist. |
| **Update integrity** | Self-update validates the download against a published checksum — but with **MD5**, which is cryptographically weak. It guards against corruption, not a determined tamper. There is no signature verification of the update payload itself (the distributed installer is Authenticode-signed for releases other than v1.1.8). |
| **Archive extraction** | `web.unzip` includes a **ZipSlip guard** (`illegal file path` on traversal outside the destination). The simpler `file.Unzip` helper only rejects names containing `..` — a weaker check; prefer the `web` path for untrusted archives. |
| **Symlink safety** | `validSymlink()` refuses to delete a target that is a physical file/directory rather than a symlink, preventing accidental destruction of real Node.js installs from a bad `settings.txt`. |
| **Network trust** | Downloads come from `nodejs.org` and the npm GitHub archive over TLS; mirrors and proxies are user-configurable and trusted as configured. The alerts feed is fetched from `author.io`. |
| **Privilege of scheduled tasks** | `subscribe` creates `schtasks` entries that invoke `nvm checkForUpdates`; these run under the registering user's context. |

> [!CAUTION]
> Treat `--insecure` as a break-glass option for TLS-intercepting corporate proxies only. Because update integrity relies on MD5, do not depend on the checksum step as a tamper-evidence control on untrusted networks — verify you are pulling releases from the official GitHub project, and prefer the Authenticode-signed installer for distribution.

---

## 7. Code Quality & Patterns

- **Concurrency:** `install`, `use`, and `upgrade` run their work in goroutines coordinated by a `Status` channel and a `sync.WaitGroup`, while a foreground goroutine renders progress (either console text or a `zenity` progress dialog when `--show-progress-ui` is set). Signal handling (`SIGINT`/`SIGTERM`) triggers rollback of partial installs.
- **Architecture detection** is done by byte-scanning the PE header of `node.exe` (`arch.Bit`: `arm64` = `5045000064AA`, `x64` = `504500006486`, `x86` = `504500004C`), rather than trusting filenames.
- **Cross-volume moves:** `utility.Rename` falls back from `os.Rename` to a recursive copy-then-delete when source and destination are on different drives — important because temp downloads and the NVM root may live on different volumes. Large-extract races are handled with exponential backoff (1/2/4/8/16s) when moving npm into place.
- **Vendored semver:** the project vendors a `semver` package (`src/semver`) in addition to importing `blang/semver`, used in the upgrade comparison paths.
- **Error surface:** 92 distinct error strings were catalogued across the codebase (richest in `src/semver` and `src/nvm.go`), giving precise, user-actionable messages — these double as the canonical troubleshooting catalog.

### Conventions for contributors

- Add new commands to the `switch` in `src/nvm.go:main()` **and** to the `help()` usage text — they are maintained by hand and must stay in sync.
- Positional argument slots are fixed (`args[2]` = version, `args[3]` = arch); preserve this contract.
- Keep new user-facing failures as explicit `error` strings with remediation guidance, consistent with the existing catalog.

---

## 8. Build & Contributing

The build is driven by `src/manifest.json` (name, version, build entrypoint `nvm.go`, output `../bin`, minify, release/dev profiles) and an Inno Setup script (`nvm.iss`). A Deno helper, `build.js`, injects the manifest version into `nvm.iss` and invokes `assets\buildtools\iscc.exe` to compile the installer.

From source (per the README):

```bash
# Install Go from https://golang.org
git clone https://github.com/coreybutler/nvm-windows
cd nvm-windows\src
go get github.com/blang/semver
go get github.com/olekukonko/tablewriter
build.bat
# resulting setup program lands in the dist directory
```

> [!NOTE]
> The build deliberately uses Go (and a Deno helper for the installer) rather than Node.js — the author's stated rationale is that a Node version manager should not depend on the tool it installs.

### Key dependencies

| Dependency | Role |
|------------|------|
| `github.com/blang/semver` | Semantic version parsing/comparison |
| `github.com/olekukonko/tablewriter` | Renders the `nvm list available` table |
| `github.com/ncruces/zenity` | Native progress dialogs for protocol-launched installs/upgrades |
| `github.com/go-toast/toast` | Windows toast notifications |
| `github.com/dustin/go-humanize` | Human-friendly relative release dates in notifications |
| `github.com/saintfish/chardet` | Charset detection for settings encoding |
| `github.com/coreybutler/go-fsutil`, `go-where` | Filesystem move helpers and executable lookup |
| `golang.org/x/sys/windows` | Windows registry, console mode, token/privilege APIs |

---

## 9. Potential Improvements

These are observations drawn from the source as it stands at this commit.

- **Wire up rollback.** The 7-day backup is created and advertised in help text, but the `rollback` handler is commented out. Either implement the restore path or correct the help text to avoid setting a false expectation.
- **Strengthen update integrity.** Replace the MD5 update checksum with SHA-256, and consider verifying a signature over the payload rather than relying on the installer's Authenticode signature alone.
- **Unify zip extraction.** Two unzip implementations coexist (`web.unzip` with a full ZipSlip guard, `file.Unzip` with only a `..` substring check). Consolidate on the hardened version.
- **Formalize the command surface.** The hand-maintained `switch`/`help()` pairing is easy to desynchronize. A small command-registry table (name, aliases, handler, help) would remove the duplication and make `--version`/alias handling uniform.
- **Externalize `settings.txt` parsing edge cases.** The colon-delimited parser splits on the first `:` and rejoins the remainder, which is correct for Windows paths but is custom logic worth unit-testing against values containing `:` and expanded env vars.
- **arm64/64 availability heuristics** (`IsNode64bitAvailable`, `IsNodeArm64bitAvailable`) carry `TODO: fixme` and optimistically assume availability above a version threshold. A definitive check against the version index would prevent misleading "only available in 32-bit" errors.

---

## 10. Quick Reference

### Files and locations

| Path | Purpose |
|------|---------|
| `%NVM_HOME%\settings.txt` | Persistent configuration |
| `%NVM_HOME%\vX.Y.Z\` | A single installed Node.js version |
| `%NVM_SYMLINK%` | Active-version symlink (on system `PATH`) |
| `<install dir>\nvm.exe` | Main executable |
| `<install dir>\author-nvm.exe` | Notification/subscribe companion |
| `<install dir>\error.log` | Fatal error log |
| `<install dir>\.update\nvm4w-backup.zip` | Pre-upgrade backup (hidden, 7-day retention) |
| `%APPDATA%\.nvm\.updates.json` | Notification de-dup state (hidden) |

### Network endpoints

| Endpoint | Used for |
|----------|----------|
| `https://nodejs.org/dist/` (+ `index.json`) | Node downloads and the version index |
| `https://github.com/npm/cli/archive/` | npm downloads |
| `https://api.github.com/repos/coreybutler/nvm-windows/releases/latest` | Self-update check |
| `https://author.io/nvm4w/feed/alerts` | Upgrade alerts/warnings |

### Operational commands

| Command | Operator use |
|---------|--------------|
| `nvm debug` | First-line diagnostic for any issue |
| `nvm root [path]` | Inspect/relocate the version store |
| `nvm upgrade` | Update NVM4W itself (no rollback command — reinstall to revert) |
| `nvm proxy / node_mirror / npm_mirror` | Configure for restricted networks |
| `nvm subscribe / unsubscribe <topic>` | Manage background update-check scheduled tasks |
| `nvm install <v> all` | Provision both 32- and 64-bit of a version |

### Architecture availability rules

| Architecture | Available from |
|--------------|----------------|
| 32-bit | All supported versions |
| 64-bit | Node 8+ (older majors are 32-bit only) |
| arm64 | Node 19.9+ |

---

_Generated June 2026 from coreybutler/nvm-windows at 5b18223. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
