7.6 KiB
Learnings
Reusable patterns. Caveman + English.
LRN-001 — Dotfiles installer idempotency pattern
2026-05-27. Re-runnable installer: rm -rf target before each git clone, mkdir -p dirs,
back up existing config to fixed ~/Oldconfig (overwrite prev backup). Avoids "clone fails,
dir exists" on second run. Apply to any provisioning script.
LRN-002 — Redundant vim plugin fetch
2026-05-27. molokai colorscheme committed in vim/colors/ AND cloned to /tmp then copied.
cp -rupv vim/* ~/.vim/ already deploys it. Dropped the clone. Lesson: check what tracked
files already cover before adding external fetch.
LRN-003 — GNOME + xrdp working recipe (Ubuntu)
2026-06-23. apt install xrdp alone = black screen + auth popups. Need: adduser xrdp ssl-cert
(xrdp reads TLS key, else black screen on login); polkit rule allowing org.freedesktop.color-manager.*
(else recurring "Authentication required to create a color managed device" popups). Polkit format
version-gated: v>=0.106 → /etc/polkit-1/rules.d/*.rules (JS); older → .pkla. Verified live polkit
127 → .rules only (.pkla backend dropped). Open RDP 3389 only if firewall active. Restart xrdp
after group add so daemon reloads ssl-cert membership. SUPERSEDED by LRN-004 for Wayland-only GNOME (xrdp recipe N/A there).
LRN-004 — gnome-remote-desktop --system: remote desktop on Wayland-only GNOME
2026-06-23. xrdp does NOT work on Wayland-only GNOME (Shell asserts XDG_SESSION_TYPE=wayland, Xorg
backend dies instantly on login) → LRN-003 xrdp recipe N/A on such hosts. Use g-r-d system "Remote
Login": self-signed TLS cert via grdctl --system rdp set-tls-cert/set-tls-key, rdp enable,
enable+start gnome-remote-desktop.service. Auth = 2 layers: shared gate creds via set-credentials
(unlock GDM) THEN per-user PAM login. Gate creds REQUIRED — empty → mstsc 0x904/0x7 (BLK-004).
Listening socket + TLS + enable NOT enough alone. TPM warn Init TPM credentials failed ... using GKeyFile as fallback = harmless on TPM-less host (creds → keyfile). Connect: client → ip:3389,
accept self-signed cert, gate creds, then GDM user. Supersedes LRN-003 for Wayland GNOME.
LRN-005 — df --output=pcent is GNU-only → keep /etc/profile.d disk scripts Linux-gated
2026-06-24. df --output=pcent (and /etc/profile.d itself) are GNU coreutils / Debian conventions,
absent on macOS BSD df. Any install step deploying such a snippet system-wide must sit inside the
command -v apt-get (Linux) block, never the OS-agnostic path. Deploy idempotently with
sudo install -D -m 0644 src /etc/profile.d/x.sh (-D makes the dir, overwrite = re-runnable). Caveat:
/etc/profile.d/*.sh runs for LOGIN shells only — non-login terminals need /etc/bash.bashrc instead.
LRN-006 — Login-resume scripts must be SOURCED, not executed
2026-06-24. dtach-router (any login script that hands control back via return + attaches to host TTY)
must be SOURCED, never run as a command. Executed: its guard case $- in *i*) ;; *) return 0 2>/dev/null ;;
can't return from a non-sourced script → error swallowed by 2>/dev/null → falls THROUGH the guard →
runs fzf + dt at … >/dev/tty → /dev/tty: No such device or address in EVERY non-interactive login shell
(bash -lc, cron, scp, tool sandbox). Repro'd live (fired on each Bash init). Fix in ~/.profile:
case $- in *i*) [ -x router ] && . router ;; esac. Also: don't re-guard by parsing decorative output
([ "$(dt ls)" != "Aucune session dtach." ]) — fragile (couples to exact string) AND redundant
(dtach-router already returns on empty dt --raw). Let the script self-guard. Bonus gotcha: ~/.profile
is NOT read by bash if ~/.bash_profile or ~/.bash_login exists.
LRN-007 — Doc-drift by file mtime misses partial doc updates
2026-06-25. git log -1 --format=%aI -- README.md reports "fresh" → false negative for staleness.
README touched in 46512ee (docs(readme): fix wkhtmltopdf→weasyprint, PACKAGE LIST ONLY) AFTER feature
commit 0bd936b (gnome-remote-desktop + code-server), so the mtime-based "commits since doc edit" scan
returned EMPTY while code-server + RDP stayed undocumented. A partial doc commit resets the clock and
hides earlier feature drift. Fix: drift-detect against FEATURE commits (scan git log for feat/* touching
source since the doc's last SUBSTANTIVE edit, OR cross-ref each entry-point / install-step in code vs doc
text) — never trust doc timestamp alone. Surfaced by /doc clean this session.
LRN-008 — VS Code Remote-SSH terminals are non-login → skip ~/.profile
2026-06-25. VS Code Remote-SSH (Linux) integrated terminals = NON-login interactive bash → source ~/.bashrc,
NEVER ~/.profile. Any login-scoped startup wiring (~/.profile, ~/.bash_profile) silently never runs there.
Diagnose via process tree: VSCODE_IPC_HOOK_CLI env + .vscode-server/.../remote-cli in PATH, no sshd /
no bash -l ancestry (the launching shell reparents to systemd); confirm with shopt -q login_shell. For
"run once at session start" that must work under VS Code, hook ~/.bashrc (universal: sourced by login shells
via ~/.profile AND directly by non-login interactive shells), NOT ~/.profile. Extends/corrects LRN-006
(its ~/.profile fix is valid only for real login shells, not IDE remotes). Deductive tell that pinned it:
wiring proven correct + target resource (session) proven present, yet menu never fires at startup → the startup
file is not being sourced → non-login shell. See BDR-009.
LRN-009 — tmpfs /tmp + agents: two symptoms, one cause; swap is not the fix
2026-09-22. "RAM overloaded" + "No space left on device" in shells = same root: /tmp tmpfs (RAM). Diagnose
findmnt -T /tmp (FSTYPE tmpfs, SIZE=50% RAM). Swap only pages tmpfs out, cap unchanged. Fix = /tmp on disk
(mask tmp.mount; it is wanted from /usr/lib/systemd/system/local-fs.target.wants/). Gotcha:
/etc/tmpfiles.d/X.conf REPLACES /usr/lib/tmpfiles.d/X.conf wholesale → copy the other lines
(q /var/tmp 30d) or they vanish. Validate: systemd-tmpfiles --dry-run --create <file>.
LRN-010 — systemd $VAR in ExecStart honours quotes inside EnvironmentFile values
2026-09-22. EARLYOOM_ARGS="-m 10 --avoid '^(a|b)$'" + ExecStart=… $EARLYOOM_ARGS: bare $VAR = split on
whitespace, quotes respected then stripped → regex arrives as ONE arg. ${VAR} = whole value as one arg (wrong
here). Old-server earlyoom file valid as-is. Env-file syntax check: sh -n.
LRN-011 — verify sudo-bound installer functions with a stub harness
2026-09-22. Can't run sudo/systemctl here (security rule; permission layer even denied systemctl is-enabled).
Extract functions (sed -n '/^fn()/,/^}/p') into scratch, define sudo(){ echo "SUDO: $*"; } + systemctl
findmntstubs, overrideconfirmper scenario,</dev/nullfor no-TTY. Covers every branch, prints exact sudo calls,set -ebehaviour included. Gotcha:unset -fon an overridden fn removes it entirely.
LRN-012 — fail2ban jail must match the real sshd port, or ban all ports
2026-09-22. Old server: sshd Port 337, fail2ban sshd jail with default port = ssh (=22) → bans hit port 22
only, SSH on 337 stayed open to the banned IP. Silent, no error. Fix options: port = 337 (needs detection /
templating, drifts if port changes) or banaction = %(banaction_allports)s (offender blocked everywhere, port
irrelevant) — chose allports. Offline checks without fail2ban installed: python configparser with
BasicInterpolation resolves %(x)s refs and proves the file parses; apt.conf → apt-config --config-file=<f> dump APT::Periodic. sshd drop-ins can't be sshd -t-tested without root (host keys).