Skip to content

fix(ansible): install btop with saved config and a launcher that fits phone panes - #31

Merged
undeemed merged 3 commits into
mainfrom
fm/cf-btop-config-mobile-b1
Oct 1, 2026
Merged

undeemed merged 3 commits into
mainfrom
fm/cf-btop-config-mobile-b1

Conversation

@undeemed

@undeemed undeemed commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Intent

save the current btop config and fix the fact that it cannot load on my mobile screen

Context: btop (snap btop 1.4.7, launched as ~/.local/bin/btop -> /snap/bin/btop) runs in a Herdr pane on the fleet host. Its "current config" is the btop.conf it wrote on a clean quit on 2026-10-01 (it had never been written to disk before). Its notable non-default values: update_ms = 100, shown_boxes = "cpu mem net proc", proc_sorting = "cpu direct", color_theme = "Default". The snap keeps config at ~/snap/btop/current/.config/btop/btop.conf. Code-Factory does not install or configure btop today.

The mobile problem: on a phone, Herdr switches at or below mobile_width_threshold (64 columns by default) to its single-column mobile layout, where the pane is the full screen width, about 50 columns in portrait over SSH or mosh (Code-Factory docs/herdr.md "On a phone"). btop refuses to draw a layout smaller than the shown boxes need and shows "Terminal size too small" instead. With all four boxes it needs about 80x24: in btop 1.4 the cpu box needs 60 columns, mem 36, net 36, proc 44, and mem/net sit beside proc. So on the phone btop never loads, while the desktop pane (177x66 today) needs the full layout.

What Changed

  • Adds btop to the tool installer (scripts/install_tools.py, factory_core_tools, --tools defaults, and container smoke commands). It installs the static musl release as ~/.local/bin/btop-bin, and tests/test_install_tools.py resolves it. Adds config/btop.conf, the saved btop config (update_ms = 100, shown_boxes = "cpu mem net proc", proc_sorting = "cpu direct", color_theme = "Default"). Its preset 4 is processes only.
  • Adds maintenance/btop.sh, installed as ~/.local/bin/btop. It starts btop-bin with preset 0 (all boxes) in a pane of at least 80x24 and with preset 4 (processes only, 44x16) in a smaller pane. A resize that crosses 80x24 restarts btop with the other preset. This stops btop showing "Terminal size too small" in Herdr's ~50-column phone pane.
  • ansible/tasks/herdr.yml now installs the launcher and the config at ~/.config/btop/btop.conf on every apply. ansible/tasks/verify.yml checks that the launcher, btop-bin and the config exist. docs/herdr.md gains a btop section. docs/dependencies.md lists btop and raises the GitHub lookup maximum from eight to nine.

Channel and mechanism

  • Channel: upstream GitHub release, not the snap. btop is installed from the latest aristocratos/btop release, using the static musl build, and is verified against the SHA-256 that GitHub publishes for the release asset. This is the same path scripts/install_tools.py uses for herdr, gh and treehouse: always latest, never pinned, checksum-verified (docs/architecture.md rule 1). The snap is not upstream. It is a third-party repack by publisher kz6fittycent, has no published checksum the installer can verify, and snapd is not available in the container image. The upstream binary reads ~/.config/btop/btop.conf, so the config is installed there instead of at the snap path. The snap stays installed on this host but is no longer used.
  • Config change against the capture (sha256 9d48d3c2…): the first commit is the capture byte-for-byte. The only line changed after it is line 26, presets, which gains proc:0:default to define preset 4 (processes only). btop 1.4.7 does not know the captured proc_tree_auto_collapse = 0 line, because the snap was built from newer upstream code. btop drops that line when it rewrites the file on exit, and every apply puts it back.
  • Mechanism: a launcher of about 20 lines of plain sh is the smallest thing that switches layouts in a live pane. btop has no command-line way to pick boxes except -p. The threshold is btop's own minimum for all four boxes, 80x24, measured on real btop, not Herdr's 64. At 50 columns only proc alone (44x16) or mem+net fits. Preset 4 keeps the saved proc_sorting = "cpu direct".
  • Restarts: when a resize crosses 80x24, the launcher stops btop with SIGINT and starts it with the other preset. btop handles SIGINT with clean_quit, so it restores the terminal. The launcher always passes -p, because btop saves the last preset's shown_boxes on exit.

Risk Assessment

⚠️ Medium: The change is well-bounded and the mobile-fit mechanism matches btop's real size checks and preset indexing. However the launcher's restart path kills btop with an unhandled SIGTERM and can leave the tty in a raw state, and fleet-wide installation of a second btop binary goes beyond the stated intent.

Testing

The config-fidelity scenario passed live: the repo config matches the operator's saved btop config except for the added 4th preset, and btop accepts it without errors. The installer tests passed through uv (54 passed) but are unit tests, so that scenario is untested live. All earlier live launcher scenarios pass: phone, desktop, resize, boundary, tiny pane and tty restore after quit.

  • Live validation: ✅ go - 7 of 8 scenarios driven live against the product
