aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/nix/README.md
blob: 2bf33832d344ba3bdb2fa313b0a362164cc13a10 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# nix

Home-Manager profiles for the Arch host (`host.nix`) and the Ubuntu
remote-dev VM (`vm.nix`), both layered on top of `common.nix`. Shares
the same nvim, zellij, and zsh configs from the same repo — no
duplication across machines.

## Bootstrap

**Host (Arch)**: managed by the top-level `bootstrap.sh` in the repo
root (installs nix + runs `home-manager switch --flake .../nix#host`
as part of `just init`).

**VM (Ubuntu)**: as the dev user (must have sudo):

```sh
curl -fsSL https://raw.githubusercontent.com/sommerfelddev/dotfiles/master/nix/bootstrap.sh | sh
```

Then log out and back in. Run `nvim` once to let it fetch plugins from
GitHub on first launch.

## What the VM bootstrap does

1. Installs Nix (Determinate Systems multi-user installer).
2. Clones this repo to `~/.local/share/dotfiles`.
3. Runs `home-manager switch --flake .../nix#vm`, which:
   - Installs the CLI tool subset (see `common.nix` + `vm.nix`).
   - Symlinks `~/.config/{nvim,zellij,zsh,direnv,ghostty,git}` and
     `~/.ssh/config` at the cloned working tree via
     `mkOutOfStoreSymlink`, so `git pull` is enough to pick up config
     edits — no rebuild needed for config-only changes.
   - Sets `ZDOTDIR=$HOME/.config/zsh` so the shared zshrc/zprofile load.
4. Appends the nix-store zsh to `/etc/shells` and `chsh`'s to it.

## Updating after a dotfiles change

Run from `~/.local/share/dotfiles/nix` (host or VM):

```sh
just update     # pull + home-manager switch (handles everything)
```

Or piece-by-piece if you know which one you need:

```sh
just pull       # config-only changes (nvim/zellij/zsh/git/ssh): no rebuild needed
just switch     # rebuild home-manager from the current checkout
```

> `just update` runs `pull` then `switch`. The home-manager invocation
> uses `--impure --flake '.#vm' -b backup`; the single-quotes around the
> flake ref matter because our zsh enables `extendedglob`, which would
> otherwise interpret `.#vm` as a glob pattern. On the host, swap
> `#vm` → `#host`.

## Adding a tool

Edit `common.nix` (shared) or the profile-specific file (`host.nix` /
`vm.nix`), add to `home.packages`, then `just switch` (or `just
update`).

## Single-shell policy (leaf tools only)

The nix profile carries **leaf CLI tools** plus **editor/AI-agent
runtimes**, and nothing else. Specifically forbidden in `home.packages`
because they would shadow the system toolchain via `PATH` and silently
break builds against the system sysroot/libc/CI: `cc`, `c++`, `gcc`,
`g++`, `clang`, `clang++`, `ld`, `make`, `cmake`, `ninja`, `meson`,
`pkg-config`, `autoconf`, `automake`, `python`, `python3`, `pip`,
`cargo`, `rustc`, `go`. The system `python3` stays the default
interpreter for project builds.

Explicit carve-outs (used only by editor/AI agents, never by the
project build):

- `nodejs``node`/`npm`/`npx` for npm-based LSPs and
  copilot-language-server.
- `uv``uv`/`uvx` for ad-hoc Python tooling in isolated venvs. `uv`
  does NOT install a `python3` in PATH; it manages its own
  interpreters under `~/.local/share/uv/`. System `python3` is
  untouched.
- `clang-tools``clang-format`, `clang-tidy`, `clangd` only (no
  compiler driver).

If a project needs a newer build toolchain, drop a `flake.nix` +
`.envrc` in that project tree (direnv + nix-direnv is already wired
up). Don't add it to `common.nix`/`host.nix`/`vm.nix`.

## Commit signing on the VM (SSH-format, no GPG secrets)

GPG private keys never leave the host. Commits on the VM are signed
with the **forwarded SSH agent** in SSH-signature format, using the
authentication subkey gpg-agent already exposes via `ssh-add -L`.

One-time setup on the VM:

```sh
mkdir -p ~/.config/git

# allowed_signers: maps your committer email to the SSH pubkey of the
# auth subkey. Adjust the grep if you have multiple keys.
printf '%s %s\n' \
  "$(git config user.email)" \
  "$(ssh-add -L | head -n1)" \
  > ~/.config/git/allowed_signers

# Machine-local git override (NOT tracked in dotfiles).
cat > ~/.config/git/config.local <<EOF
[gpg]
    format = ssh
[gpg "ssh"]
    allowedSignersFile = ~/.config/git/allowed_signers
[user]
    signingkey = $(ssh-add -L | head -n1 | awk '{print $1" "$2}')
EOF
```

The tracked `dot_config/git/config` ends with `[include] path =
~/.config/git/config.local`, so the override is picked up
automatically (and silently ignored on machines that don't have it).

Required on the **host's** `~/.ssh/config` for the VM `Host` block:

```
ForwardAgent yes
```

Verify on the VM after SSH-ing in:

```sh
ssh-add -L                 # should list your auth pubkey(s)
git commit --allow-empty -m test
git log --show-signature -1
```

## Caveats

- **GPG / pass**: HM installs `gnupg` and `pass` but does _not_ import
  any private key. On the VM, use SSH-format signing via the forwarded
  agent instead (see above). On the host, smartcard access via
  `pcscd` is configured in `host.nix` (`~/.gnupg/scdaemon.conf`).
- **Disk usage**: Nix store + nvim plugins consumes ~3-5 GB. Check
  partition size first on the VM.
- **Network for first nvim launch**: `vim.pack.add` fetches plugins
  from GitHub on first start.
- **Ubuntu apt collisions**: Nix-installed binaries appear first in
  PATH. The leaf-tools policy above exists precisely to keep this
  shadowing contained to harmless tools.

## Podman (rootless, VM only)

Nix can't manage setuid helpers, `/etc/subuid`/`/etc/subgid`, or kernel
cmdline. Do this once on the VM as root:

```sh
sudo apt install -y uidmap
grep "^$USER:" /etc/subuid /etc/subgid || \
  sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 "$USER"
```

Then (optional, **only** if you need rootless CPU/memory limits) enable
cgroups v2. Ubuntu 20.04 still defaults to v1; flipping this requires a
reboot and affects every workload on the box, so skip unless you have a
concrete need:

```sh
sudo sed -i 's|^GRUB_CMDLINE_LINUX_DEFAULT="\(.*\)"|GRUB_CMDLINE_LINUX_DEFAULT="\1 systemd.unified_cgroup_hierarchy=1"|' /etc/default/grub
sudo update-grub && sudo reboot
```

Verify:

```sh
podman info | grep -E 'cgroupVersion|graphDriverName|networkBackend'
# expected: graphDriverName: overlay, networkBackend: netavark
# cgroupVersion: v1 is fine — only blocks --memory/--cpus flags. The
# podman v5 deprecation warning is silenced by PODMAN_IGNORE_CGROUPSV1_WARNING,
# set in vm.nix.
podman run --rm docker.io/library/alpine echo hi
```

The VM home-manager profile installs `podman`, `crun`, `conmon`,
`netavark`, `aardvark-dns`, `slirp4netns`, and `passt`, and writes
sensible `~/.config/containers/{registries,storage,policy}.conf` files.

## How it's wired

`common.nix` uses `config.lib.file.mkOutOfStoreSymlink` so the symlinks
point at the **live working tree** at `~/.local/share/dotfiles/...`,
not at copies in `/nix/store`. This means:

- Editing `dot_config/nvim/init.lua` in the cloned repo takes effect
  on the next `nvim` launch with no rebuild.
- `home-manager switch` only needs to re-run when adding/removing a
  package or changing what's symlinked.

The zsh plugins (`zsh-syntax-highlighting`, etc.) live in
`$HOME/.nix-profile/share/`. The shared `dot_zshrc` prefers the
nix-profile path on both host and VM, falling back to system paths for
un-bootstrapped states.