`update.sh --pull` could abort with "Your local changes to the following files would be overwritten by merge: web/bun.nix" — before a single service was restarted — and the only way past it was to delete the file by hand. The updater did it to itself. web/bun.nix is generated (bun2nix, a pure function of web/bun.lock) but committed, because the Nix build fetches node_modules only from it. The web step ran `bun install --frozen-lockfile` without --ignore-scripts, so web's `postinstall` (`bun2nix -o bun.nix`) rewrote that tracked file on every update. Harmless while the committed file is in sync — but main carried a stale web/bun.nix from1db8f763tob79d90b4, so any Deck updated in that window had it rewritten to the *correct* content and has been sitting dirty ever since. The SDK step has always passed --ignore-scripts, which is why only web/bun.nix ever went dirty. Two changes, both in install.sh and update.sh: * the web install now passes --ignore-scripts and runs `bun run codegen` explicitly. web has two install lifecycle scripts and we want exactly one: `prepare` IS `bun run codegen` (orval + paraglide + the i18n check) and is required, since src/api/gen, src/paraglide and src/routeTree.gen.ts are gitignored and `prebuild` only re-runs orval; `postinstall` is the one that writes a committed file. Equivalent to the old behaviour minus bun2nix. * --pull restores web/bun.nix and sdk/bun.nix before pulling, which unsticks the installs already broken out there. Deliberately NOT `git reset --hard`: $SRC is the operator's own checkout and may carry real local work, so a still-dirty tree now fails with a message that names the files and the way out instead of git's raw abort. Discarding these two is provably lossless — regenerating them from the lockfiles is exactly what bun2nix does. CI already gates the drift that made this visible (scripts/ci/check-bun-nix.sh, ci.yml), so main cannot ship a stale bun.nix again.