Scenario Result Live Evidence
Saved config equals the operator's current btop config (update_ms, shown_boxes, proc_sorting, color_theme) plus the 4th custom preset ✅ pass live Read-only diff of ~/snap/btop/current/.config/btop/btop.conf vs config/btop.conf; the only difference is the added proc:0:default preset
Real btop accepts config/btop.conf with the 4th preset: -p 4 --debug runs and exits 0 with no warn or error in the btop log ✅ pass live Isolated tmux run with its own HOME and XDG_CONFIG_HOME; EXIT=0, no_warn_error
Installer entry for btop (btop-bin link, default --tools list) behaves per the unit tests ⏸️ untested no The prior payload established this only through unit tests (uv run --with pytest python -m pytest tests/test_install_tools.py -q: 54 passed). It did not run the installer against the live product, s…
Open btop in a 50x40 phone-width pane: the processes box renders instead of 'Terminal size too small' ✅ pass live s1-phone-50x40.txt
Open btop in a 177x66 desktop pane: all boxes render ✅ pass live s2-desktop.txt
Resize a running pane across 80x24 in either direction: btop restarts with the matching preset ✅ pass live s3-wide-to-phone.txt, s4-phone-to-wide.txt
Quit after resize restarts: stty -g is identical to before launch (SIGINT restart fix) ✅ pass live tmux transcript: TTY_RESTORED
Boundary 80x24 shows all boxes, 80x23 falls back to processes-only; 30x10 shows btop's own too-small message ✅ pass live s7-80x24.txt, s7-80x23.txt, s6-tiny.txt
Evidence: Config comparison: saved snap config vs config/btop.conf
update_ms = 100 / shown_boxes = "cpu mem net proc" / proc_sorting = "cpu direct" / color_theme = "Default" identical in both files. Only diff: line 26 presets gains ` proc:0:default` (4th custom preset, index 4).
Evidence: Installer tests
uv run --with pytest python -m pytest tests/test_install_tools.py -q -> 54 passed
Evidence: Phone 50x40 pane
╭─┐⁴proc┌┐filter┌─────────┐tree┌┐← cpu direct →┌─╮
│    Pid: Program:       User:  MemB       Cpu% ↑│
│ 2737165 btop-bin      admi+  6.6M ⣀⣀⣀⣀⣀  0.6  █│
│ 2903364 btop-bin      admi+  3.2M ⣀⣀⣀⣀⣀  0.3   │
│ 2891265 omp           admi+  462M ⣀⣀⣀⢀⢀  0.3   │
│  294528 herdr         admi+   63M ⣀⣀⣀⣀⣀  0.2   │
│ 2538310 omp           admi+  554M ⡀⣀⡀⢀⢀  0.2   │
│ 2810573 bash          admi+   11M ⣀⣀⣀⡀⣀  0.1   │
│ 2606435 omp daemon br admi+  180M ⣀⣀⣀⣀⢀  0.1   │
│ 2564283 omp           admi+  727M ⣀⢀⣀⡀⡀  0.0   │
│      25 ksoftirqd/1   root     0B ⣀⣀⣀⣀⡀  0.0   │
│   18787 tailscaled    root    50M ⣀⣀⣀⢀⣀  0.0   │
│ 2266327 herdr         admi+   12M ⣀⣀⣀⢀⣀  0.0   │
│ 1572493 omp           admi+  474M ⣀⣀⣀⢀⣀  0.0   │
│ 2163700 omp daemon br admi+  165M ⣀⣀⣀⢀⣀  0.0   │
│ 2903355 tmux: server  admi+  3.9M ⣀⡀⣀⡀⣀  0.0   │
│ 1516174 herdr         admi+   12M ⡀⣀⢀⣀⣀  0.0   │
│      15 rcu_preempt   root     0B ⣀⣀⡀⣀⣀  0.0   │
│ 2649099 omp daemon br admi+  165M ⣀⢀⣀⣀⣀  0.0   │
│ 1525387 omp           admi+  512M ⢀⣀⣀⣀⣀  0.0   │
│ 1741902 herdr         admi+   11M ⡀⣀⣀⣀⣀  0.0   │
│ 1516173 mosh-server   admi+   12M ⡀⣀⣀⣀⣀  0.0   │
│ 1567621 omp           admi+  454M ⡀⣀⣀⣀⣀  0.0   │
│ 2510715 kworker/u386: root     0B ⡀⣀⣀⣀⣀  0.0   │
│ 1552561 omp           admi+  465M ⣀⣀⣀⣀⣀  0.0   │
│ 2513751 kworker/10:1- root     0B ⣀⣀⣀⣀⣀  0.0   │
│   18371 systemd       admi+   10M ⣀⣀⣀⣀⣀  0.0   │
│ 2636322 omp           admi+  180M ⣀⣀⣀⣀⣀  0.0   │
│ 1542720 omp           admi+  558M ⣀⣀⣀⣀⣀  0.0   │
│   19505 kworker/82:2- root     0B ⣀⣀⣀⣀⣀  0.0   │
│ 2617875 kworker/u386: root     0B ⣀⣀⣀⣀⣀  0.0   │
│ 2473163 herdr         admi+  4.6M ⣀⣀⣀⣀⣀  0.0   │
│ 2729103 chrome-headle admi+   96M ⣀⣀⣀⣀⣀  0.0   │
│       1 systemd       root    15M ⣀⣀⣀⣀⣀  0.0   │
│       2 kthreadd      root     0B ⣀⣀⣀⣀⣀  0.0   │
│       3 pool_workqueu root     0B ⣀⣀⣀⣀⣀  0.0   │
│       4 kworker/R-rcu root     0B ⣀⣀⣀⣀⣀  0.0   │
│       5 kworker/R-syn root     0B ⣀⣀⣀⣀⣀  0.0   │
│       6 kworker/R-kvf root     0B ⣀⣀⣀⣀⣀  0.0  ↓│
╰┘↑ select ↓└┘info ↵└┘signals└┘Nice└─────┘0/1004└╯
Evidence: Desktop 177x66 pane
╭─┐¹cpu┌──┐menu┌┐preset 0┌──────────────────────────────────────────────────────────┐18:23:22┌──────────────────────────────────────────────────────────────────────┐- 100ms +┌─╮
│                                                                                          ╭─┐@┌──────────────────────────────────────────────────────────────────────┐1.0 GHz┌╮│
│                                                                                          │CPU ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■   3% ⣀⣀⣀⣀⣀  34°C││
│                                                                                          │C0   0%  26°C│C16  0%  26°C│C32  0%  26°C│C48  0%  26°C│C64  0%  26°C│C80  0%  26°C││
│                                                                                          │C1   0%  26°C│C17 10%  26°C│C33  0%  26°C│C49  0%  26°C│C65  0%  26°C│C81  0%  26°C││
│                                                                                          │C2   0%  27°C│C18  0%  27°C│C34  9%  25°C│C50  0%  27°C│C66  0%  27°C│C82  9%  25°C││
│                                                                                          │C3   0%  26°C│C19  0%  25°C│C35 10%  25°C│C51  0%  26°C│C67  0%  25°C│C83  0%  25°C││
│                                                                                          │C4   0%  26°C│C20 64%  25°C│C36  0%  28°C│C52  9%  26°C│C68  0%  25°C│C84  0%  28°C││
│                                                                                          │C5   0%  26°C│C21  0%  25°C│C37  0%  26°C│C53  0%  26°C│C69  0%  25°C│C85  0%  26°C││
│                                                                                          │C6   0%  25°C│C22  0%  25°C│C38  0%  25°C│C54 10%  25°C│C70  0%  25°C│C86  0%  25°C││
│                                                                           ⣀⣀⣀⣠⣀⣀⣀⣀⣀⣠⣤⣀⣀⣀⣄│C7   0%  26°C│C23  0%  25°C│C39  0%  26°C│C55  0%  26°C│C71  0%  25°C│C87  0%  26°C││
│                                                                           ⠉⠉⠉⠙⠉⠉⠉⠉⠉⠙⠛⠉⠉⠉⠋│C8   0%  26°C│C24  0%  26°C│C40  0%  26°C│C56 80%  26°C│C72  0%  26°C│C88  0%  26°C││
│                                                                                          │C9   0%  26°C│C25  0%  26°C│C41  0%  26°C│C57  0%  26°C│C73  0%  26°C│C89  0%  26°C││
│                                                                                          │C10  0%  25°C│C26  0%  27°C│C42  0%  27°C│C58  0%  25°C│C74  8%  27°C│C90  0%  27°C││
│                                                                                          │C11  0%  25°C│C27  0%  26°C│C43  0%  25°C│C59  0%  25°C│C75  0%  26°C│C91  8%  25°C││
│                                                                                          │C12  0%  28°C│C28 10%  26°C│C44  0%  25°C│C60  0%  28°C│C76  0%  26°C│C92  0%  25°C││
│                                                                                          │C13 60%  26°C│C29  0%  26°C│C45  0%  25°C│C61  0%  26°C│C77  0%  26°C│C93  0%  25°C││
│                                                                                          │C14 10%  25°C│C30  0%  25°C│C46  0%  25°C│C62  0%  25°C│C78  0%  25°C│C94  9%  25°C││
│                                                                                          │C15  0%  26°C│C31  0%  26°C│C47  0%  25°C│C63  0%  26°C│C79  0%  26°C│C95  0%  25°C││
│                                                                                          │                                                           Load avg: 3.45 4.28 3.92││
│ up 1d 13:22                                                                              ╰───────────────────────────────────────────────────────────────────────────────────╯│
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─┐²mem┌────────────────────────────────┬─┐disks┌─────────────────────────┐io┌─╮╭─┐⁴proc┌┐filter┌──────────────────────────────┐pause┌┐per-core┌┐reverse┌┐tree┌┐← cpu direct →┌─╮
│ Total:                        247 GiB ├─root─────────────────────────234─GiB─┤│    Pid: Program:         Command:                       Threads: User:       MemB       Cpu% ↑│
├─Used:────────────────────────14.5─GiB─┤ IO% ⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⡀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ ││  294528 herdr            ~/.local/share/c  132 administr+   63M ⣀⣀⣀⣀⣀  0.9 █│
│   6%                                  │ Used: 12% ■■■■■■■■■■■■■■■■■ 27.2 GiB ││ 2737165 btop-bin         ~/.local/bin/bto    2 administr+  6.6M ⣀⣀⣀⣀⣀  0.6  │
│                                       │ Free: 88% ■■■■■■■■■■■■■■■■■  207 GiB ││ 2904013 btop-bin         /tmp/tmp.gULdTKfH7Y/bin/btop-bin -    2 administr+  3.2M ⣀⣀⣀⣀⣀  0.6  │
│                                       │                                      ││ 2904004 tmux: server     tmux -L t2891265 new-session -d -s    1 administr+  3.9M ⣀⣀⣀⡀⣀  0.1  │
│                        ⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀├─sdb-disk────────────────────1.83─TiB─┤│ 2538310 omp              bun ~/.local/bin  316 administr+  554M ⣀⣀⢀⣀⣀  0.1  │
├─Available:────────────────────232─GiB─┤ IO% ⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ ││ 1741902 herdr            herdr --session default               7 administr+   11M ⣀⣀⣀⣀⢀  0.1  │
│  94%                   ⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶│ Used:  5% ■■■■■■■■■■■■■■■■■ 95.4 GiB ││ 1113371 cloudflared      ~/.local/bin/clo   33 administr+   37M ⣀⣀⣀⣀⢀  0.1  │
│                        ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿│ Free: 95% ■■■■■■■■■■■■■■■■■ 1.73 TiB ││ 2904456 python3          /usr/bin/python3 ~    1 administr+   14M ⣀⣀⣀⢀⡀  0.0  │
│                        ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿│                                      ││ 2564283 omp              bun ~/.local/bin  315 administr+  730M ⡀⡀⣀⢀⡀  0.0  │
│                        ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿├─sdc-disk────────────────────1.78─TiB─┤│   18787 tailscaled       /usr/sbin/tailscaled --state=/var/   39 root         50M ⢀⣀⡀⡀⡀  0.0  │
├─Cached:──────────────────────16.7─GiB─┤ IO% ⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ ││ 2266327 herdr            herdr --session default               7 administr+   12M ⣀⢀⣀⣀⡀  0.0  │
│   7%                                  │ Used:  5% ■■■■■■■■■■■■■■■■■ 93.1 GiB ││ 1516174 herdr            herdr --session default               7 administr+   12M ⣀⣀⣀⣀⡀  0.0  │
│                                       │ Free: 95% ■■■■■■■■■■■■■■■■■ 1.69 TiB ││ 1572493 omp              bun ~/.local/bin  305 administr+  473M ⣀⣀⣀⣀⡀  0.0  │
│                                       │                                      ││ 2891265 omp              bun ~/.local/bin  316 administr+  463M ⢀⣀⣀⢀⣀  0.0  │
│                        ⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀├──────────────────────────────────────┤│ 2606435 omp daemon brok  ~/.local/share/c  108 administr+  180M ⣀⢀⡀⣀⣀  0.0  │
├─Free:─────────────────────────216─GiB─┤                                      ││ 1552561 omp              bun ~/.local/bin  306 administr+  465M ⣀⣀⡀⣀⣀  0.0  │
│  88%                   ⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤│                                      ││ 1525387 omp              bun ~/.local/bin  316 administr+  512M ⢀⣀⣀⣀⣀  0.0  │
│                        ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿│                                      ││ 2729105 bun              bun run /tmp/fm-swarms-carddate-16    9 administr+   33M ⢀⣀⣀⣀⣀  0.0  │
│                        ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿│                                      ││ 1567621 omp              bun ~/.local/bin  311 administr+  454M ⡀⣀⣀⣀⣀  0.0  │
│                        ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿│                                      ││ 2617875 kworker/u386:0-e                                       1 root          0B ⡀⣀⣀⣀⣀  0.0  │
├───────────────────────────────────────┤                                      ││ 2729216 chrome-headless  /tmp/carddate-shots/browsers/chrom   44 administr+  1.1G ⣀⣀⣀⣀⣀  0.0  │
│                                       │                                      ││ 2729209 chrome-headless  /tmp/carddate-shots/browsers/chrom   26 administr+   73M ⣀⣀⣀⣀⣀  0.0  │
│                                       │                                      ││ 2729103 chrome-headless  /tmp/carddate-shots/browsers/chrom   23 administr+   96M ⣀⣀⣀⣀⣀  0.0  │
╰───────────────────────────────────────┴──────────────────────────────────────╯│ 2328440 kworker/60:1-mem                                       1 root          0B ⣀⣀⣀⣀⣀  0.0  │
╭─┐³net┌┐127.0.0.1┌────────────────────────────────┐sync┌┐auto┌┐zero┌┐←b lo n→┌╮│ 2864787 kworker/0:1-rcu_                                       1 root          0B ⣀⣀⣀⣀⣀  0.0  │
│10K                                   ⡇      ⢸  ⡇                             ││      15 rcu_preempt                                            1 root          0B ⣀⣀⣀⣀⣀  0.0  │
│                                      ⡇      ⢸  ⡇                             ││      31 ksoftirqd/2                                            1 root          0B ⣀⣀⣀⣀⣀  0.0  │
│                                      ⡇      ⢸  ⡇                             ││ 2163700 omp daemon brok  ~/.local/share/c  109 administr+  165M ⣀⣀⣀⣀⣀  0.0  │
│                                      ⡇      ⢸  ⡇                             ││ 1948879 kworker/u385:15-                                       1 root          0B ⣀⣀⣀⣀⣀  0.0  │
│                                      ⡇      ⢸  ⡇  ╭─┐download┌──────────────╮││ 2266325 mosh-server      /usr/bin/mosh-server new -s -c 256    1 administr+   10M ⣀⣀⣀⣀⣀  0.0  │
│                                      ⡇      ⢸  ⡇  │▼ 0 Byte/s      (0 bitps)│││ 1542720 omp              bun ~/.local/bin  305 administr+  558M ⣀⣀⣀⣀⣀  0.0  │
│                                      ⡇      ⢸  ⡇  │▼ Top:        (569 Kibps)│││   18827 kworker/36:2-mm_                                       1 root          0B ⣀⣀⣀⣀⣀  0.0  │
│                                    ⣀⣀⣧⣀⣀⣀⣀⣀⣀⣸⣄⣀⣇⣀⣀│▼ Total:         4.24 GiB│││ 2649099 omp daemon brok  ~/.local/share/c  109 administr+  165M ⣀⣀⣀⣀⣀  0.0  │
│                                    ⠉⠉⡟⠉⠉⠉⠉⠉⠉⢹⠋⠉⡏⠉⠉│                         │││   18371 systemd          /usr/lib/systemd/systemd --user --    1 administr+   10M ⣀⣀⣀⣀⣀  0.0  │
│                                      ⡇      ⢸  ⡇  │▲ 0 Byte/s      (0 bitps)│││ 1572008 bash             bash ~/.treehous    1 administr+   10M ⣀⣀⣀⣀⣀  0.0  │
│                                      ⡇      ⢸  ⡇  │▲ Top:        (569 Kibps)│││  294512 kworker/86:2-mem                                       1 root          0B ⣀⣀⣀⣀⣀  0.0  │
│                                      ⡇      ⢸  ⡇  │▲ Total:         4.24 GiB│││   22931 systemd-journal  /usr/lib/systemd/systemd-journald     1 root         80M ⣀⣀⣀⣀⣀  0.0  │
│                                      ⡇      ⢸  ⡇  ╰─┘upload└────────────────╯││       1 systemd          /usr/lib/systemd/systemd --system     1 root         15M ⣀⣀⣀⣀⣀  0.0  │
│                                      ⡇      ⢸  ⡇                             ││       2 kthreadd                                               1 root          0B ⣀⣀⣀⣀⣀  0.0  │
│                                      ⡇      ⢸  ⡇                             ││       3 pool_workqueue_r                                       1 root          0B ⣀⣀⣀⣀⣀  0.0  │
│10K                                   ⡇      ⢸  ⡇                             ││       4 kworker/R-rcu_gp                                       1 root          0B ⣀⣀⣀⣀⣀  0.0 ↓│
╰──────────────────────────────────────────────────────────────────────────────╯╰┘↑ select ↓└┘info ↵└┘terminate└┘kill└┘signals└┘Nice└┘Follow└───────────────────────────┘0/1001└╯
Evidence: Live resize wide to phone
╭─┐⁴proc┌┐filter┌─────────┐tree┌┐← cpu direct →┌─╮
│    Pid: Program:       User:  MemB       Cpu% ↑│
│  294528 herdr         admi+   62M ⣀⣀⣀⣀⣀  1.1  █│
│ 2737165 btop-bin      admi+  5.8M ⣀⣀⣀⣀⣀  0.7   │
│ 2904559 btop-bin      admi+  3.2M ⣀⣀⣀⣀⣀  0.4   │
│ 2564283 omp           admi+  734M ⣀⣀⢀⡀⣀  0.2   │
│ 1741902 herdr         admi+   11M ⣀⣀⣀⢀⢀  0.1   │
│ 1572493 omp           admi+  473M ⣀⣀⣀⣀⢀  0.1   │
│ 2636322 omp           admi+  180M ⣀⣀⣀⣀⢀  0.1   │
│      14 ksoftirqd/0   root     0B ⣀⣀⣀⣀⢀  0.1   │
│ 2904890 python3       admi+   14M ⣀⣀⣀⢀⡀  0.0   │
│ 2538310 omp           admi+  556M ⣀⢀⣀⡀⡀  0.0   │
│ 2891265 omp           admi+  462M ⣀⣀⡀⣀⡀  0.0   │
│ 2266327 herdr         admi+   12M ⣀⣀⣀⣀⡀  0.0   │
│ 2904004 tmux: server  admi+  3.9M ⣀⡀⡀⢀⣀  0.0   │
│       1 systemd       root    15M ⣀⣀⣀⢀⣀  0.0   │
│   18787 tailscaled    root    50M ⢀⢀⣀⣀⣀  0.0   │
│ 2473163 herdr         admi+  4.6M ⣀⢀⣀⣀⣀  0.0   │
│ 1525387 omp           admi+  512M ⣀⡀⣀⣀⣀  0.0   │
│ 2606435 omp daemon br admi+  180M ⣀⡀⣀⣀⣀  0.0   │
│ 1516174 herdr         admi+   12M ⣀⡀⣀⣀⣀  0.0   │
│      15 rcu_preempt   root     0B ⢀⣀⣀⣀⣀  0.0   │
│ 2729216 chrome-headle admi+  1.1G ⣀⣀⣀⣀⣀  0.0   │
│ 2729209 chrome-headle admi+   78M ⣀⣀⣀⣀⣀  0.0   │
│   18371 systemd       admi+   10M ⣀⣀⣀⣀⣀  0.0   │
│ 1567621 omp           admi+  455M ⣀⣀⣀⣀⣀  0.0   │
│ 2729211 chrome-headle admi+   64M ⣀⣀⣀⣀⣀  0.0   │
│ 2649099 omp daemon br admi+  165M ⣀⣀⣀⣀⣀  0.0   │
│ 1552561 omp           admi+  465M ⣀⣀⣀⣀⣀  0.0   │
│ 1601035 bash          admi+  9.8M ⣀⣀⣀⣀⣀  0.0   │
│ 1542720 omp           admi+  558M ⣀⣀⣀⣀⣀  0.0   │
│ 2659643 kworker/u385: root     0B ⣀⣀⣀⣀⣀  0.0   │
│       2 kthreadd      root     0B ⣀⣀⣀⣀⣀  0.0   │
│       3 pool_workqueu root     0B ⣀⣀⣀⣀⣀  0.0   │
│       4 kworker/R-rcu root     0B ⣀⣀⣀⣀⣀  0.0   │
│       5 kworker/R-syn root     0B ⣀⣀⣀⣀⣀  0.0   │
│       6 kworker/R-kvf root     0B ⣀⣀⣀⣀⣀  0.0   │
│       7 kworker/R-slu root     0B ⣀⣀⣀⣀⣀  0.0   │
│       8 kworker/R-net root     0B ⣀⣀⣀⣀⣀  0.0  ↓│
╰┘↑ select ↓└┘info ↵└┘signals└┘Nice└─────┘0/1000└╯
Evidence: Live resize phone to wide
╭─┐¹cpu┌──┐menu┌┐preset 0┌──────────────────────────────┐18:23:28┌─────────────────────────────────────────┐- 100ms +┌─╮
│                                               ╭─┐@┌────────────────────────────────────────────────────────┐1.0 GHz┌╮│
│                                               │CPU ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■   3% ⣀⣀⣀⣀⣀  33°C││
│                                               │C0   0%  26°C│C7   0%  24°C│C14  0%  25°C│C21  0%  24°C│C28  0%  26°C││
│                                               │C1   0%  26°C│C8   0%  25°C│C15  0%  25°C│C22  0%  25°C│C29  0%  26°C││
│                                               │C2   0%  26°C│C9   0%  25°C│C16  0%  25°C│C23  0%  25°C│C30  0%  24°C││
│                                ⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀│C3  45%  25°C│C10  0%  26°C│C17  0%  25°C│C24  0%  26°C│C31  0%  24°C││
│                                ⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉│C4   0%  26°C│C11  0%  25°C│C18  0%  26°C│C25  0%  26°C│C32  0%  25°C││
│                                               │C5   0%  26°C│C12 10%  25°C│C19  0%  25°C│C26  0%  26°C│C33  0%  25°C││
│                                               │C6   0%  24°C│C13  0%  25°C│C20  0%  25°C│C27  0%  25°C│C34  0%  26°C││
│                                               │                                             Load avg: 3.33 4.25 3.91││
│ up 1d 13:22                                   ╰─────────────────────────────────────────────────────────────────────╯│
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─┐²mem┌──────────────────┬─┐disks┌─────────────┐io┌─╮╭─┐⁴proc┌┐filter┌──────┐per-core┌┐reverse┌┐tree┌┐← cpu direct →┌─╮
│ Total:          247 GiB ├─root─────────────234─GiB─┤│    Pid: Program: Command:              User:  MemB       Cpu% ↑│
├─Used:──────────14.5─GiB─┤ IO% ⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⢀⣀⣀⣀⣀ ││  294528 herdr    ~/. admi+   62M ⣀⣀⣀⣀⣀  0.9 █│
│   6%                    │ Used: 12% ■■■■■ 27.2 GiB ││ 2737165 btop-bin ~/. admi+  5.8M ⣀⣀⣀⣀⣀  0.6  │
│          ⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀│ Free: 88% ■■■■■  207 GiB ││ 2904962 btop-bin /tmp/tmp.gULdTKfH7Y/b admi+  3.2M ⣀⣀⣀⣀⣀  0.6  │
├─Available:──────232─GiB─┤                          ││ 2564283 omp      bun ~ admi+  734M ⢀⢀⢀⢀⣀  0.1  │
│  94%     ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿├─sdb-disk────────1.83─TiB─┤│ 2904004 tmux: se tmux -L t2891265 new- admi+  3.9M ⡀⡀⡀⢀⢀  0.1  │
│          ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿│ IO% ⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ ││ 2891265 omp      bun ~ admi+  462M ⣀⢀⣀⣀⢀  0.1  │
├─Cached:────────16.7─GiB─┤ Used:  5% ■■■■■ 95.4 GiB ││ 2905940 python3  /usr/bin/python3 /hom admi+   14M ⣀⣀⣀⣀⢀  0.1  │
│   7%                    │ Free: 95% ■■■■■ 1.73 TiB ││ 2538310 omp      bun ~ admi+  557M ⢀⣀⣀⢀⣀  0.0  │
│          ⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀│                          ││ 1525387 omp      bun ~ admi+  512M ⣀⣀⣀⢀⣀  0.0  │
├─Free:───────────216─GiB─├─sdc-disk────────1.78─TiB─┤│      15 rcu_pree                       root     0B ⣀⣀⣀⢀⣀  0.0  │
│  88%     ⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶│ IO% ⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀ ││ 1572493 omp      bun ~ admi+  473M ⣀⣀⣀⡀⣀  0.0  │
│          ⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿│ Used:  5% ■■■■■ 93.1 GiB ││ 1741902 herdr    herdr --session defau admi+   11M ⣀⣀⣀⡀⣀  0.0  │
├─────────────────────────┤ Free: 95% ■■■■■ 1.69 TiB ││   18787 tailscal /usr/sbin/tailscaled  root    50M ⡀⡀⢀⣀⣀  0.0  │
╰─────────────────────────┴──────────────────────────╯│ 2636322 omp      ~/. admi+  180M ⣀⣀⡀⣀⣀  0.0  │
╭─┐³net┌┐127.0.0.1┌──────┐sync┌┐auto┌┐zero┌┐←b lo n→┌╮│     677 kworker/                       root     0B ⣀⢀⣀⣀⣀  0.0  │
│10K        ⡇   ⡇  ⢸      ╭─┐download┌──────────────╮││ 2864787 kworker/                       root     0B ⣀⢀⣀⣀⣀  0.0  │
│           ⡇   ⡇  ⢸      │▼ 0 Byte/s      (0 bitps)│││ 2606435 omp daem ~/. admi+  180M ⣀⡀⣀⣀⣀  0.0  │
│           ⡇   ⡇  ⢸      │▼ Top:        (402 Kibps)│││ 1542720 omp      bun ~ admi+  558M ⣀⡀⣀⣀⣀  0.0  │
│          ⣀⣇⣀⣀⣀⣇⣀⣀⣸⣀⣀⣀⣀⣀⣀│▼ Total:         4.24 GiB│││ 2266327 herdr    herdr --session defau admi+   12M ⢀⣀⣀⣀⣀  0.0  │
│          ⠉⡏⠉⠉⠉⡏⠉⠉⢹⠉⠉⠉⠉⠉⠉│                         │││ 2827750 bash     bash ~ admi+  9.8M ⣀⣀⣀⣀⣀  0.0  │
│           ⡇   ⡇  ⢸      │▲ 0 Byte/s      (0 bitps)│││   18371 systemd  /usr/lib/systemd/syst admi+   10M ⣀⣀⣀⣀⣀  0.0  │
│           ⡇   ⡇  ⢸      │▲ Top:        (402 Kibps)│││   27171 containe /usr/bin/containerd   root    45M ⣀⣀⣀⣀⣀  0.0  │
│           ⡇   ⡇  ⢸      │▲ Total:         4.24 GiB│││ 1552561 omp      bun ~ admi+  465M ⣀⣀⣀⣀⣀  0.0  │
│10K        ⡇   ⡇  ⢸      ╰─┘upload└────────────────╯││ 2473163 herdr    /usr/local/bin/herdr  admi+  4.6M ⣀⣀⣀⣀⣀  0.0 ↓│
╰────────────────────────────────────────────────────╯╰┘↑ select ↓└┘info ↵└┘terminate└┘kill└┘signals└┘Nice└────┘0/1006└╯
- Outcome: ⚠️ 1 info across 1 run (3m38s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 warning
  • ⚠️ maintenance/btop.sh:19 - The resize-triggered restart stops btop with kill "$pid", which sends SIGTERM. btop 1.4.7 has no SIGTERM handler. Its src/btop.cpp _signal_handler handles only SIGINT, TSTP, CONT, WINCH, USR1 and USR2. The default action kills it without clean_quit or Term::restore. So the first instance never restores the tty (echo off, non-canonical mode) and never leaves the alternate screen. The relaunched btop then runs tcgetattr in Term::init, which stores that already-raw state as its initial_settings. When the user later quits, Term::restore writes the raw state back instead of the real original. Any phone/desktop resize that crosses 80x24 therefore leaves the pane's tty with echo off and line buffering off after the final quit. The first instance also skips its Config::write. Smallest fix: kill -INT "$pid". btop installs a SIGINT handler that does should_quit then clean_quit, which restores the terminal and saves the config. This is the only restart path in the changed code.
  • ⚠️ ansible/group_vars/all.yml:180 - The intent asks to save the current btop config and make btop load on the phone. Its btop is the snap, launched via ~/.local/bin/btop -> /snap/bin/btop, with config under ~/snap/btop/current/.config/btop. The change goes further. It adds btop to factory_core_tools, so every host gets the upstream musl release downloaded and linked as btop-bin. It also overwrites ~/.local/bin/btop with a launcher and rewrites ~/.config/btop/btop.conf on every apply (herdr.yml, installer entry at scripts/install_tools.py:68, plus docs and verify checks). No intent line requires fleet-wide installation of a second btop binary that replaces the snap one. A narrower form would ship only config/btop.conf and maintenance/btop.sh, with the launcher running the btop already on the host. Confirm that the fleet-wide release install is wanted; removing it is the remedy, not hardening it. Verified in btop 1.4.7 source: the min sizes (cpu 60, mem/net 36, proc 44x16, all boxes 80x24) and the preset indexing (the added 4th custom preset is index 4) are correct.

🔧 Fix applied.
1 warning still open:

  • ⚠️ ansible/group_vars/all.yml:180 - The intent asks to save the current btop config and make btop load on the phone. Its btop is the snap, launched via ~/.local/bin/btop -> /snap/bin/btop, with config under ~/snap/btop/current/.config/btop. The change goes further. It adds btop to factory_core_tools, so every host gets the upstream musl release downloaded and linked as btop-bin. It also overwrites ~/.local/bin/btop with a launcher and rewrites ~/.config/btop/btop.conf on every apply (herdr.yml, installer entry at scripts/install_tools.py:68, plus docs and verify checks). No intent line requires fleet-wide installation of a second btop binary that replaces the snap one. A narrower form would ship only config/btop.conf and maintenance/btop.sh, with the launcher running the btop already on the host. Confirm that the fleet-wide release install is wanted; removing it is the remedy, not hardening it. Verified in btop 1.4.7 source: the min sizes (cpu 60, mem/net 36, proc 44x16, all boxes 80x24) and the preset indexing (the added 4th custom preset is index 4) are correct.
⚠️ **Test** - 1 info
  • ℹ️ Running btop via the launcher at phone width (-p 4) and quitting rewrites the live btop.conf with shown_boxes = "proc", because save_config_on_exit = true. The launcher passes -p on every start, so layout selection is unaffected. The saved all-boxes config only comes back when the Ansible herdr.yml task re-copies config/btop.conf on the next apply, which is the documented behavior.
  • Live validation: ✅ go - 7 of 8 scenarios driven live against the product
Scenario Result Live Evidence
Saved config equals the operator's current btop config (update_ms, shown_boxes, proc_sorting, color_theme) plus the 4th custom preset ✅ pass live Read-only diff of ~/snap/btop/current/.config/btop/btop.conf vs config/btop.conf; the only difference is the added proc:0:default preset
Real btop accepts config/btop.conf with the 4th preset: -p 4 --debug runs and exits 0 with no warn or error in the btop log ✅ pass live Isolated tmux run with its own HOME and XDG_CONFIG_HOME; EXIT=0, no_warn_error
Installer entry for btop (btop-bin link, default --tools list) behaves per the unit tests ⏸️ untested no The prior payload established this only through unit tests (uv run --with pytest python -m pytest tests/test_install_tools.py -q: 54 passed). It did not run the installer against the live product, s…
Open btop in a 50x40 phone-width pane: the processes box renders instead of 'Terminal size too small' ✅ pass live s1-phone-50x40.txt
Open btop in a 177x66 desktop pane: all boxes render ✅ pass live s2-desktop.txt
Resize a running pane across 80x24 in either direction: btop restarts with the matching preset ✅ pass live s3-wide-to-phone.txt, s4-phone-to-wide.txt
Quit after resize restarts: stty -g is identical to before launch (SIGINT restart fix) ✅ pass live tmux transcript: TTY_RESTORED
Boundary 80x24 shows all boxes, 80x23 falls back to processes-only; 30x10 shows btop's own too-small message ✅ pass live s7-80x24.txt, s7-80x23.txt, s6-tiny.txt
  • Read-only compare of ~/snap/btop/current/.config/btop/btop.conf against config/btop.conf: update_ms, shown_boxes, proc_sorting and color_theme are identical. The only diff is the presets line, which gains the 4th custom preset proc:0:default.
  • Isolated tmux run of real btop 1.4.7 with --debug, -p 4 and the repo config: exit code 0, and the btop log has no warn or error lines
  • uv run --with pytest python -m pytest tests/test_install_tools.py -q: 54 passed (unit test, not live)
  • tmux runs of the launcher at 50x40, 177x66, 80x24, 80x23 and 30x10, a resize of a running pane in both directions, and a tty-restore check after quit
  • Removed .venv, .pytest_cache and __pycache__ from the worktree afterwards
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

administrator added 3 commits October 1, 2026 18:03
Byte-for-byte copy of the btop.conf btop 1.4.7 wrote on a clean quit
(sha256 9d48d3c289346e4e9f55bc9e8888369ed155268448857de5bb4a7f8326ec49e0).
btop shows only "Terminal size too small" in a pane smaller than its shown
boxes need: 80x24 for all four boxes in btop 1.4 (cpu 60 columns wide,
proc 44 beside the 36 of mem and net). A Herdr phone pane is about 50
columns, so btop never loaded there.

Channel: btop comes from the latest upstream GitHub release
(aristocratos/btop), not the snap. scripts/install_tools.py installs the
static musl build as a core tool, verified against the GitHub
release-asset SHA-256, the same path herdr, gh and treehouse use
(docs/architecture.md rule 1). The snap is a third-party repack
(publisher kz6fittycent, not upstream) with no published checksum the
installer can verify, and snapd is not available in the container image.
The installer links the binary as ~/.local/bin/btop-bin. It reads
~/.config/btop/btop.conf, so the config is installed there instead of at
the snap path.

Config change against the capture (sha256 9d48d3c2...): the previous
commit is the capture byte-for-byte. This commit changes one line:
line 26, presets, gains " proc:0:default", which defines preset 4
(processes only). btop 1.4.7 does not know the captured
proc_tree_auto_collapse = 0 line (the snap was built from a newer
upstream commit), so it ignores the line and drops it when it rewrites
the file on exit. Every apply restores it.

Mechanism: maintenance/btop.sh (plain sh, about 20 lines), installed as
~/.local/bin/btop, reads stty size. It starts btop with preset 0 (all
boxes) in a pane of at least 80x24, and with preset 4 otherwise. The
threshold is btop's real minimum for all four boxes, measured on real
btop; at 50 columns only proc alone (44x16) or mem+net fits, and preset
4 keeps the saved proc_sorting = "cpu direct". A preset in btop.conf is
the only way to choose boxes from the command line (-p). The launcher
runs btop in the background and traps WINCH. When a live resize crosses
80x24, it sends TERM (btop exits cleanly) and starts btop again with the
other preset. When btop quits, the launcher exits with btop's status.
It always passes -p, because btop saves the last preset's shown_boxes
on exit; without -p, a phone session would leave the desktop pane
showing only proc. btop's stdin is a copy of the original stdin, not
/dev/tty: with a /dev/tty stdin btop assumes a Linux console and drops
to 16-color tty mode.

ansible/tasks/herdr.yml installs the launcher and config on every apply;
verify.yml checks they exist.

Proof, real btop 1.4.7 in throwaway tmux sessions: 50x40 shows preset 4;
a live resize to 177x66 shows the full layout; back to 50x40 and wide
again switch correctly; quitting exits 0. 80x24 shows the full layout,
79x30 and 120x23 show preset 4. Quitting in phone mode and then starting
wide shows the full layout. Five rapid resize cycles end on the correct
preset with no leftover processes.
@undeemed
undeemed merged commit faf5b86 into main Oct 1, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant