aboutsummaryrefslogtreecommitdiffstats
path: root/INSTALL.md
blob: 19387488bb029522d62299ca9f39bc5089409b5d (plain)
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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# Installation

Full setup guide, from a freshly unboxed machine to a working environment.
For the short version, see [README.md](README.md).

- [macOS (nix-darwin)](#macos-nix-darwin)
- [NixOS](#nixos)
- [Linux (standalone home-manager)](#linux-standalone-home-manager)
- [Day-to-day](#day-to-day)
- [Troubleshooting](#troubleshooting)

## macOS (nix-darwin)

### 1. Xcode Command Line Tools

Needed for `git` and the system toolchain before Nix is in place.

```shell
xcode-select --install
```

Sign in to the **App Store** now as well — `mas` installs the App Store apps
during the first switch and fails if no account is signed in.

### 2. Install Determinate Nix

```shell
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install --determinate
```

`--determinate` matters: `modules/darwin/default.nix` sets `nix.enable = false`,
which hands Nix management to the Determinate installer instead of nix-darwin.
Installing plain upstream Nix here leaves the daemon unmanaged.

Open a new shell afterwards so `nix` is on `PATH`.

### 3. Install Homebrew

nix-darwin declares Homebrew packages but does **not** install Homebrew itself.
The first switch fails without it.

```shell
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
eval "$(/opt/homebrew/bin/brew shellenv)"
```

### 4. Clone the repository

The clone path is not arbitrary — the `rebuild` and `update` aliases in
`modules/home/zsh.nix` point at `~/ghq/simenandre.no/dotfiles`, so clone it
there.

```shell
mkdir -p ~/ghq/simenandre.no
git clone https://simenandre.no/dotfiles.git ~/ghq/simenandre.no/dotfiles
cd ~/ghq/simenandre.no/dotfiles
```

HTTPS on purpose: SSH access depends on 1Password's SSH agent, which is not
installed yet. After the first switch, git rewrites the HTTPS remote to SSH
(`url."git@simenandre.no:".insteadOf`), so this remote keeps working.

### 5. Grant Full Disk Access to the terminal

`modules/darwin/macos.nix` writes preferences for three sandboxed apps — Safari,
Help Viewer, and TextEdit. Their preference files live under
`~/Library/Containers/`, which macOS protects with TCC, so `defaults` is denied
unless the terminal running the switch has Full Disk Access. The activation
script runs under `set -e`, so a single denial aborts the whole switch.

System Settings → Privacy & Security → Full Disk Access → **+** → add
`/System/Applications/Utilities/Terminal.app` (Ghostty is not installed yet on a
fresh machine) → quit and reopen the terminal, so the new grant applies to a
fresh process.

### 6. First switch (bootstrap nix-darwin)

Pick the configuration that matches the machine — each one pins its own username,
so switching with the wrong attribute fails during activation:

| Attribute | Machine | User |
| --------- | ------- | ---- |
| `makkie`  | MacBook Pro (sets its hostname to `makkie`) | `simenandre` |
| `default` | Older MacBook Pro | `cobraz` |

```shell
sudo nix run nix-darwin#darwin-rebuild -- switch --flake .#makkie
```

This is the long one: it installs every Homebrew cask, the Mac App Store apps,
all nixpkgs packages, and applies the macOS defaults. Expect Homebrew to prompt
for your password, and expect to approve a few system dialogs.

Subsequent switches use the installed binary:

```shell
sudo darwin-rebuild switch --flake .#makkie
```

The `rebuild` and `update` aliases already carry the right attribute for the
machine they were built for, so after the first switch you can just run
`rebuild`.

Adding another Mac later means one entry in `flake.nix`:

```nix
"newhost" = mkDarwin {
  username = "simenandre";
  flakeAttr = "newhost";
  hostName = "newhost";
};
```

Restart the terminal (or log out and back in) so the new zsh config, `PATH`, and
Ghostty configuration take effect.

### 7. 1Password and SSH

Git commits are signed through 1Password's SSH agent
(`/Applications/1Password.app/Contents/MacOS/op-ssh-sign`), so this has to be
set up before committing anything.

1. Open **1Password**, sign in, and unlock it.
2. Settings → Developer → enable **Use the SSH agent**.
3. Settings → Developer → enable **Integrate with 1Password CLI** (for `op`).
4. Verify the signing key matches the one pinned in `modules/home/git.nix`:

   ```shell
   ssh-add -l
   ```

5. Confirm GitHub over SSH works:

   ```shell
   ssh -T git@github.com
   ```

### 8. Sign in to the rest

Each of these is a one-time manual login:

```shell
gh auth login          # GitHub CLI
op signin              # 1Password CLI
gcloud auth login      # Google Cloud
gcloud auth application-default login
```

GUI apps that need a login or permission grant: Tailscale (App Store), Slack,
Discord, Signal, Obsidian, Proton Mail, ProtonVPN, Spotify, Raycast (grant
Accessibility), Google Drive.

### 9. Container runtime

Docker CLIs come from Homebrew, but the VM has to be started once:

```shell
colima start
```

`DOCKER_HOST` is already pointed at `~/.colima/default/docker.sock` by
`modules/home/zsh.nix`.

### 10. Rust toolchain

`rustup` is installed, but ships without a toolchain:

```shell
rustup default stable
```

### 11. Tools not managed by Nix

These are installed with `go install` and live in `~/go/bin`. Shell completions
for them are generated on every switch, and the generator quietly skips any that
are missing:

```shell
go install github.com/sokkelorg/cli/cmd/so@latest
go install github.com/simenandre/bobbin/cmd/bobbin@latest
go install github.com/simenandre/robin-cli/cmd/robin@latest
go install github.com/dinacomputer/api/cmd/dinactl@latest
go install github.com/sokkelorg/identity/cmd/identityctl@latest
```

### 12. Loose ends

```shell
mkdir -p ~/Screenshots     # macos.nix points screencapture here; it is not created for you
```

`~/.gitignore_global` is referenced by `core.excludesfile` but is not managed by
this repo. Create it if you want it:

```
.DS_Store
.twm.yaml
.twm.yml

**/.claude/settings.local.json
```

Some macOS defaults (Dock, Finder, trackpad) only take effect after a logout or
restart.

## NixOS

```shell
sudo nixos-generate-config --show-hardware-config > hosts/nixbox/hardware-configuration.nix
sudo nixos-rebuild switch --flake .#nixbox
```

The `nixbox` host expects the user `simenandre`. Git commit signing is disabled
outside macOS, so no 1Password setup is required.

## Linux (standalone home-manager)

```shell
# x86_64
home-manager switch --flake .#simenandre

# aarch64
home-manager switch --flake .#simenandre@aarch64-linux
```

## Day-to-day

Aliases defined in `modules/home/zsh.nix` (macOS):

| Alias     | Does                                              |
| --------- | ------------------------------------------------- |
| `rebuild` | `darwin-rebuild switch` against this flake        |
| `update`  | `nix flake update`, then rebuild                  |
| `nix-gc`  | Garbage-collect store paths older than 14 days    |

`scripts/cleanup.sh` is a separate macOS cleanup utility (caches, logs, trash).

## Troubleshooting

**`error: Homebrew is not installed`** — step 3 was skipped, or `brew` is not on
`PATH` for the shell running the switch. Run `eval "$(/opt/homebrew/bin/brew shellenv)"`.

**Mac App Store apps fail to install** — sign in to the App Store, then re-run
the switch. `mas` cannot authenticate on its own.

**`defaults[…] Could not write domain …/Library/Containers/…; exiting`** — the
terminal lacks Full Disk Access (step 5), so a sandboxed app's preferences can't
be written, and `set -e` aborts the switch. Grant it, then reopen the terminal
before retrying — an already-running process keeps the old denial.

If it still fails after that, the app's container does not exist yet:

```shell
ls -d ~/Library/Containers/com.apple.Safari
```

`defaults` cannot create a container the app has never made. Launch the app once
(Safari, Help Viewer, or TextEdit — whichever the error names), quit it, and
re-run. The three container-backed domains are all in `modules/darwin/macos.nix`
under `CustomUserPreferences`; deleting the offending block is the other way out
if you don't care about those settings.

**home-manager refuses to overwrite an existing file** — files it would clobber
are backed up with a `.bak` suffix (`backupFileExtension = "bak"`). If a switch
still fails on a collision, a stale `.bak` from an earlier run is usually in the
way; delete it and re-run.

**A cask fails to install because the app already exists** — remove the
hand-installed copy from `/Applications` and re-run the switch.

**`warning: failed to generate _<tool>`** during activation — that tool is not
installed (see step 10). Harmless; only its completions are missing.

**A `nixpkgs` package suddenly fails to build** — check `flake.lock`. Pinned
overrides live in `modules/darwin/default.nix` (for example WhatsApp's version
and hash).