aboutsummaryrefslogtreecommitdiff
Commit message (Collapse)AuthorAgeFilesLines
...
* feat(archsetup): make the claude-code install optionalCraig Jennings2026-05-262-4/+32
| | | | The claude-code step is a curl|sh from a third party, and not every user wants AI tooling. I put it behind INSTALL_CLAUDE_CODE (default yes, so current behavior is unchanged), with matching --claude-code / --no-claude-code flags. I wired it the same way as AUTOLOGIN and NO_GPU_DRIVERS: config mapping, a validate_config yes/no check, and a guard around the install block. I also documented the key in archsetup.conf.example.
* fix(archsetup): give git_install the same retry logic as pacman/aurCraig Jennings2026-05-261-9/+28
| | | | | | pacman_install and aur_install retry up to MAX_INSTALL_RETRIES. git_install only retried the clone once and never the build. I folded it into the same retry loop: it cleans the build dir between attempts and runs clone && make-install as one command list, so the captured exit code is the real failure rather than the if-statement's status (the trap retry_install documents). One behavior change: a build that fails every attempt now warns and continues instead of being fatal on the re-clone path. That matches how pacman and aur failures are handled, so one source build that won't compile no longer aborts the whole install. It surfaces in the error summary instead.
* docs: add per-host override spec and backlog tasksCraig Jennings2026-05-262-4/+260
| | | | | | I added a spec for a per-machine override mechanism (docs/PLAN-per-host-overrides.org). It proposes per-host stow tiers keyed on hostname, with the existing conf.d glob as the first tenant, so HiDPI scaling can differ between ratio and velox without per-app patches. It's gated on review. I also updated the task list: added a pocketbook development backlog and a waybar timer module task, fleshed out the per-host task with a spec link, and rewrote the stale velox "pocketbook not installed" review item as a dated entry now that nothing installs it.
* refactor: fold pocketbook in-tree and drop its install stepsCraig Jennings2026-05-2620-3/+1054
| | | | | | Pocketbook is nowhere near ready, so I pulled it back from publication: deleted the github mirror and the cjennings.net repo, removed the server mirror hook, and copied the package into pocketbook/ here until it's ready to spin back out. Dropped the steps that provisioned it on a fresh install: the gtk4-layer-shell dep and the pip install in archsetup, and the clone in post-install.sh. That clone pointed at the now-deleted github repo, so it would have failed a fresh run regardless. Re-wiring the install is tracked in the pocketbook backlog.
* chore: log dotfiles-separation progress and file processed handoffsCraig Jennings2026-05-235-0/+193
|
* fix(testing): expect ~/.dotfiles symlink target in dotfiles validationCraig Jennings2026-05-221-2/+4
|
* docs(claude): correct velox/ratio desktop env (both run Hyprland)Craig Jennings2026-05-221-2/+3
|
* docs(claude): document the external dotfiles repo and migrationCraig Jennings2026-05-221-3/+41
| | | | | | Phase 2 moved the dotfiles into their own repo at git.cjennings.net/dotfiles.git, so a fresh install clones DOTFILES_REPO to ~/.dotfiles and stows per DESKTOP_ENV. I documented that, the DOTFILES_REPO/BRANCH/DIR config keys, the DOTFILES= Makefile override, the post-install pull-and-restow flow, and the steps to migrate an existing machine off the in-repo dotfiles/. The migration note calls out quitting Hyprland first: unstowing while it runs makes it write a stub hyprland.conf into the gap, which blocks the restow. The in-repo dotfiles/ stays until ratio and velox migrate, then it gets removed.
* refactor(testing): clone the dotfiles repo into the VM testCraig Jennings2026-05-222-1/+32
| | | | The dotfiles live in a separate repo now, so the VM test clones that source, bundles it, and clones from the bundle into /tmp/dotfiles-test on the VM. archsetup's in-VM clone of DOTFILES_REPO then resolves against the local path with no network. DOTFILES_SOURCE overrides the source for testing a local checkout instead of the published repo. The cleanup trap tracks the host-side bundle and clone, so a mid-run abort leaves nothing behind.
* feat(archsetup): clone dotfiles repo and stow per DESKTOP_ENVCraig Jennings2026-05-222-33/+84
| | | | | | Dotfiles now live in their own repo (git.cjennings.net/dotfiles.git), so archsetup clones DOTFILES_REPO to the user's ~/.dotfiles and stows the right tree for the chosen DESKTOP_ENV: dwm and hyprland get common/ plus their own layer, none gets the standalone minimal/ tree. The clone runs as the target user, so the working tree is user-owned with no chown-after race. Add DOTFILES_REPO / DOTFILES_BRANCH / DOTFILES_DIR config keys with the same injection guard the other repo keys carry. If the clone doesn't produce a git checkout, error_fatal stops the install rather than silently skipping the restore step that reverts what stow --adopt pulled in. That restore now runs for every DESKTOP_ENV, including none — minimal/ ships the .bashrc/.bash_profile that collide with /etc/skel, so its --adopt needs the same cleanup.
* docs(plan): use SSH push URL and add claude.sh to minimal treeCraig Jennings2026-05-221-2/+6
|
* chore: tag sub-30-minute tasks with :quick:Craig Jennings2026-05-211-18/+18
| | | | I walked the open work and tagged 18 tasks finishable in 30 minutes or less — CSS spacing tweaks, single-file archsetup edits, gitignore-and-untrack cleanups, and config-flag gates. The tag filters the agenda down to quick wins.
* chore: cancel obsolete X11 VT-switch task, demote 6 undated A's to BCraig Jennings2026-05-211-9/+180
| | | | | | | | The priority scheme requires an [#A] to carry a date. Six were undated aspirational backlog with no active work — the CI/CD pipeline, recovery-script generation, the package-inventory trio (inventory system, monthly review, automated comparison), and security education — so I dropped them to [#B]. I cancelled "Prevent X termination and VT switching." Its whole approach is xorg.conf.d with DontVTSwitch and DontZap, dead now that these machines run pure Wayland with no XWayland. I also recorded today's review date on the open tasks I walked.
* feat(notify): add --silent flag, volume knob, and level sound filesCraig Jennings2026-05-2112-10/+271
| | | | | | | | The touchpad toggle's notification was too loud, and the eight notify sounds varied by ~13 dB in RMS loudness — bug and fail came out two to three times louder than info or security. I added a --silent flag to notify (shows the popup, plays no sound) and a NOTIFY_VOLUME knob (paplay scale, default 65536) so the master level can drop without re-encoding. toggle-touchpad now passes --silent on both enable and disable. normalize-notify-sounds.sh measures each .ogg and shifts it to a uniform -31 dB mean. It writes through the file instead of mv-ing over it, so the stow symlinks survive when the script runs against the live sound dir. I re-encoded all eight sounds to the new level. Tests: a new tests/notify suite (12 tests) covers --silent, the volume knob, flag composition, and the error paths.
* chore: log UI follow-up bugs and ignore dated secret backupsCraig Jennings2026-05-212-56/+61
| | | | I logged three follow-ups: uneven waybar indicator spacing, airplane-mode toggle hardening (a laptop guard and a brightness fallback), and rectangular wlogout exit-menu buttons. I also moved the finished touchpad and airplane-mode entries into the resolved section, and added a gitignore rule so dated .bak backups stay on disk but out of git.
* feat(hyprland): add airplane-mode waybar toggleCraig Jennings2026-05-217-0/+652
| | | | | | | | I added a laptop-only waybar button that drops the machine into a low-power state and restores it on a second click. Engaging turns wifi off, sets the CPU energy-performance preference to power, dims the backlight to 35%, and stops network-only services (tailscale, proton-vpn, avahi, cups, wsdd, geoclue, sshd, fail2ban, syncthing). Bluetooth is left alone so earbuds keep working. Disengaging replays the state recorded when airplane mode was engaged rather than writing hardcoded defaults. A lever already in its low-power position is left untouched: wifi that was already off stays off, and a service that was already stopped isn't restarted. The indicator hides itself on machines with no battery, so desktops never show the button. State lives in $XDG_RUNTIME_DIR/airplane-state, and the bar refreshes the moment the toggle fires via a realtime signal.
* feat(authinfo): add calendar feed URLs for calendar-syncCraig Jennings2026-05-211-0/+0
| | | | The .ics feed URLs are secret tokens, so I keep them in the encrypted store instead of a plaintext config. Emacs calendar-sync looks them up at sync time through auth-source on the calendar-google, calendar-proton, and calendar-deepsat hosts. They travel across machines now with the rest of authinfo.
* chore(btop): sync config to v1.4.7 defaultsCraig Jennings2026-05-201-2/+16
|
* feat(hyprland): rebind monitor to Super+R, touchpad toggle to Super+MCraig Jennings2026-05-201-3/+3
| | | | Super+M now toggles the touchpad (M for Mouse), and the monitor scratchpad moves to Super+R (R for Resources, since btop is a resource monitor). The touchpad toggle was on Super+F9, which is now free. Super+Shift+M still switches to the monocle layout.
* chore: document a priority scheme and close two tasks in todo.orgCraig Jennings2026-05-201-5/+70
| | | | | | I added an A/B/C/D priority scheme to the task list: what each level means, the assignment criteria, and the rule that an [#A] task needs a scheduled or deadline date so priorities stay grounded in time. I also closed two finished tasks: the rm -rf path guard (done, with a pointer to the safe_rm_rf helper and its tests) and the touchpad toggle indicator (spec, implementation, and deploy record).
* feat(hyprland): add touchpad state indicator to waybarCraig Jennings2026-05-207-0/+212
| | | | | | | | The $mod+F9 toggle and the toggle-touchpad / touchpad-auto scripts already worked, but the scripts lived only in ~/.local/bin and were never committed, and there was no way to see the touchpad's state at a glance. I committed both scripts into the repo so stow installs them, and added a waybar indicator: a waybar-touchpad status script and a custom/touchpad module that shows a mouse glyph when the touchpad is on and a mouse-off glyph (in the dupre orange) when it's off. The scripts signal the module with pkill -RTMIN+9 waybar after each state change, so the icon updates the moment the touchpad toggles. touchpad-auto now runs at login via exec-once. The waybar-touchpad script has unit tests under tests/waybar-touchpad/ covering the enabled, disabled, and missing-state-file cases.
* fix(installer): guard constructed-path rm -rf deletesCraig Jennings2026-05-202-3/+229
| | | | | | | | Three rm -rf sites in archsetup delete paths built from variables: $state_dir for --fresh, and $source_dir/$prog_name for the git and AUR clone-retry cleanups. If a path variable were empty or malformed (preflight skipped, a degenerate git URL), the delete could expand to a top-level or otherwise unintended directory. I added a safe_rm_rf <path> <allowed_prefix> helper that refuses to run unless the target is absolute, free of '..', deeper than a bare top-level dir, strictly inside the allowed prefix, and a real directory rather than a symlink. On the happy path it delegates to rm -rf, so successful installs are unchanged. The helper is self-contained and defined before the top-level --fresh handler, which runs before the logging helpers exist. I covered the guard with unit tests under tests/safe-rm-rf/ that source the real function and exercise normal, boundary, and error cases against temp directories.
* chore: clean dead todo.org doc-links and file three docs to outboxCraig Jennings2026-05-204-6/+190
| | | | | | I removed three dead `file:` links in todo.org. They pointed at docs that were never written: testing-strategy.org, firmware-cleanup.org, and PLAN-browser-themes.org. Each task body already carries that content inline, so the links were dead weight. I also reworded the Testinfra task's lead-in that claimed the testing-strategy doc exists. I filed three resolved reference docs into assets/outbox/: the calendar-sync scrub note, the tmux copy-mode handoff, and the 2026-05-19 lint follow-up report.
* chore: log the calendar-secret scrub and queue URL rotationCraig Jennings2026-05-201-11/+21
| | | | Recorded the git-history scrub of the private calendar config and added a follow-up to rotate the three exposed feed URLs once I'm at ratio's GUI. Also closed the testing-docs and shellcheck tasks that shipped this round.
* fix: correct a POSIX-sh bashism in init and document two SC2034 casesCraig Jennings2026-05-203-2/+4
| | | | | | init runs under #!/bin/sh but used $(<file) to read /etc/hostname, a bashism that breaks on a strict POSIX sh. I switched it to $(cat) and quoted $interface_up in the same script. The two VM_IP assignments in the test scripts are read by the sourced validation.sh, which shellcheck can't follow, so they now carry a documented disable=SC2034 instead of a bare suppression. The rest of the shellcheck findings across the scripts are intentional (word-splitting on $SSH_OPTS, integer tests in [ ]) or already accepted, so I left them alone.
* docs: document the unit-test layer and add a make test-unit targetCraig Jennings2026-05-202-2/+37
| | | | | | The README only covered the VM integration harness. The unit suites under tests/ (one directory per script, fake binaries on PATH) went unmentioned, so a contributor adding a .local/bin script had no way to know they existed or how to run them. I added a make test-unit target that runs every tests/*/test_*.py suite explicitly. unittest discover can't find them because the per-script directory names are hyphenated and aren't valid package paths. Then I split the README Testing section into Unit and VM-integration layers, added a guide for adding a suite, and pointed Contributing at test-unit for script changes.
* chore: log AUR exit-0 fix verification and close the sub-taskCraig Jennings2026-05-191-6/+4
| | | | | | Two updates in todo.org. I marked the two dconf entries in the `[#B] Fix install errors` parent body as RESOLVED in `dc06895`, since the system-wide dconf db migration verified clean in last night's VM run. I converted the `*** TODO [#B] AUR exit-0 logged as error` sub-task into a dated event-log entry. The root cause was a bash if-compound semantics trap in `retry_install` (the if-compound exits 0 when no condition tested true, overwriting the eval's actual exit code that `last_exit_code=$?` was trying to capture). Today's VM run verified the fix: `mkinitcpio-firmware` and `tidaler` now report `error code: 1` instead of the misleading `error code: 0`.
* feat(tmux-util): add rename subcommand (fzf pick + prompt)Craig Jennings2026-05-193-9/+174
| | | | | | | | | | | | | | tmux-util rename closes out the original six-subcommand plan. The flow: 1. fzf-pick a session from the list. 2. Prompt for a new name on stdin. 3. Bail with a useful message on empty input, same-as-old, or conflict with an existing session. 4. Otherwise `tmux rename-session -t <old> <new>` and confirm. The conflict check uses `tmux has-session -t =<new>` with the same `=`-prefix exact-match guard as the go subcommand. Without it, tmux's default prefix matching would let `rename foo` succeed even when a session named `foobar` already exists, then surprise the user later. 5 new tests cover Normal cases (pick + rename happy path) and Boundary cases (no sessions, fzf cancel, empty new name, same-as-old no-op, conflict with existing session). The test harness's run_script grew an `stdin=` param so tests can feed the prompt input. fake-tmux picked up a rename-session handler that mutates the state file. Total suite: 48 tests, all green. Six subcommands shipped: go, pick, ls, find, reap, rename. The original "no args prints help" requirement still holds, and the stub-test for unimplemented subcommands got removed since everything's wired now.
* feat(tmux-util): add pick subcommand (fzf session switcher)Craig Jennings2026-05-193-4/+139
| | | | | | | | | | | | | tmux-util pick lists every session ("attached"/"detached" plus name), pipes it through fzf, and attaches or switch-clients to the chosen one (matching the go subcommand's inside-vs-outside-tmux discipline). If the user cancels fzf (Ctrl-C, Esc, empty selection), the pipeline returns empty and pick exits 0 without touching tmux state. The new fake-fzf testing fake is driven by env vars: - FAKE_FZF_CHOICE_LINE=<N>: return the Nth line of stdin as the selection - FAKE_FZF_CHOICE=<string>: return the literal string (ignores stdin) - (neither): exit 130 to simulate cancel 4 new tests cover Normal cases (pick second line, inside/outside tmux behavior) and Boundary cases (no sessions, user cancellation). Total suite: 43 tests, all green.
* feat(tmux-util): add find subcommandCraig Jennings2026-05-193-6/+130
| | | | | | | | | | | | | tmux-util find <pattern> walks every pane across every session, queries each pane's foreground command (`#{pane_current_command}`), and prints the location of any pane whose command matches the pattern. Output format: `<session>:<window>.<pane> <command>`, one per line. Pattern is a basic ERE (passed through `grep -E`), so anchors and alternation work. Substring matching is the common case. Exit code: - 0 with output: matches found - 1 with no output: no matches (lets you script around it) - 2 with usage on stderr: missing or empty pattern 7 new tests cover Normal cases (single match, multi-match across sessions, format verification) and Boundary cases (no matches, no sessions, missing pattern, empty pattern). fake-tmux now parses pid:cmd entries in the state file so panes can carry a synthetic command name. Total suite: 39 tests, all green.
* feat(tmux-util): add go subcommand (attach-or-create)Craig Jennings2026-05-193-1/+159
| | | | | | | | | | | tmux-util go <name> attaches to a session named <name> if it exists, creates it otherwise. Behavior depends on whether the caller is already inside tmux: - Outside tmux: `tmux attach-session -t <name>` (existing) or `tmux new-session -s <name> -c $PWD` (new). - Inside tmux (TMUX env set): `tmux switch-client -t <name>` (existing) or `tmux new-session -d -s <name> -c $PWD` followed by `switch-client` (new). Attaching from inside tmux would nest sessions and break the outer view, so the inside path uses switch-client instead. The existence check uses `tmux has-session -t =<name>` with the leading `=` to force exact-match. Without it, tmux does prefix matching, which would let `go foo` resolve to a session named `foobar`. I added 6 new tests covering both inside/outside-tmux paths, both create/attach paths, plus error handling for missing or empty name arguments. fake-tmux picked up handlers for new-session (mutates state), attach-session and switch-client (record-only), and the `=`-prefix form of has-session. Total suite: 32 tests, all green.
* feat(tmux-util): add ls subcommandCraig Jennings2026-05-193-21/+240
| | | | | | | | | | | | tmux-util ls is an opinionated replacement for `tmux ls` with columns for state (attached/detached), name, idle time (humanized), window count, and the current pane's cwd (tilde-fied if it sits under $HOME). The cwd query goes through `tmux display -p -t <session> '#{pane_current_path}'`, which returns the cwd of the active pane of the active window. That's close enough to "what the session is about" for a one-line summary. Idle calculation reads `date +%s` by default and accepts an override via the TMUX_UTIL_NOW env var so tests can pin "now" to a known epoch. 12 new tests cover Normal cases (attached / detached, multiple sessions) and Boundary cases (no sessions, idle exactly at minute / hour / day boundaries, $HOME tilde). One existing dispatch test got reworked because the original stub target (`ls`) is no longer unimplemented. Total suite is 26 tests, all green. The fake-tmux harness picked up two things along the way: real format-string parsing for `list-sessions -F` and a new handler for `display -p`. The state file format extended to include activity epoch, window count, and cwd, with sensible defaults for older 3-tuple test inputs so the reap tests keep passing untouched.
* feat(tmux-util): add script skeleton and reap subcommandCraig Jennings2026-05-195-0/+463
| | | | | | | | | | | | A new utility in dotfiles/common/.local/bin/ for managing tmux sessions. The eventual plan covers six subcommands (go, pick, ls, find, reap, rename). This commit ships the skeleton, the dispatch + help, and the first subcommand: reap. reap walks every unattached tmux session whose name doesn't match $TMUX_UTIL_REAP_SKIP (default `^aiv-`), sends SIGHUP to each pane's PID (the same signal that fires when you close a terminal window), waits up to three seconds for the session to wind down, and falls back to `tmux kill-session` if anything's still alive. Tests live under tests/tmux-util/ with the same fake-binary-on-PATH pattern layout-navigate uses. fake-tmux reads canned session state from a file and records every invocation. fake-kill records signal calls without sending them. fake-sleep is a no-op so tests don't actually wait. 14 tests cover Normal / Boundary cases for dispatch + reap. Run them with: cd tests && python3 -m unittest tmux-util.test_tmux_util The other five subcommands stub out for now and exit non-zero with "not implemented yet" so future TDD turns can drop them in one at a time.
* fix(archsetup): log the real exit code from failed installsCraig Jennings2026-05-191-2/+7
| | | | | | `retry_install` was logging "error code: 0" for every retry-exhausted install (6 AUR packages on the 2026-05-16 run, 2 on the 2026-05-18 run). Root cause: `last_exit_code=$?` ran AFTER an `if eval ...; then return 0; fi` block. Bash defines an if-compound's exit status as zero when no condition tested true. With no else clause and a failing eval, that's exactly what happens, so `$?` captures 0 instead of eval's actual exit code. Now eval runs unconditionally, `$?` lands in `last_exit_code` right after it, and the if just compares against the captured value. Same control flow, accurate codes in the summary. Verified with a small bash test: the old pattern reports `$?=0` after a failing if, the new pattern reports the real exit.
* chore: close LICENSE task and log emacs-fix VM verificationCraig Jennings2026-05-191-3/+8
| | | | | | Two updates in todo.org. I closed the LICENSE-file subtask as a dated event-log entry. The canonical GPL-3 text landed at `LICENSE` in 028f144 on 2026-05-11. The SPDX/NOTICE-headers follow-up splits into its own subtask for the eventual public release. I also logged the 7cd7392 emacs-stow fix verification under `[#B] Fix install errors`. The 2026-05-18 `make test` exercised the new three-branch path (git init + fetch + checkout -B main against a stow-populated `~/.emacs.d`) and the exit-128 error is gone from the Error Summary.
* docs(claude): correct cursor theme referenceCraig Jennings2026-05-191-1/+1
| | | | CLAUDE.md listed `capitaine-cursors-light`, but every other place in the repo names `Bibata-Modern-Ice`: `hyprland.conf` XCURSOR_THEME, the three Xresources files, `gtk-3.0/settings.ini`, the dconf defaults block in `archsetup`, and the `aur_install bibata-cursor-theme-bin` line. I fixed CLAUDE.md to match.
* fix(archsetup): write desktop defaults as a system dconf dbCraig Jennings2026-05-191-17/+26
| | | | | | The previous block ran two `sudo -u $user dbus-launch dconf write …` calls during install to set GTK file-chooser and GNOME interface defaults. Both exited 1 on a headless install. The user had no real session bus, so dconf couldn't create `~/.config/dconf/user`. They also showed up in the 2026-05-11 / 16 / 18 VM-run error summaries. I rewrote the block to compile a system-wide dconf db under `/etc/dconf/db/site.d/`. `dconf update` runs as root and needs no D-Bus. Settings apply to every user and can still be overridden per-user. Bonus: portal-gtk reads these on first login without the ~50s settings-proxy timeout the prior code aimed to prevent but couldn't deliver, since the per-user writes failed during install.
* chore(todo): collapse stray **NOTE:** label to italicCraig Jennings2026-05-191-1/+1
| | | | | | Lint flagged the **NOTE:** marker as a possibly-misplaced heading (double-asterisk at start of line). Switching to single-asterisk italic keeps the visual emphasis without the heading collision.
* fix(hyprland): paint scratchpad active border goldenrodCraig Jennings2026-05-191-2/+1
| | | | | | | | | | Pyprland 3.4+ applies `group deny` to scratchpads, which routes their border through col.nogroup_border* instead of col.*_border. Set col.nogroup_border_active to #daa520 (same as col.active_border) so the scratchpad and tiled active borders read identically. Also drop `pseudotile = true` from the unused dwindle{} block — this config uses layout = master, so the dwindle settings never take effect.
* fix(tmux): pipe M-w copies to the system clipboard via wl-copyCraig Jennings2026-05-181-4/+7
| | | | | | | | | `copy-selection' writes only to tmux's internal paste buffer, so a paste in any other app got stale content. Switch the M-w binding to `copy-pipe-no-clear "wl-copy"' (same engine as the existing y binding, minus the -and-cancel so M-w stays in copy-mode for repeated grabs). The selection stays visible after copy to make multi-range copying easier to follow.
* feat(tmux): mirror vterm-copy-mode key story in tmux copy-modeCraig Jennings2026-05-181-2/+10
| | | | | | | | | | | M-w copies and stays in mode so multiple selections can be grabbed in a row (was copy-selection-and-cancel). C-g exits (was clear-selection). Enter is unbound so RET has no copy-and-exit shortcut. q and Escape already cancel by default and are kept as-is. Mirrors the cj/vterm-tmux-history and vterm-copy-mode story on the Emacs side, so all three surfaces for lifting text out of a vterm share one key story.
* fix(archsetup): handle stow-created ~/.emacs.d when cloning dotemacsCraig Jennings2026-05-172-3/+21
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The 2026-05-16 VM run surfaced `setting up emacs configuration files (error code: 128)` with `fatal: not a git repository`. Root cause: `dotfiles/common/.emacs.d/calendar-sync.local.el` lives in the stow tree, so by the time the dotemacs clone step runs, `~/.emacs.d/` already exists with a stowed symlink inside. The previous two-branch check `[ -d "$emacs_dir" ]` then took the `git pull` path and failed because the dir isn't a git checkout. I replaced the check with three branches: - `.git` exists → `git pull --recurse-submodules` (the existing upgrade path). - Dir missing or empty → `git clone --recurse-submodules` (the fresh install path). - Dir exists with content but no `.git` (the stow case, today's bug) → `git init` + `remote add origin` + `fetch --recurse-submodules` + `checkout -B main origin/main` + `submodule update --init --recursive`. This pulls the tree in on top of the stowed file without losing it. The one-line `[ -d ]` → `[ -d .git ]` fix wouldn't have been enough on its own: the clone branch would still fail because `git clone` refuses a non-empty target. `bash -n archsetup` is clean. The VM test run that surfaced the bug exercised the old two-branch path. The new third branch wants a fresh `make test` to verify before this lands on a real machine. todo.org picks up two things this commit touches. The stale gitrepos.sh reference under "Remove/template personal info from scripts" comes out. A new sub-TODO under `[#B] Fix install errors` expands the existing tidaler item: the same "AUR exit 0 logged as error" pattern now hits six packages instead of one (tidaler, mkinitcpio-firmware, speedtest-go-bin, rar, masterpdfeditor, zsh-fast-syntax-highlighting-git). The recommendation points at `aur_install` and the error-summary aggregator as trace targets.
* fix(testing): cleanup traps, arg validation, and two real bugsCraig Jennings2026-05-179-48/+146
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Two real bugs and a sweep of hygiene across the harness. `make test` passed cleanly on this branch with the same 52/0/5 profile as the 2026-05-11 run, so the wiring is verified end-to-end. Real bugs: - `lib/vm-utils.sh` `snapshot_exists` was running `qemu-img snapshot -l | grep -q "$snapshot_name"`, which matches the name as a substring anywhere in the output — including inside dates or filenames in other fields. Replaced with an awk field extraction on the TAG column plus `grep -Fxq` for a whole-line literal match. - `run-test-baremetal.sh` was setting `VALIDATION_PASSED=true|false` after validation, but `validation.sh` already uses `VALIDATION_PASSED` as a pass counter. The test report then referenced `$VALIDATION_PASSED_COUNT`, which is defined nowhere. Renamed the boolean to `TEST_PASSED` (matching run-test.sh's pattern) and report the actual counter. Cleanup traps and arg validation: - `run-test.sh` now installs a top-level EXIT trap that, on abort, kills QEMU and restores the clean-install snapshot. A `CLEANUP_DONE=1` sentinel keeps the existing normal-path cleanup from double-firing. This is the recurring pain from 2026-05-11 where two failed runs left orphaned QEMU processes and dirty base disks behind. - `create-base-vm.sh` and `debug-vm.sh` got the same kind of trap, plus `debug-vm.sh` now rejects non-`.qcow2` paths up front instead of letting QEMU fail later. - `run-test.sh`, `run-test-baremetal.sh`, and `cleanup-tests.sh` now validate that options with required values actually receive one (`${var:?msg}` for `--script`/`--snapshot`/`--host`/`--password`, numeric check for `--keep`). - `run-test-baremetal.sh` traps the temp git bundle for cleanup if the script aborts before its explicit `rm`. The ZFS rollback loop now uses `while IFS= read -r ds` and quotes `$ds` inside the ssh_cmd so dataset names with whitespace wouldn't break it. Smaller hygiene: - `vm-utils.sh` `check_ovmf` also checks `OVMF_VARS_TEMPLATE`; `start_qemu` validates disk and ISO paths before building the QEMU command; numeric tests quoted. - `cleanup-tests.sh` find expression for temp disks wrapped in `\( ... -o ... \)`, all `while read` loops use `IFS= read -r`, orphaned QEMU cleanup tries SIGTERM with a 2s sleep before SIGKILL. - `create-base-vm.sh` moved the "Copy an archangel-*.iso" info line before its `fatal` instead of after (unreachable), and added the serial-log path to the final summary. - `lib/logging.sh` `stop_timer` no longer produces `$((end - ))` when the named timer was never started. - `lib/network-diagnostics.sh` `read` → `IFS= read -r`. - `setup-testing-env.sh` now installs all missing pacman packages in one transaction instead of one-at-a-time (avoids half-installed state if package N fails). KVM check also verifies the user has read+write on `/dev/kvm` and prints the `gpasswd -a $(id -un) kvm` fix if not. A few items from the review I deliberately skipped: replacing the codebase-wide unquoted `$SSH_OPTS` string with an array (cosmetic, would need to be done everywhere at once), `set -e` adds where the existing fall-through-on-failure is intentional, and a `--force` gate on `create-base-vm.sh` (would break the expected workflow).
* refactor(scripts/games): array + loop, flathub remote check, failure summaryCraig Jennings2026-05-171-29/+50
| | | | | | | | | | | | | | | | | | | | Three things were wrong with the old script: - One line had a duplicate `flathub` argument (`flatpak install ... flathub flathub org.gnome.Crosswords`). Flatpak tolerated it; still a typo. - Nothing added the flathub remote, so a fresh machine without it would fail on every line. - 24 nearly identical `flatpak install` lines with no central handling if one app failed. I moved the app IDs into a single `APPS=()` array (sorted) and looped. The script now runs `flatpak remote-add --if-not-exists --user flathub https://flathub.org/repo/flathub.flatpakrepo` up front, collects any failed apps into a `failed` array, and prints a summary plus a non-zero exit at the end if anything didn't install. I used `set -uo pipefail` rather than `-euo` on purpose so a single failed install doesn't abort the rest of the run.
* fix(cmail-setup-finish): verify both Bridge ports and dump status on failureCraig Jennings2026-05-171-2/+8
| | | | | | | | | | | | | | The "verifying Bridge is listening" check used a regex `127\.0\.0\.1:(1143|1025)` against `ss -ltn` output. That matches if *either* port is listening, but the success message claims both are. So a half-broken Bridge (IMAP up, SMTP down or vice versa) would pass the check. I split the check into two greps and report which port is missing. When the check fails, the script now also prints the last 10 lines of `systemctl --user status protonmail-bridge` to stderr so the operator sees the service state immediately instead of being told to go run the command themselves.
* refactor(scripts/post-install): consolidate gitrepos.sh and harden the scriptCraig Jennings2026-05-173-52/+73
| | | | | | | | | | | | | | | | | | | | | | | | gitrepos.sh did the same `~/.dotfiles` / `~/.emacs.d` remote swap that post-install.sh already had, plus a `git pull --set-upstream origin main` follow-on that post-install was missing. I folded the pull into the post-install remote-rewrite block and dropped gitrepos.sh. While in the file, I also: - Quoted every variable (`"$logfile"`, `"$HOME"` paths, `"$(whoami)"`). - Sent the remote-rewrite block to the log file like the other blocks do (was leaking to stdout). - Made the remote-rewrite idempotent. A re-run used to break the `cd && remote remove && remote add` chain because remove fails when origin is already the desired URL. The loop now uses `git -C "$dir" remote set-url` when origin exists and `remote add` when it does not. - Pre-created `~/sync`, `~/pictures`, `~/code`, `~/projects` so the clones don't fail on missing parent dirs. - Wrapped each `git clone` in a `clone_if_missing` helper so a re-run skips destinations that already exist instead of erroring out. README.md picks up the gitrepos.sh removal in the forking note.
* chore(scripts): drop dead and superseded scriptsCraig Jennings2026-05-165-101/+0
| | | | | | | | | | | | | | | | | Audit pass: each of these had no references anywhere in the repo (excluding self-references and review notes). - wip-bootcandy.sh — "wip" prefix, non-executable. Comments mention a boot animation but the script only installs ly and disables getty@tty2. - protonmail-bridge.sh — `pacman_install protonmail-bridge` (the package landed in extra) plus cmail-setup-finish.sh now cover this. - wireguard-proton.sh — hardcoded USGA tunnel and a relative `../assets/wireguard-config/*.conf` path that depends on the caller's pwd. - create-archiso-zfs.sh — one-off ISO build snippet, non-executable. - scripts/testing/lib/finalize-base-vm.sh — libvirt-era leftover. The test stack moved to direct QEMU and nothing sources or calls it.
* Update En Croissant AppImage pinCraig Jennings2026-05-161-2/+2
|
* Install setup-chess prerequisites automaticallyCraig Jennings2026-05-162-77/+520
|
* chore(todo): bump GitHub-prep subtask priorities to ACraig Jennings2026-05-151-14/+14
|