aboutsummaryrefslogtreecommitdiff
Commit message (Collapse)AuthorAgeFilesLines
* chore: archive completed work at wrapHEADmainCraig Jennings13 hours1-9/+9
|
* docs: close maint status-wall copy taskCraig Jennings13 hours1-2/+4
|
* docs: close GRUB_CMDLINE merge taskCraig Jennings13 hours1-1/+3
|
* fix(installer): merge GRUB cmdline instead of overwriting itCraig Jennings13 hours2-1/+181
| | | | | | configure_grub replaced the whole GRUB_CMDLINE_LINUX_DEFAULT line with a fixed string. A base install that had set cryptdevice=, resume=, zfs=, or any other boot-critical parameter lost it, and the grub-mkconfig that follows baked an unbootable config. update_grub_cmdline now reads the current value and merges: every existing token survives, archsetup's tokens are added, and where both set the same key archsetup's value wins. A safety check refuses to write when any existing token's key would vanish from the merge, and the write goes through awk + mv so paths with slashes can't break the substitution. The block also backs up /etc/default/grub before editing, which the other system-file edits already did.
* chore: overnight sentry hygiene — file 2 inbox tasks, archive completed workCraig Jennings14 hours1-86/+89
|
* docs: close audit test-quality clusterCraig Jennings20 hours1-1/+3
|
* test: fix the weak assertions from the S6/S7 auditCraig Jennings20 hours3-8/+33
| | | | | | | | The Hyprland socket check passed a shell glob to `test -S`, which breaks at both edges: zero matches leaves the literal pattern and several instances pass multiple args, failing for reasons unrelated to the socket. It now resolves one socket with find -type s. The gen_tokens degenerate-marker test asserted properties loose enough to pass on garbled output. It now pins the exact reconstruction as a characterization, with the impossible-input caveat stated, so any change to the branch surfaces. The gallery tick count leaned on split-string omit-nulls arithmetic that happened to equal the match count. It now counts occurrences directly. gallery-widget-write-svg, the last uncovered public entry point, gets a Normal-case test.
* docs: close gallery-widget elisp bugsCraig Jennings20 hours1-1/+3
|
* fix(design): clamp the gauge once so needle and readout agreeCraig Jennings20 hours2-5/+55
| | | | | | The needle angle clamped to 0-100 while the readout rendered the raw value, so a gauge fed 150 pinned the needle at +60 degrees under a "150%" label. A shared gallery-widget--clamp-value now feeds both halves. Two hygiene fixes ride along. The module requires cl-lib explicitly instead of leaning on svg.el's transitive require. The token-file load resolves its directory through gallery-widget--source-dir, which falls back to default-directory when the form is re-evaluated outside a load and outside a file buffer (both location variables are nil there and file-name-directory errored).
* docs: close VM-framework robustness clusterCraig Jennings20 hours1-1/+3
|
* fix(test): harden four VM-framework paths from the S5 auditCraig Jennings20 hours5-19/+171
| | | | | | | | | | init_vm_paths suffixed the disk and NVRAM by FS_PROFILE but left PID_FILE, MONITOR_SOCK, and SERIAL_LOG shared. A concurrent btrfs and zfs run read each other's PID file, so the second run's stop logic could kill the first run's VM. All runtime paths now carry the suffix. kill_qemu sent kill -9 and returned without waiting, so the force-kill fallback could run qemu-img snapshot against a qcow2 the dying qemu still held locked (the restore failed silently behind || true, leaving the base image dirty). It now reaps or polls the process to death before returning. debug-vm.sh hardcoded the btrfs base-disk name, so FS_PROFILE=zfs booted the wrong base or fatalled. It now takes DISK_PATH from init_vm_paths. Both runners reported a completion-marker grep as "ArchSetup Exit Code," but the installer runs detached with set -e off, so its real exit status was never captured -- a run that errored mid-way and still reached its last line reported exit 0. The flag is now ARCHSETUP_COMPLETED with the report labeled honestly; Testinfra stays the pass/fail authority.
* docs: close stale inetutils task (done in 1115543)Craig Jennings21 hours1-2/+4
|
* docs: close pacman-hook-order tautology taskCraig Jennings21 hours1-1/+3
|
* test: make the pacman hook-order test measure the sourceCraig Jennings21 hours1-10/+62
| | | | | | The ordering asserts compared two string literals to each other ("05..." < "60..."), which is constant-true and never read the file. The invariant it claims to guard is boot-critical: pacman runs hooks in filename order, and a guard hook renamed past 60-mkinitcpio-remove would let a blocked transaction remove the current initramfs with this test still green. The test now extracts the hook filenames the installer actually writes and compares those against the stock mkinitcpio hook name, so a rename flows into the assertion. Verified by mutation: renaming 05-zfs-snapshot to 70- in a source copy fails the new extraction compare where the old literal compare stayed true. Also added: every written hook must carry a two-digit ordering prefix.
* docs: close net-scenarios green-exit taskCraig Jennings21 hours1-1/+3
|
* fix(test): count a diagnose miss as a failed net scenarioCraig Jennings21 hours2-1/+109
| | | | | | | | The scenario_diagnose_expect else-branch printed its FAIL line but never forced a non-zero subshell exit. A scenario whose only failure was the diagnose check counted as passed, so the run printed "all scenarios passed" and exited 0 over a real net-doctor diagnosis regression. The miss now sets a per-scenario rc that carries to the subshell exit. The fix and assert still run first, since the repair result is worth having either way. The new harness drives the real script with stubbed ssh/rsync/jq and a controllable fake scenario, pinning all four check outcomes.
* docs: close stale cmail umask task (fixed in dffecf5)Craig Jennings21 hours1-1/+3
|
* docs: close nvme early-module taskCraig Jennings21 hours1-1/+3
|
* fix(installer): rebuild initramfs after the nvme MODULES editCraig Jennings21 hours2-11/+121
| | | | | | The nvme early-load edit landed in mkinitcpio.conf, but the only nearby mkinitcpio -P ran behind an is-not-zfs-root gate. On ZFS-root machines (all of mine) the hardening was never compiled into the initramfs, which is exactly the case the early load exists for. The extracted ensure_nvme_early_module now rebuilds whenever it changed the file, and skips the rebuild on an unchanged conf so a resume stays idempotent. The already-present check also grepped the whole file for "nvme", so a comment or nvme_tcp anywhere skipped the edit. It now checks for the nvme word on the MODULES line only.
* docs: file the Hyprland issue draft for manual postingCraig Jennings21 hours2-1/+44
|
* docs: file the Hyprland layoutmsg crash with preserved evidenceCraig Jennings27 hours4-0/+418
| | | | SIGSEGV via uncaught bad_variant_access in Layout::CAlgorithm::layoutMsg on a layoutmsg mfact dispatch. Upstream bug, seen on v0.54.1 (March) and v0.55.4 (today), both times from the layout-resize binds. Crash reports and tmpfs log excerpts land in working/hyprland-layoutmsg-crash/ so the evidence survives a reboot; graded [#B] (Critical x rare) in todo.org.
* docs: complete the code audit (S5-S7) and triage findings into tasksCraig Jennings28 hours2-3/+97
| | | | | | | | Ran read-only audits over the last three coverage slices: the VM test framework (S5), the Python (S6), and the elisp prototype (S7). That closes the sentry code-inspection sweep -- all seven slices covered. S5 turned up five bugs (one Major: run-net-scenarios prints a diagnose failure but exits green) and three refactors. S6 found one bug plus a High test-quality defect -- the pacman-hook-order test compares two string literals, so the boot-critical hook ordering it claims to guard is never actually measured. S7 found three Minor bugs in the gallery-widget prototype (the needle clamps while the readout doesn't, a missing cl-lib require, a nil-path load) plus refactor and coverage gaps. Appended every finding to the sentry findings doc, marked the coverage tracker, and filed six graded tasks: the two Major/High defects as [#C], and four [#D] clusters (VM-framework robustness, prototype elisp bugs, test-quality, and a test-framework/prototype refactor group). Detection only -- nothing fixed here.
* docs: log validate_yesno + NVIDIA constant refactors, reslice rollupCraig Jennings28 hours1-1/+3
|
* refactor(installer): extract validate_yesno and a NVIDIA driver-floor constantCraig Jennings28 hours3-21/+80
| | | | | | | | Four near-identical blocks validated that AUTOLOGIN, NO_GPU_DRIVERS, INSTALL_CLAUDE_CODE, and INSTALL_DEVICE_UDEV_RULES were empty or exactly yes/no. They collapse into a validate_yesno helper the callers guard with `|| exit 1`. The NVIDIA driver floor of 535 was a magic literal in the comparison and three prose lines. It now lives in a NVIDIA_MIN_DRIVER constant, and the nvidia-preflight test sources that constant alongside the function it extracts. This is the safe, purely-testable slice of the refactor rollup. The structural extractions that touch the installer's system-mutation paths -- the GPU modalias scan, the stow and retry loops, the display-server/window-manager dispatch, and the snapper/fsck/GRUB/waybar sed blocks -- are split into a follow-up so they get characterization coverage first rather than a blind rewrite.
* docs: close normalize-notify atomicity taskCraig Jennings28 hours1-1/+3
|
* fix(sounds): make normalize-notify-sounds write atomically and clean upCraig Jennings28 hours3-6/+162
| | | | | | The script re-encoded each notify sound with `cat "$tmp" > "$f"`, which truncates the target first -- a short or empty encode left a corrupt, repo-tracked sound file. The mktemp also had no trap, so an interrupted encode leaked a temp. Now it resolves the real target with `readlink -f` (SOUND_DIR is usually the stow-symlinked ~/.local copy), stages the temp beside it, guards on a non-empty encode, and moves it into place. The mv is atomic on one filesystem and replaces the real file, so the stow symlink still points at it. An EXIT trap removes the in-flight temp when a failed encode aborts the run.
* docs: close unguarded chmod/cp cluster taskCraig Jennings28 hours1-1/+3
|
* fix(installer): guard the cp and chmod that install helper scriptsCraig Jennings28 hours2-6/+87
| | | | | | With `set -e` off, the installer copied its zfs-replicate, zfs-pre-snapshot, and hypr-live-update-guard scripts into place with bare cp/chmod. A missing or partial source failed silently, leaving a systemd service with a dead ExecStart or a pacman hook pointing at a script that never landed. The two zfs scripts now install through install_executable, which guards the copy, warns on failure, and skips the chmod when the copy did not land. The two live-update-guard chmods (the guard binary and its hook) are guarded inline with error_warn.
* docs: close resume-idempotency cluster taskCraig Jennings28 hours1-1/+3
|
* fix(installer): make three resume-fragile steps idempotent and correctCraig Jennings28 hours2-11/+174
| | | | | | | | | | Each of these steps misbehaves on a resume re-run or under unexpected system state, so I pulled the logic into small testable helpers and fixed the behavior. The log-cleanup cron line was appended unconditionally. essential_services is one resume step, so a failure past that point plus a re-run stacked a second identical cron line every time. crontab_append_once now appends only when no existing line matches. The weekly ZFS scrub timer used `zpool list | head -1`, which picks an arbitrary pool when several exist and builds the malformed unit `zfs-scrub-weekly@.timer` when none do. zfs_scrub_timer_units emits one timer per pool and nothing for no pools, so every pool is scrubbed and an absent pool warns instead of enabling a broken unit. gamemode was enabled through `systemctl --user enable`, which the script itself documents fails at install time with no user session bus -- so gamemoded was likely left un-enabled behind a silent warning. It now wires the wants symlink by hand through enable_user_service, the same helper syncthing's block folds into.
* docs: close wireguard tunnel-ordering taskCraig Jennings28 hours1-1/+3
|
* fix(wireguard): bring the tunnel down before the rename modifyCraig Jennings28 hours3-3/+30
| | | | | | `nmcli connection import` activates a full-tunnel (0.0.0.0/0) Proton profile immediately. The import loop renamed and set autoconnect off, then brought the tunnel down. Under `set -euo pipefail`, a failed modify aborted the script before that down ran, leaving a live full-tunnel VPN routing all traffic through Proton -- the exact unasked-for VPN the script exists to prevent. The next-run guard catches the leftover profile but not the still-active tunnel. Moved the down ahead of the modify. It targets the UUID, which is stable across the rename, so downing first is safe and no failure between import and modify can leave the tunnel up.
* docs: close sudoers.pacnew validation taskCraig Jennings28 hours1-1/+3
|
* fix(installer): validate sudoers.pacnew with visudo before copyingCraig Jennings28 hours2-1/+94
| | | | | | The installer copied /etc/sudoers.pacnew over /etc/sudoers with no validation. A malformed pacnew that sudo refuses to parse would replace a working sudoers with an unparseable one, locking out privilege escalation right before the NOPASSWD rule gets appended. replace_sudoers_pacnew now runs `visudo -cf` on the pacnew first and only copies a file that validates; a failure warns and leaves the current sudoers in place. Extracted the logic into a function taking the pacnew and target as positional args defaulting to the system paths, so the guard can run against fixtures.
* docs: close run_step state + exit-code taskCraig Jennings28 hours1-1/+3
|
* fix(installer): record the state marker on non-fatal step warningsCraig Jennings28 hours2-11/+113
| | | | | | run_step marked a step complete only when its function returned 0. But error_fatal exits the script outright, so a step function that returns at all has already cleared every fatal check -- a non-zero return can only be a trailing non-fatal warning, since error_warn returns 1. The old code read that as a failure and withheld the state marker, so the step re-ran on every resume even though its body had finished. run_step now records the marker whenever the function returns and logs a warning line when the return was non-zero. Two smaller fixes alongside it. run_step's step_name/step_func and show_status's step/timestamp now use local so they stop leaking to global scope. The package-cache refresh loop captures pacman's real exit code instead of reading `$?` off the `$refresh_ok` test, which always read 1 and made error_fatal report "error code: 0" -- the same trap retry_install already documents.
* docs: close df disk-space pre-flight taskCraig Jennings28 hours1-1/+3
|
* fix(installer): harden the disk-space pre-flight gateCraig Jennings28 hours2-9/+118
| | | | | | | | `df /` wraps to two lines when the root device name is long -- a device-mapper or live-ISO root -- so `awk 'NR==2 {print $4}'` read the wrapped device line, found an empty Available field, and aborted a valid install with "Insufficient disk space." `df -P` forces POSIX single-line output. The gate now compares available KB against the minimum in KB rather than truncating to GB first, which rejected a disk sitting just under a whole-GB boundary. Non-numeric df output falls back to zero so a malformed read aborts loudly instead of crashing the arithmetic test. Extracted the logic into check_disk_space so it can be exercised directly.
* docs: close chpasswd guard taskCraig Jennings29 hours1-1/+3
|
* fix(installer): guard the user-password chpasswdCraig Jennings29 hours2-1/+65
| | | | set -e is off, so a chpasswd failure silently left the primary user with no password and no log entry, leaving an unloggable account on a fresh install. I extracted set_user_password, which aborts via error_fatal on a chpasswd failure. A fake-chpasswd test pins that the guard fires.
* chore: reorder open-work tasks in todo.orgCraig Jennings29 hours1-244/+244
|
* docs: file waybar module-separator taskCraig Jennings31 hours1-0/+4
|
* fix(security): decrypt the cmail password under a tight umaskCraig Jennings31 hours2-2/+89
| | | | gpg writes its output at the process umask (often 0644), so the plaintext mail password was world-readable between the decrypt and the chmod. I extracted decrypt_to_secure, which runs the decrypt in a 0077 umask subshell so the file is 0600 from creation. A fake-gpg test pins that the write runs under the tight umask.
* feat: add inetutils to the install baseCraig Jennings31 hours2-1/+59
| | | | TRAMP's /ftp: method shells out to a command-line ftp client, and Arch ships none by default. inetutils provides /usr/bin/ftp, so I added it to install_required_software. A static test asserts it stays in the base set.
* docs: file archsetup move off cgit to private remoteCraig Jennings36 hours1-1/+4
|
* docs: close WireGuard leak task, note cgit follow-upCraig Jennings36 hours2-17/+23
| | | | | | Record the resolution (keys expired, all 10 configs purged from history, server gc'd, verified via anonymous clone) and file the cgit-exposure audit as the systemic follow-up. Update the config README to the out-of-band workflow.
* fix(security): stop tracking plaintext WireGuard configsCraig Jennings36 hours2-0/+24
| | | | | | The repo is public via cgit, so the three plaintext configs (added 849c3fa) exposed live Proton PrivateKeys. Remove them, gitignore plaintext, and document the encrypted-only workflow. Keys rotated at Proton; history purged separately.
* docs: log overnight code audit and file triaged installer bugsCraig Jennings37 hours2-80/+286
| | | | | | Overnight sentry ran its hygiene passes plus a code-inspection pass over the installer and the scripts/ directory. It archived 6 completed tasks to Resolved and logged 19 bugs and 12 refactors in docs/design/2026-07-19-sentry-code-findings.org. I filed the bugs as tasks graded by severity times frequency. Nothing in the codebase changed, this was detection only. Two things stand out. The installer does no partitioning (it's a post-install config layer), so the real data-loss surface is boot config, and it overwrites GRUB_CMDLINE wholesale, which can drop a cryptdevice or resume param and leave a machine unbootable. Three WireGuard configs with live private keys are tracked in git, flagged for a rotate-or-keep decision.
* docs: expand desktop-settings spec and add panel prototypesCraig Jennings44 hours10-46/+1440
| | | | | | | | I folded the home-handoff control-set decisions into the desktop-settings panel spec. The controls table gains night-light, Do Not Disturb, power profile, lock/suspend actions, scenes, and a wallpaper sub-view. Volume and theme are out of scope, with reasons, and the format pickers move to their own display-format sibling spec (stubbed). The scenes and wallpaper-shape decisions close. The spec stays DRAFT pending UI prototyping. I added five direction prototypes over a shared engine (tile grid, sectioned rack, scenes-first, instrument cluster, expandable rows) so the layout gets picked against working behavior. I closed the shipped Next Session Focus work in todo.org: notification loudness, clock right-click dismiss, date-only scrolling, the wired-interface indicator, and best-saved WiFi connect, plus velox boot recovery. The three design-blocked network and audio items stay TODO with their open question recorded.
* docs: set next session focusCraig Jennings2 days1-0/+14
| | | | Prioritize autonomous Waybar, panel, and audio work.