Add FredOS-Rescue: Claude Code rescue USB

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-05 21:22:14 +01:00
parent d40d9924b2
commit b960a6ccbf
5 changed files with 217 additions and 0 deletions

View file

@ -4,6 +4,7 @@ This is a NixOS flake-based configuration for multiple hosts:
- **FredOS-Gaming** — gaming desktop - **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-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-Macbook** — MacBook laptop
- **FredOS-Rescue** — live rescue USB that boots into Claude Code (`nix build .#rescue-iso`; prompt + greeting in `hosts/rescue/`)
## Structure ## Structure

View file

@ -109,6 +109,13 @@
FredOS-Eee = nixpkgs.lib.nixosSystem { FredOS-Eee = nixpkgs.lib.nixosSystem {
modules = [ ./hosts/FredOS-Eee.nix ./hosts/hardware/FredOS-Eee.nix ]; 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;
}; };
} }

102
hosts/FredOS-Rescue.nix Normal file
View file

@ -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@<ip>` 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;
}

41
hosts/rescue/menu.sh Normal file
View file

@ -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 <<EOF
${b}rescue${r} get online and start Claude (as root)
${b}nmtui${r} networking only (wifi, static IPs)
${b}menu${r} show this screen again
${b}How it goes${r}
1. Type ${b}rescue${r}. Wired connects by itself; otherwise pick a wifi.
2. First start: Claude shows a login URL. Open it on your phone,
sign in, paste the code back here.
3. Tell Claude what broke: what you saw, what changed before it did.
It looks first, and asks before anything touches a disk.
${d}Runs from RAM: nothing here survives a reboot. Your disks are
untouched until you OK a change. Keyboard is UK.${r}
EOF
ips=$(ip -brief -4 addr show scope global 2>/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

View file

@ -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-<N>-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#<pkg>`. 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.