From b960a6ccbfd84cc160180bade7f352211530d5a8 Mon Sep 17 00:00:00 2001 From: rope Date: Mon, 5 Oct 2026 21:22:14 +0100 Subject: [PATCH] Add FredOS-Rescue: Claude Code rescue USB Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 1 + flake.nix | 7 +++ hosts/FredOS-Rescue.nix | 102 ++++++++++++++++++++++++++++++++++ hosts/rescue/menu.sh | 41 ++++++++++++++ hosts/rescue/rescue-prompt.md | 66 ++++++++++++++++++++++ 5 files changed, 217 insertions(+) create mode 100644 hosts/FredOS-Rescue.nix create mode 100644 hosts/rescue/menu.sh create mode 100644 hosts/rescue/rescue-prompt.md diff --git a/CLAUDE.md b/CLAUDE.md index 50722ca..14224e8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,6 +4,7 @@ This is a NixOS flake-based configuration for multiple hosts: - **FredOS-Gaming** — gaming desktop - **FredOS-Mediaserver** — home media server **and the home router** (nftables NAT/firewall in `services/router.nix`; `networking.firewall` is disabled on this host, WAN exposure comes from `ports.toml`) - **FredOS-Macbook** — MacBook laptop +- **FredOS-Rescue** — live rescue USB that boots into Claude Code (`nix build .#rescue-iso`; prompt + greeting in `hosts/rescue/`) ## Structure diff --git a/flake.nix b/flake.nix index 2036f36..5b131a9 100644 --- a/flake.nix +++ b/flake.nix @@ -109,6 +109,13 @@ FredOS-Eee = nixpkgs.lib.nixosSystem { modules = [ ./hosts/FredOS-Eee.nix ./hosts/hardware/FredOS-Eee.nix ]; }; + # Live rescue USB with Claude Code; standalone like the Eee. + FredOS-Rescue = nixpkgs.lib.nixosSystem { + modules = [ ./hosts/FredOS-Rescue.nix ]; + }; }; + + packages.${system}.rescue-iso = + self.nixosConfigurations.FredOS-Rescue.config.system.build.isoImage; }; } diff --git a/hosts/FredOS-Rescue.nix b/hosts/FredOS-Rescue.nix new file mode 100644 index 0000000..5d37d5c --- /dev/null +++ b/hosts/FredOS-Rescue.nix @@ -0,0 +1,102 @@ +# FredOS-Rescue — live USB that boots into Claude Code for diagnosing and +# repairing a broken Linux (or Windows) install. Built on the minimal NixOS +# installer, which already brings NetworkManager, sshd and the usual disk +# tools (testdisk, ddrescue, smartctl, cryptsetup, ...; profiles/base.nix). +# +# nix build .#rescue-iso # -> result/iso/fredos-rescue-*.iso +# +# On boot: type `rescue`. It gets you online (nmtui) and starts Claude as root +# with hosts/rescue/rescue-prompt.md as its system-wide CLAUDE.md. Login is +# the usual URL + code dance (phone works); nothing persists across reboots. +# +# Remote mode: sshd runs with Fred's keys + the server's for root, so Claude +# elsewhere can `ssh root@` in (or you can run `rescue` over ssh). +{ config, lib, pkgs, modulesPath, ... }: +let + serverKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMbIdOSPjnJYQ7z7n0mHKo8T8PnAkCyIBCmT4KpsHyy/ fred@FredOS-Mediaserver"; + + # Any HTTP answer counts (no -f): the API root is a 404. + online = "${pkgs.curl}/bin/curl -sS -m 5 -o /dev/null https://api.anthropic.com 2>/dev/null"; + rescue = pkgs.writeShellScriptBin "rescue" '' + # Claude's binary needs AVX (2011+ CPUs); without it, it spins forever + # instead of crashing. + if ! grep -qw avx /proc/cpuinfo; then + echo "This CPU is too old for Claude (no AVX)." + echo "Use remote mode: get online with 'nmtui', run 'menu' for the" + echo "address, then run Claude on another machine and have it ssh in." + exit 1 + fi + # Typed right after boot, wired may still be coming up. + ${pkgs.networkmanager}/bin/nm-online -q -t 15 + if ! ${online}; then + echo "Not online yet. Pick a network (wired should connect by itself)." + sleep 1 + ${pkgs.networkmanager}/bin/nmtui-connect + fi + if ! ${online}; then + echo "Still offline. Try 'nmtui', then run 'rescue' again." + exit 1 + fi + exec sudo -i ${pkgs.claude-code}/bin/claude "$@" + ''; + menu = pkgs.writeShellScriptBin "menu" (builtins.readFile ./rescue/menu.sh); +in +{ + imports = [ + "${modulesPath}/installer/cd-dvd/installation-cd-minimal.nix" + ../settings/locale.nix + ../settings/users.nix # only for Fred's ssh keys below + ]; + + nixpkgs.hostPlatform = "x86_64-linux"; + nixpkgs.config.allowUnfreePredicate = pkg: lib.getName pkg == "claude-code"; + nix.settings.experimental-features = [ "nix-command" "flakes" ]; + + networking.hostName = "FredOS-Rescue"; + image.baseName = lib.mkForce "fredos-rescue-${config.system.nixos.label}"; + isoImage.appendToMenuLabel = " - Claude Rescue"; # no apostrophes: GRUB quotes it with '' + + users.users.root.openssh.authorizedKeys.keys = + config.users.users.fred.openssh.authorizedKeys.keys ++ [ serverKey ]; + # The installer leaves root/nixos with empty passwords; never over ssh. + services.openssh.settings.PasswordAuthentication = false; + services.openssh.settings.KbdInteractiveAuthentication = false; + + environment.etc."claude-code/CLAUDE.md".source = ./rescue/rescue-prompt.md; + + environment.systemPackages = with pkgs; [ + rescue + menu + claude-code + # Chroot into the patient: arch-chroot does the bind mounts for any + # distro; nixos-enter is already on the ISO for NixOS. + arch-install-scripts + mdadm + chntpw # Windows: reset passwords, edit registry hives + lsof + file + tree + ripgrep + git + rsync + wget + tmux + htop + lm_sensors + dmidecode + memtester + ]; + + # The installer's getty text scrolls past under autologin; the menu replaces + # it. Interactive only, so `sudo -i claude` inside `rescue` doesn't print it. + services.getty.helpLine = lib.mkForce ""; + environment.loginShellInit = '' + [[ $- == *i* ]] && menu + ''; + + # Serial too, for headless boxes (tty0 last = it keeps the boot messages). + boot.kernelParams = [ "console=ttyS0,115200" "console=tty0" ]; + + # Don't import ZFS pools that weren't exported cleanly; rescue asks first. + boot.zfs.forceImportRoot = false; +} diff --git a/hosts/rescue/menu.sh b/hosts/rescue/menu.sh new file mode 100644 index 0000000..64c39de --- /dev/null +++ b/hosts/rescue/menu.sh @@ -0,0 +1,41 @@ +# Greeting for FredOS-Rescue: shown on every interactive login, and again +# with `menu`. +b=$'\e[1m' y=$'\e[1;33m' d=$'\e[2m' r=$'\e[0m' + +# The console is still littered with boot messages at this point. +case "$(tty)" in /dev/tty*) clear ;; esac + +printf '%s' "$y" +cat <<'ART' + Fred's + ____ _ _ ____ + / ___| | __ _ _ _ __| | ___ | _ \ ___ ___ ___ _ _ ___ +| | | |/ _` | | | |/ _` |/ _ \ | |_) / _ \/ __|/ __| | | |/ _ \ +| |___| | (_| | |_| | (_| | __/ | _ < __/\__ \ (__| |_| | __/ + \____|_|\__,_|\__,_|\__,_|\___| |_| \_\___||___/\___|\__,_|\___| +ART +printf '%s\n' "$r" + +cat </dev/null | awk '{print $3}' | cut -d/ -f1) +if [ -n "$ips" ]; then + printf ' %sRemote:%s ssh root@%s\n\n' "$b" "$r" "$(echo $ips | sed 's/ / or root@/g')" +else + printf ' %sRemote: not online yet; run menu again once connected.%s\n\n' "$d" "$r" +fi diff --git a/hosts/rescue/rescue-prompt.md b/hosts/rescue/rescue-prompt.md new file mode 100644 index 0000000..aa12717 --- /dev/null +++ b/hosts/rescue/rescue-prompt.md @@ -0,0 +1,66 @@ +# You are running on a rescue disk + +This is FredOS-Rescue: a live NixOS USB stick, booted on a machine whose real +OS is broken. You run as root. The live system lives in RAM, so nothing you +install or change *on the live system* survives a reboot. The machine's own +disks are **the patient**. They are not mounted until you mount them. + +The user is Fred: at home in Linux and NixOS, but rarely does disk/boot +rescue, so explain what you find and why each step matters, in plain terms, +one step at a time. Ask about symptoms first (what happened, what changed +recently, what the screen says) before guessing. + +## Rules for the patient's disks + +1. **Diagnose read-only first.** Look before you touch: `lsblk -f`, `blkid`, + `fdisk -l`, `smartctl -a`, `dmesg`, `efibootmgr -v`. Mount read-only first + (`mount -o ro`, and `ro,noload` for ext4 so the journal isn't replayed). +2. **Every write needs a yes.** Before anything that modifies a disk (fsck + without `-n`, mkfs, partitioning, `dd`, bootloader installs, deleting or + editing files), state the exact command and what it will change. Then wait + for Fred's OK. +3. **A dying disk changes the plan.** If SMART shows reallocated, pending or + uncorrectable sectors, or dmesg shows I/O errors, stop repairing. Propose + imaging it first with `ddrescue` to another disk, then work on the copy. + Every extra read can make a dying disk worse. +4. **Back up before editing.** `cp file file.bak` before changing any config + on the patient. Never run fsck on a mounted filesystem. +5. **Recovered files go elsewhere.** photorec/testdisk output goes to a + different disk than the one being recovered, never the source. +6. LUKS passphrases come from Fred. Ask, never guess or brute force. + +## Useful moves + +- Was the live USB booted as UEFI? Check `[ -d /sys/firmware/efi ]`. Fixing a + bootloader needs the same mode the patient boots in. +- Read the patient's logs without booting it: + `journalctl -D /mnt/var/log/journal --list-boots`, then `-b -1 -p warning`. +- Chroot into any distro: mount root at `/mnt` (plus `/boot` or `/boot/efi`), + then `arch-chroot /mnt`. It handles /proc, /sys, /dev and efivars. +- NixOS patients: `nixos-enter --root /mnt`. Often the fastest fix is just + picking an older generation in the boot menu. Otherwise roll back from the + chroot with + `/nix/var/nix/profiles/system--link/bin/switch-to-configuration boot`. + List generations with `ls -l /mnt/nix/var/nix/profiles/`. +- Fred's own machines (FredOS-*) are NixOS flakes from + `git+https://forg.gregersen.it/rope/nixos` and normally build on the home + server (nordhammer.it). Prefer rolling back over rebuilding in the chroot. + FredOS-Gaming uses Lanzaboote Secure Boot: its keys live in + `/var/lib/sbctl` on the patient, and plain systemd-boot reinstalls won't be + signed. +- Windows patients: `ntfsfix` for dirty NTFS, `chntpw` for local accounts and + the registry, `ms-sys` for MBR boot code. BitLocker volumes need the + recovery key. +- Hardware suspicion: `memtester` (in-OS RAM test), `sensors`, `smartctl -t + short`, `dmidecode`. + +## Tools + +Already here: testdisk/photorec, ddrescue, smartctl, nvme-cli, hdparm, +parted, gdisk, cryptsetup, lvm2, mdadm, btrfs/xfs/ext4/f2fs/ntfs/vfat/zfs +tools, efibootmgr, arch-chroot, nixos-enter, chntpw, ms-sys, rsync, git, jq, +ripgrep, tmux. If you need something else, use `nix shell nixpkgs#`. It +lands in RAM, so mind memory on small machines. + +Remote access: sshd is running, root only accepts Fred's keys. +`ip -brief addr` shows where to reach it.