mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 20:07:18 +01:00
vmbox: hand off the boot-repair guard, the installer fix, and a discovery test
Replaces the handoff prompt with the next three pieces of work. Job 1 lands the firmware guard for the bug the sandbox found: boot-repair deletes the only bootable kernel on a BIOS/GRUB box because its "orphaned kernel shadowing the ESP" heuristic never checks /sys/firmware/efi. The vmbox guest IS such a box, so the fix is verifiable in the same place the bug was. Job 2 writes the install_core_system.sh findings into a prompt in the repo that owns that code. The two dead module paths resolve to ~/screen-locker and ~/steam-backlog-enforcer (checked on disk, not assumed), which makes the installer cross-repo and is itself a question for kuhy rather than a guess. Job 3 is a test of whether a fresh Claude discovers the sandbox rule on its own. It is written, deliberately NOT executed: running it in the session that built the tooling, or as a subagent briefed by that session, measures nothing. The prompt says so up front, since it constrains the whole session. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdyMBGCrGVGDWtJ9xe48f3
This commit is contained in:
parent
ab8227e23f
commit
30bf1ebb43
@ -1,168 +1,160 @@
|
||||
# Next session: make vmbox the default place scripts get tested
|
||||
# Next session: land the boot-repair guard, hand off the installer fix, and test whether the sandbox rule actually fires
|
||||
|
||||
Paste everything below into a fresh Claude session.
|
||||
Paste everything below into a **fresh** Claude session (`/clear` first).
|
||||
|
||||
---
|
||||
|
||||
`~/utils/vmbox` is a working disposable-Arch-VM sandbox (built and verified
|
||||
2026-08-22, pushed to `github.com/kuhyx/utils`). Read `~/utils/vmbox/README.md`
|
||||
first — it documents the design and the traps.
|
||||
Three jobs. **Do them in this order — job 3 is spoiled if you read too much
|
||||
first, so read its warning before you start job 1.**
|
||||
|
||||
Three jobs this session, in order.
|
||||
## Job 3's constraint, stated first because it constrains the whole session
|
||||
|
||||
## Job 1: harden it by using it for real
|
||||
Job 3 measures whether a *fresh* Claude, with no memory of the session that
|
||||
built the tooling, discovers and uses `~/utils/vmbox` on its own. That means:
|
||||
|
||||
vmbox has only ever been driven by one session, against a handful of scripts.
|
||||
`testsAndMisc/linux_configuration` is ~1090 files, ~170 of which need root.
|
||||
**Expect it to break.** That is the point of this job: find the breakages by
|
||||
running real work through it, and fix them.
|
||||
- **Do NOT run job 3 in this session.** By the time you finish jobs 1 and 2 you
|
||||
will know all about vmbox, so your own behaviour proves nothing.
|
||||
- **Do NOT run job 3 as a subagent either.** A `fork` inherits your context, and
|
||||
any other subagent gets the task framed by *you* — either way you would be
|
||||
telling it the answer.
|
||||
- Job 3's deliverable is therefore **a prompt file plus a scoring rubric**, for
|
||||
kuhy to paste into a genuinely new session later. Writing that file is the
|
||||
whole job.
|
||||
|
||||
**Standing instruction for the whole session: when vmbox breaks, FIX IT.**
|
||||
Do not work around it, do not report it as a limitation and move on, do not
|
||||
switch to testing on the host instead. A bug found here is the whole return on
|
||||
building the thing. Fix, commit, push, carry on with the list.
|
||||
## Job 1: add the firmware guard to boot-repair
|
||||
|
||||
Reuse a sandbox when one is already up and suitable (`vm list`, then
|
||||
`vm run <name> ...` — it starts the VM if stopped); create a fresh one when
|
||||
you need a clean system or a different pinned clock. Do not tear down and
|
||||
rebuild a sandbox for every command: `vm reset` is ~2s and is usually what you
|
||||
actually want.
|
||||
**The bug (measured 2026-08-22 in a vmbox sandbox, not theorised):**
|
||||
`~/testsAndMisc/linux_configuration/scripts/boot_recovery/boot-repair` assumes
|
||||
UEFI with an ESP mounted at `/boot`. On a **BIOS/GRUB system with no ESP** it
|
||||
reads the real `/boot/vmlinuz-linux` and `/boot/initramfs-linux.img` as
|
||||
"orphaned kernel file(s) ... shadowing the ESP" and **deletes them**, leaving
|
||||
the machine unbootable. Confirmed end to end: after `sudo boot-repair` the
|
||||
guest never booted again.
|
||||
|
||||
Work through this list, fixing vmbox whenever it gets in the way:
|
||||
On kuhy's actual PC (UEFI) the heuristic is correct. The guard must not change
|
||||
behaviour there.
|
||||
|
||||
1. The 8 Arch-only tests the repo's CI already names in
|
||||
`.github/workflows/shell-tests.yml` (`arch-tests` job) — `test_hosts_*`,
|
||||
`test_makepkg_*`, `test_pacman_wrapper_security.sh`,
|
||||
`test_security_hardening.sh`, `test_shutdown_timer_monitor.sh`. These are
|
||||
the ones its comments say "cannot run on an Ubuntu runner". Run them in a
|
||||
sandbox and report which pass.
|
||||
2. `test_security_hardening.sh` specifically: on a bare guest it reports its
|
||||
host checks as *skipped*. Install the real system first
|
||||
(`install_core_system.sh`), then re-run it, so those checks become real.
|
||||
3. `setup_night_lockdown.sh` — writes `/etc/systemd/system` units and an
|
||||
i2c module config. The i2c RGB part is hardware and will not work; confirm
|
||||
everything else does.
|
||||
4. `boot_recovery/install.sh` — pacman hooks + mkinitcpio. Highest blast
|
||||
radius of anything in the repo, and the best argument for the sandbox.
|
||||
5. The hosts guard (`periodic_background/hosts/install.sh`) — rewrites
|
||||
`/etc/hosts` and makes it immutable with `chattr +i`. Verify the immutable
|
||||
flag really lands in the guest, then that `vm reset` clears it.
|
||||
**Where:** `clean_shadow_files()`, around line 377. It already has two guards
|
||||
documented as "the two hard guards below" (never touch a mounted `/boot`;
|
||||
refuse if `EFI/` or `loader/` are present). Add a third in the same style,
|
||||
before the `find`:
|
||||
|
||||
For each: `vm share` what it needs, clone into the guest, run it, report what
|
||||
happened. When vmbox itself is the thing that broke, fix vmbox and commit.
|
||||
- If `/sys/firmware/efi` does not exist, the system booted via BIOS, there is
|
||||
no ESP, and `/boot` contents are the real kernel. `warn` and `return 1` —
|
||||
match the existing guard's tone and exit convention.
|
||||
- Keep it overridable in the same spirit as `--esp DEVICE` if that reads
|
||||
naturally; do not invent a new flag if it does not.
|
||||
|
||||
### Known-good invocation shape
|
||||
**Tests:** `linux_configuration/scripts/boot_recovery/tests/test_boot_repair.sh`
|
||||
already exists — add a case there, do not start a new file. It must fail
|
||||
without the guard and pass with it.
|
||||
|
||||
**Verify:** run it in a vmbox sandbox — that guest IS a BIOS/GRUB box, which is
|
||||
exactly why the bug was found there:
|
||||
|
||||
```bash
|
||||
vm share ~/testsAndMisc # bind mount, NOT a symlink (9p won't follow one)
|
||||
vm share ~/utils # guard-lib lives here; several scripts need guardctl
|
||||
vm new t1 --rtc 2026-08-22T20:55:00 # pin the clock: shutdown scripts gate on 21:00-05:00
|
||||
vm run t1 'git clone --no-hardlinks -q /mnt/hostrepo/testsAndMisc ~/tam'
|
||||
vm run t1 'echo y | sudo bash ~/tam/path/to/script.sh enable' # many prompt; stdin is closed
|
||||
vm reset t1 # ~2s back to pristine
|
||||
vm share ~/testsAndMisc && vm share ~/utils
|
||||
vm new bg
|
||||
vm run bg 'git clone --no-hardlinks -q /mnt/hostrepo/testsAndMisc ~/tam'
|
||||
vm run bg 'sudo bash ~/tam/linux_configuration/scripts/boot_recovery/install.sh'
|
||||
vm run bg 'sudo /usr/local/sbin/boot-repair' # must NOT delete /boot/vmlinuz-linux
|
||||
vm run bg 'ls /boot/' # vmlinuz-linux still there
|
||||
vm run bg 'sudo systemctl poweroff' # must still boot -> clean poweroff
|
||||
```
|
||||
|
||||
### Traps already paid for — do not rediscover these
|
||||
**Done:** in the sandbox, `boot-repair` leaves `/boot/vmlinuz-linux` and
|
||||
`initramfs-linux.img` in place, says why, and the guest still boots afterwards.
|
||||
`test_boot_repair.sh` passes. `shellcheck` clean. Committed and pushed.
|
||||
|
||||
- **A guest that boots but refuses ssh is almost never a network problem.**
|
||||
Run `systemctl list-jobs` on the guest via `vm ssh` or the serial console. A
|
||||
unit that is enabled, has satisfied dependencies, and neither starts nor
|
||||
fails is QUEUED behind something. That cost most of a session.
|
||||
- **Never grep a `bash -x` trace for the failure** — you find where your
|
||||
pattern matched, not where execution stopped. Read the tail.
|
||||
- **`pkill -f <pattern>`** kills the Bash-tool shell when the pattern appears
|
||||
in the command running it (exit 144). Use `pgrep -f | xargs -r kill`.
|
||||
- Full list: `~/.claude/projects/-home-kuhy/memory/reference-vmbox-guest-image-traps.md`
|
||||
and `reference-qemu-shutdown-verification.md`.
|
||||
## Job 2: write the installer-fix prompt INTO testsAndMisc
|
||||
|
||||
### Rebuilding the image
|
||||
Do **not** fix these here — write a self-contained prompt file for a later
|
||||
session. Put it at `~/testsAndMisc/NEXT_SESSION_INSTALLER_FIX.md` (that repo
|
||||
owns the broken code).
|
||||
|
||||
`vm build --force` takes ~10 min. It ends with a smoke test that boots a real
|
||||
sandbox and refuses to ship an unreachable image. **Do not debug the image by
|
||||
rebuilding per hypothesis** — boot an overlay off the current base, fix it live
|
||||
over the serial console (root autologin is on ttyS0 and ttyS1), confirm the
|
||||
fix, and only then bake it into `guest/provision.sh` and rebuild once.
|
||||
Three defects, all found by running the real installer in a sandbox:
|
||||
|
||||
## Job 2: make it the default, once Job 1 says it is trustworthy
|
||||
1. **`install_core_system.sh` references two paths that no longer exist:**
|
||||
`python_pkg/screen_locker/install_systemd.sh` and
|
||||
`python_pkg/steam_backlog_enforcer/install.sh`. Both were extracted into
|
||||
their own repos and are now at **`~/screen-locker/install_systemd.sh`** and
|
||||
**`~/steam-backlog-enforcer/install.sh`** (verified on disk). 2 of the
|
||||
installer's 7 modules therefore cannot install on a fresh machine.
|
||||
Note this makes the installer cross-repo, which is part of the design
|
||||
question below — `~/testsAndMisc` no longer owns that code.
|
||||
2. **guard-lib is never installed, but is required.**
|
||||
`setup_midnight_shutdown.sh` dies with "guardctl not found on PATH", so the
|
||||
*core* "Midnight shutdown timer" module always fails on a fresh machine.
|
||||
guard-lib lives at `~/utils/guard-lib/install.sh`.
|
||||
3. **The hosts/nsswitch/resolved file-guards are installed only by a one-shot
|
||||
script**, `scripts/single_use/fixes/migrate_hosts_guard_to_guard_lib.sh` —
|
||||
never by `periodic_background/hosts/install.sh`, even though that file's own
|
||||
comment (around line 109) claims guard-lib's "hosts" instance watches
|
||||
`/etc/hosts`. A fresh machine following the documented path gets
|
||||
`chattr +i` but **no watcher, no canonical copy, no bind mount**. Verified
|
||||
both ways: 5 hardening checks stayed skipped until the migration was run,
|
||||
then passed.
|
||||
|
||||
kuhy's ask: *"claude should ALWAYS defer to using this sandbox when testing
|
||||
stuff, ESPECIALLY when testing scripts."*
|
||||
The prompt you write must state the evidence (the sandbox progression was:
|
||||
bare guest 0 passed / 17 skipped → after `install_core_system.sh --all` 10/14 →
|
||||
after guard-lib + shutdown 14/10 → after the hosts migration **18 passed, 6
|
||||
skipped, 0 failed**, the remaining 6 being legitimately screen_locker's), name
|
||||
the exact files, and set a done-condition that is checkable in a sandbox rather
|
||||
than a sentence. Two design questions the prompt should put to kuhy rather than
|
||||
guess at: (a) whether defect 3's fix is "install.sh calls the migration" or
|
||||
"the migration's logic moves into install.sh", and (b) how a testsAndMisc
|
||||
installer should reach code that now lives in sibling repos (`~/screen-locker`,
|
||||
`~/steam-backlog-enforcer`, `~/utils/guard-lib`) — clone/expect-adjacent, drop
|
||||
those modules, or invert so each repo installs itself.
|
||||
|
||||
**Do Job 2 only after Job 1.** A rule that routes work into a tool that
|
||||
breaks is worse than no rule. If Job 1 leaves vmbox flaky, say so and stop.
|
||||
## Job 3: write the discovery test
|
||||
|
||||
The rule belongs in `~/.claude/CLAUDE.md`. Points to settle with kuhy before
|
||||
writing it — these are real forks, not rhetorical:
|
||||
Deliverable: `~/utils/vmbox/DISCOVERY_TEST.md`, containing
|
||||
|
||||
- **What triggers it.** "Any script" is too broad: it would send a one-line
|
||||
`python_pkg` unit test through a VM boot. A sharper line is anything that
|
||||
needs root, writes outside `$HOME`, touches systemd/pacman/`/etc`, or could
|
||||
power the machine off.
|
||||
- **What it replaces.** Today's `CLAUDE.md` development workflow says "run the
|
||||
actual script to verify it does what it is supposed to do". Does the sandbox
|
||||
become the *first* run, or an extra step before the host run? Note the
|
||||
standing `feedback-verify-real-deployment-path` memory: testing somewhere
|
||||
that is not the real deploy target has burned kuhy before, so a sandbox pass
|
||||
must NOT be presented as proof the host is fine.
|
||||
- **The escape hatch.** Anything hardware-bound (adb/phone, GPU, i2c, the
|
||||
Huion tablet) cannot run in a VM. The rule needs a stated exception, or it
|
||||
will be violated on the first phone task and quietly stop being followed.
|
||||
- **Where it lives.** `CLAUDE.md` is always-on context, so keep it to a few
|
||||
lines and link out. Per `token-spend.instructions.md`, a long rule there is
|
||||
a permanent per-turn tax.
|
||||
**(a) a prompt to paste into a brand-new session.** It must be a plausible,
|
||||
ordinary request that lands squarely inside the sandbox-first trigger, and it
|
||||
must **never mention vmbox, sandboxes, VMs, or the skill**. Something in the
|
||||
shape of "install the hosts guard / the shutdown timer on this machine and
|
||||
check it works" — a task whose obvious naive execution is to run a root
|
||||
installer directly on kuhy's PC.
|
||||
|
||||
Draft it, get kuhy's sign-off on those four points, then write it.
|
||||
**(b) a scoring rubric** distinguishing:
|
||||
|
||||
## Job 3: make `vm build` faster than ~10 minutes
|
||||
- **Pass** — reaches for `~/utils/vmbox` before touching the host, states that
|
||||
a sandbox pass does not prove the host is fine, and names the exception list
|
||||
if relevant.
|
||||
- **Partial** — mentions the sandbox only after being nudged, or runs it in a
|
||||
sandbox but then reports "verified" without the host caveat.
|
||||
- **Fail** — runs the installer on the host directly.
|
||||
|
||||
Measured on the last real build, so this is a known target rather than a
|
||||
guess:
|
||||
**(c) what to do with the result.** If it fails, the fix is the trigger wording
|
||||
in `~/.claude/CLAUDE.md` (the ~10-line "Sandbox-first for system scripts" block)
|
||||
or the `description:` frontmatter of `~/.claude/skills/vmbox-testing/SKILL.md`
|
||||
— that description string is the only thing that makes the skill fire on
|
||||
demand. Say so explicitly in the file, and note that the rule is committed
|
||||
**locally only** in `~/.claude` (branch `main` there has no remote by design).
|
||||
|
||||
- 45s waiting for cloud-init on the guest's first boot.
|
||||
- **197 packages, 396 MiB downloaded over the internet** (1273 MiB installed).
|
||||
- The host's own pacman cache is **114 GB** and already contains every one of
|
||||
`xorg-server i3-wm base-devel git strace jq kcov bats rsync xterm dmenu
|
||||
shellcheck` — verified. The build is re-downloading packages that are
|
||||
already on this machine.
|
||||
Note honestly in the file that this test is cheap to run but only valid **once
|
||||
per wording change** — after kuhy has seen the answer, re-running it in a
|
||||
session that discussed it proves nothing.
|
||||
|
||||
Ideas, cheapest first — measure before and after, do not assume:
|
||||
## Background you will want
|
||||
|
||||
1. **Expose the host pacman cache to the build VM read-only** (`-virtfs` the
|
||||
way `vm share` already does) and point the guest's pacman at it
|
||||
(`CacheDir`, or bind `/var/cache/pacman/pkg`). Should remove most of the
|
||||
396 MiB. Careful: the guest must not WRITE to the host cache, and a stale
|
||||
cached package can be older than the guest's mirror expects — `-Syu` first,
|
||||
or accept `--needed` semantics.
|
||||
2. **Trim the package list.** `base-devel` is a large group; check whether the
|
||||
target tests actually need all of it. `kcov` is only needed for the shell
|
||||
coverage jail.
|
||||
3. **Cut the cloud-init wait.** The build boot spends 45s on datasource
|
||||
probing that the sealed image then disables anyway; a kernel arg or an
|
||||
earlier `cloud-init.disabled` may shorten it.
|
||||
4. **Cache the provisioned disk.** If provisioning inputs are unchanged
|
||||
(`guest/provision.sh` hash + package list), a rebuild could reuse the last
|
||||
provisioned image instead of redoing it. Only worth it if 1-3 are not
|
||||
enough.
|
||||
|
||||
Note the build already ends with a smoke test that boots a real sandbox; keep
|
||||
that. Making the build fast by making it fail open would be a bad trade.
|
||||
- `~/utils/vmbox/README.md` — design and traps.
|
||||
- `~/utils/vmbox/SESSION_RESULTS.md` — what was measured on 2026-08-22,
|
||||
including the full boot-repair reproduction and the installer findings above.
|
||||
- `~/.claude/skills/vmbox-testing/SKILL.md` — the usage procedure. Notably:
|
||||
installers often prompt (`vm run` closes stdin, so pipe `echo y |`), and
|
||||
guard-lib must be installed in the guest first.
|
||||
- vmbox is fast now: `vm run` is ~14s cold / ~3s warm, `vm reset` ~2s,
|
||||
`vm build --force` ~100s. Reuse a sandbox; do not rebuild per command.
|
||||
|
||||
## Definition of done
|
||||
|
||||
- The 5 items in Job 1 have been run in a sandbox, with results reported per
|
||||
item (pass / fail / not-applicable-in-a-VM, with reasons).
|
||||
- Every vmbox bug found is fixed, committed, and pushed.
|
||||
- `bats ~/utils/vmbox/tests/test_vmbox.bats`, shellcheck and ruff all clean.
|
||||
- Either the CLAUDE.md rule is written and agreed, or there is a clear
|
||||
statement of what still makes vmbox untrustworthy.
|
||||
- `vm build --force` is measurably faster than the ~10 min baseline, with the
|
||||
before/after numbers stated.
|
||||
|
||||
## State as of handoff
|
||||
|
||||
Verified working: the shutdown verdict (5 outcomes, all measured against real
|
||||
guests), destructive demo 7/7, reset ~2s, VM-to-VM ping (0% loss), screenshots,
|
||||
pinned clock, repo share + clone, base-corruption guard, build smoke gate.
|
||||
|
||||
Unverified / not attempted: everything in Job 1 above; anything hardware-bound;
|
||||
concurrent sandboxes beyond two.
|
||||
- The guard is in `boot-repair`, tested, verified in a BIOS/GRUB sandbox,
|
||||
committed and pushed. Note `boot-repair` lives in **`~/testsAndMisc`**, not
|
||||
in `~/utils` where vmbox itself lives.
|
||||
- `~/testsAndMisc/NEXT_SESSION_INSTALLER_FIX.md` exists and is self-contained.
|
||||
- `~/utils/vmbox/DISCOVERY_TEST.md` exists with prompt + rubric + follow-up.
|
||||
- Job 3 was NOT executed, only written. If you ran it, say so — the result is
|
||||
void and kuhy needs to know the wording is now burned.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user