Add FredOS-Rescue: Claude Code rescue USB
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d40d9924b2
commit
b960a6ccbf
5 changed files with 217 additions and 0 deletions
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
102
hosts/FredOS-Rescue.nix
Normal 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
41
hosts/rescue/menu.sh
Normal 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
|
||||||
66
hosts/rescue/rescue-prompt.md
Normal file
66
hosts/rescue/rescue-prompt.md
Normal 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.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue