Commit Graph

23 Commits

Author SHA1 Message Date
071963b92b Fix: stop the Flutter SDK reading your own repo as itself inside git hooks
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
`git commit`/`git push` export GIT_DIR to every hook they run, and the
flutter tool shells out to git to identify its own SDK. `git -C
"$FLUTTER_ROOT" rev-parse HEAD` in bin/internal/shared.sh does not beat an
inherited GIT_DIR -- `-C` only changes directory, GIT_DIR overrides
repository discovery outright:

    $ git -C /opt/flutter rev-parse HEAD
    058e0af2c2...                                  <- Flutter's HEAD
    $ GIT_DIR=~/diet-guard/.git git -C /opt/flutter rev-parse HEAD
    84bd550331...                                  <- diet-guard's HEAD

So inside a hook, flutter reports the project as the SDK:

    Flutter 3.47.0-0.1.pre • channel main • https://github.com/kuhyx/diet-guard

pub then resolves the SDK as 0.0.0-unknown and every version constraint
becomes unsatisfiable. flutter also writes that into its own version cache,
so the wrong answer outlives the hook and follows the next caller into an
unrelated repository -- which is why the identical command passes when you
re-run it by hand and fails again on the next push.

diet-guard's pre-push gate hit this today. Clearing the variables per call in
each hook works, and screen-locker and diet-guard now do, but only for as
long as every future hook author remembers -- deployment hygiene, not a fix.
This installs a wrapper ahead of /usr/bin on PATH that strips them for every
caller in every repo, covering both flutter and dart. It removes its own
directory from PATH before exec so the Flutter entry point's internal `which
flutter` cannot loop back into it, and it verifies itself by running flutter
against a throwaway poisoned GIT_DIR and failing unless the real SDK comes
back.

Not patching bin/internal/shared.sh: that file belongs to the flutter-bin
package, so the next update would silently restore the bug. Upstream already
unsets these variables in content_aware_hash.sh and update_engine_version.sh,
just not in the version path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 15:39:33 +02:00
d91c0ab0e9 fix(i3): repair the dead idle-inhibitor autostart and make it a singleton
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
The i3 `exec` line pointed at
/home/kuchy/linux-configuration/scripts/utils/turn_off_auto_idle_screen_shutdown.sh
- a typo'd home ("kuchy") *and* a path that stopped existing when the repo
was reorganised in db6276b. The exec has been failing silently at every
login ever since, so the idle inhibitor was simply not running: a controller
was connected with no "idle-off" lock held, meaning the screen could blank
and lock during controller gaming, which is exactly what the line exists to
prevent.

Point it at the script's real location.

Also make --watch-controller a singleton. It is a long-lived watcher started
from an i3 `exec`, and i3 re-runs those on every `i3-msg restart`, so each
restart would leave another watcher holding a redundant inhibitor for the
rest of the session. One flock, held for the life of the watcher.

Verified: the watcher now holds
  idle-off 1000 kuhy <pid> systemd-inhibit idle "game controller connected"
and a second invocation declines instead of stacking a second inhibitor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 19:57:57 +02:00
4f2c82fee7 Add overnight power-save to night-lockdown; harden dnsmasq with a watchdog
Some checks failed
Pre-commit checks / pre-commit (push) Has been cancelled
Shell tests / shell-tests (push) Has been cancelled
Shell tests / arch-tests (push) Has been cancelled
Caps CPU (amd-pstate-epp) and GPU (nvidia-smi power limit) during the
existing night-lockdown window instead of migrating services to a
Raspberry Pi (tried, abandoned after SSH auth failed). A more aggressive
GPU PCI-unbind option is built but left disabled by default.

Also fixes an unrelated live incident found while testing: dnsmasq (this
PC's LAN DHCP/DNS server) had died with zero error logged, breaking WiFi
for every device on the network. Added a watchdog timer mirroring the
existing shutdown-timer-monitor-watchdog pattern, and whitelisted the
Tapo smart plug app so the overnight power reading is checkable during
the phone's curfew window.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 11:58:37 +02:00
0bdaecd281 Add personal website deploy + move Gitea to a subdomain
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Serve the personal website (extracted to github.com/kuhyx/personal-website)
at the root kuhy.duckdns.org behind the shared gitea-caddy edge, moving Gitea
to gitea.kuhy.duckdns.org.

- setup_gitea.sh: relocate Gitea to gitea.kuhy.duckdns.org and refactor the
  shared Caddyfile into per-service sites/*.caddy imports (fixes the latent
  bug where re-running dropped the dufs/syncyomi blocks).
- setup_personal_website.sh (new): idempotent setup/status/help; migrates
  existing Caddy blocks to snippets, relocates Gitea, builds ~/personal-website,
  serves dist/ from a loopback caddy (admin off) on 127.0.0.1:8088 fronted on
  the root domain, reuses the DuckDNS/firewall edge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KG7yQj4hP8DDpasK2e4cn7
2026-07-13 19:10:48 +02:00
59430f91a1 Add setup_syncyomi.sh: self-hosted SyncYomi manga sync
Stands up SyncYomi (ghcr.io/syncyomi/syncyomi:v1.1.11) in Docker behind
the existing shared gitea-caddy edge at kuhy-sync.duckdns.org, so
TachiyomiSY syncs its library from a mobile network. Follows the
setup_dufs_cloud.sh pattern: loopback-bound container, one appended Caddy
site block (auto Let's Encrypt), the shared DuckDNS updater extended, and
80/443 opened via setup_wireguard_ssh.sh. Auto-generates stored 0600
credentials, onboards the first account, and mints/reuses an API key over
the local REST API, printing host + key. Idempotent setup/status/help.

Verified live: container up (restart=unless-stopped), external HTTPS
reachable, and a real phone sync landed a 13.9 MB sync_data row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CbvNNUYcdNtg5NjAhYc12E
2026-07-13 17:20:02 +02:00
b500b5daba Extract dufs cloud (web + app + scripts) to github.com/kuhyx/dufs-cloud
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Move cloud_gallery/, the Flutter dufs_client app, and the dufs/cloud setup
scripts into a new public repo (with full git history), leaving pointers here.
The live media-cloud-sync systemd units were re-pointed to ~/dufs-cloud/scripts/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126QvdMrv2WNeUVShFiLTc3
2026-07-12 18:38:37 +02:00
dfd0e2d70a import_media_archives.sh: fold Downloads media_archive zips into the cloud
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
New reusable utility that extracts each media_archive_*.zip snapshot, MOVEs its
media into ~/cloud/Media/YYYY/MM via sync_media_to_cloud.sh (dedup across the
overlapping snapshots), verifies staging drained, then deletes the zip — only
after its media is confirmed in the cloud. Staging lives on the cloud
filesystem so moves are atomic renames.

Ran over ~/Downloads/media_archive_archive: 44 zips / 27 GB → 44 imported +
deleted, 0 kept. The two 13.7 GB near-duplicate snapshots collapsed (2412
duplicates dropped). Cloud Media 4.9 GB → 19 GB (2949 files); Downloads
reclaimed 27 GB; empty archive dir removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126QvdMrv2WNeUVShFiLTc3
2026-07-12 17:43:23 +02:00
76d4e8e0cf cloud_gallery: React media-gallery SPA served by dufs (Phase 3)
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Replace dufs's plain file list with a React 19 + Vite + TypeScript SPA served
same-origin by dufs, so the cloud URL becomes a real gallery behind the existing
auth: thumbnail grid, image lightbox (zoom + keyboard nav), inline video, drag/
pick upload, download, confirm-guarded delete, and a simple .txt/.md editor.

Serving architecture (de-risked against the real dufs 0.46.0): --assets does not
override the UI, so dufs `render-spa` serves the app shell, the SPA lists dirs
via WebDAV PROPFIND (render-flag-immune) and streams files via GET (Range for
video seeking), with HashRouter so navigation stays at "/". Verified render-spa
leaves KeePass file GET/PUT/?hash intact (Phase 1 unaffected).

Adds generate_thumbnails.sh (ImageMagick images + ffmpeg video posters →
~/cloud/.thumbs, idempotent) and setup_cloud_gallery.sh (build + deploy the SPA
into the cloud root, enable render-spa, wire thumbnail refresh into the Phase-2
media sync). setup_dufs_cloud.sh now emits render-spa only when the SPA is
deployed, so a fresh dufs keeps its default listing.

Verified: pnpm build + lint (strictTypeChecked) clean; 38 vitest tests, coverage
92.8% stmts / 96% lines (dufs-client 100%); local dufs --render-spa serves shell
+ assets + thumbnails + PROPFIND (all 200). Live deploy left for the user (needs
sudo; enables render-spa on the public service). Note: host ffmpeg is broken
(libvpx.so.11) so video posters are skipped until it's fixed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126QvdMrv2WNeUVShFiLTc3
2026-07-12 06:05:52 +02:00
095587c0de linux_configuration: move Downloads media into the dufs cloud (browsable)
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Phase 2 of the cloud roadmap. sync_media_to_cloud.sh MOVES organized images and
videos out of ~/Downloads into ~/cloud/Media/YYYY/MM (by mtime), unzipped and
browsable via the dufs URL — no duplication, the cloud folder becomes the single
storage. Move is crash-safe (mv: atomic on same fs, copy-then-unlink across fs);
files modified in the last 2 min are skipped (in-progress downloads); identical
files are de-duplicated and name clashes get a numeric suffix.

setup_media_cloud_sync.sh installs a oneshot service + 30-min catch-up timer +
a ~/Downloads path watcher, plus a drop-in ordering the existing media-organizer
after the cloud sync so media is moved before it would be zipped.

Verified: shellcheck clean; 12-assertion MOVE suite on throwaway dirs; real
move of a PNG into ~/cloud/Media fetched via the live URL (HTTP 200 image/png).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126QvdMrv2WNeUVShFiLTc3
2026-07-12 05:19:31 +02:00
0902a5e1f4 linux_configuration: unified multi-device KeePass sync over dufs
Add "one vault, synced everywhere" for PC + Ubuntu laptop + phone, with an
identical flow and no stored master password. keepass-open prompts the master
password (in memory only), folds in strays, pulls+merges the cloud copy over
WebDAV, opens KeePassXC on ~/Keepass/Passwords.kdbx, and publishes edits on
close. Safety rests on `keepassxc-cli merge` (entry-level union) plus a dufs
?hash compare-and-swap on upload, so concurrent edits merge instead of clobber.

The consolidator enforces exactly one vault per machine: it folds a stray only
if it opens with the same master password, backs up first, never removes the
canonical, and auto-excludes the dufs serve-path (the remote store) — a
data-safety fix after the first real run tried to delete ~/cloud/Keepass.

Only the dufs *server* password (not the vault key) is cached in the keyring.
Single cross-distro installer, no role flag; the phone uses KeePassDX natively.

Verified: shellcheck clean; 8 (reconcile) + 14 (consolidator) + 8 (installer)
assertions on throwaway vaults; real PC apply with live WebDAV GET=200 and
?hash==sha256; a clean keepass-open run ("no strays / already in sync").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126QvdMrv2WNeUVShFiLTc3
2026-07-12 05:00:27 +02:00
20bc1aeb02 linux_configuration: self-hosted dufs cloud storage + KeePass WebDAV auto-sync
Add setup_dufs_cloud.sh: a self-sustaining installer that stands up dufs
(single Rust binary; web UI + built-in WebDAV) on 127.0.0.1:5000 behind the
existing Caddy + DuckDNS exposure stack at a new subdomain, so files are
reachable from mobile network over HTTPS. An event-driven systemd path unit
mirrors ~/Keepass/Passwords.kdbx into the served folder on every change, so
KeePassDX syncs the vault natively over WebDAV.

Paranoid secret handling for the gitignored .dufs_cloud.conf (plaintext web
password): ignored at repo-root + linux_configuration + **/ globs; a new
check-no-dufs-conf pre-commit hook blocks it even against `git add -f`; the
installer refuses to write it unless git check-ignore confirms it is ignored;
the password is hashed via `openssl passwd -6 -stdin` (never in argv); and a
cloud-root guard forbids serving home/.ssh/.config/.gnupg/the repo/root.

Verified: dufs auth (401/200) + WebDAV (207) against the real v0.46.0 binary,
idempotent Caddy/DuckDNS/unit-render logic, and a live phone-on-cellular test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0126QvdMrv2WNeUVShFiLTc3
2026-07-12 03:40:54 +02:00
a456314f1a Add local-LLM setup script (ollama-cuda + qwen3 + Open WebUI)
setup_local_llm.sh: idempotent Bash installer for a self-hosted local LLM
stack tuned for the RTX 3090 (24GB) — installs ollama-cuda, writes a
systemd drop-in enabling flash attention + Q8_0 KV-cache quantisation,
pulls the model, creates a custom Ollama model with a developer system
prompt, and launches Open WebUI via Docker.

Verification: bash -n and shellcheck -x clean (see evidence artifact).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RRCdR4D8SaVrN2xraMQxM
2026-07-11 00:16:24 +02:00
343d38eaf8 feat: add Pi-hole-like LAN DNS blocking served from the PC (dnsmasq)
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
setup_dns_blocker.sh turns this PC into a DNS server that serves the
identical /etc/hosts blocklist to the whole LAN with no per-device app,
by feeding generate_hosts_file.sh output into dnsmasq via addn-hosts
(same StevenBlack variant + custom entries + unblocks). Adds status/
refresh subcommands, a daily refresh timer, Restart=always, and logging
to /var/log/dns-blocker/.

Because the ISP router cannot advertise a custom DNS server, a dhcp/
dhcp-off mode makes the PC the LAN DHCP server (static IP via nmcli with
a gateway-ping self-check and safe, always-online rollback), handing out
itself as the DNS server so every device is blocked automatically.

setup_wireguard_ssh.sh gains an allow-dns subcommand that opens DNS (53,
LAN saddr) and DHCP (67) in the single-owner nftables ruleset. The DHCP
accept rule is placed before 'ct state invalid drop' because the
0.0.0.0->255.255.255.255 DHCP broadcast is classified INVALID and was
otherwise dropped. Firewall detection now reads the actual loaded ruleset
(a ruleset can be loaded while nftables.service reads inactive) and avoids
a grep -q/pipefail SIGPIPE false-negative.

Verified end-to-end: Pixel-6a leased 192.168.1.82 with DNS 192.168.1.43;
youtube.com blocked, legit sites resolve. Regression tests added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018pqSnrLt8QoWup7n2Ymk77
2026-07-10 19:02:46 +02:00
ea94435c4f Add self-hosted Gitea deployment, mirroring all GitHub repos publicly
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Deploys Gitea+Caddy (auto-HTTPS via Let's Encrypt) at kuhy.duckdns.org,
extends setup_wireguard_ssh.sh with an allow-web firewall subcommand, and
mirrors all 21 GitHub repos (5 private) via Gitea's native pull-mirror.
Runs containers with host networking to work around a discovered bug where
this host's nftables forward-chain silently blocks Docker bridge egress.

Adds a self-hosted-service-exposure skill capturing the reusable pattern
and gotchas for future public-facing deployments.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C5jnu99ZuENSkuFQKLcSdh
2026-07-04 07:45:00 +02:00
66272dc95a feat: add self-hosted WireGuard+SSH remote access from Android, across networks
Lets SSH terminal access reach this PC from a phone on a different network
(mobile data vs home broadband), using only FOSS/free software: self-hosted
WireGuard (no relay/coordination server), DuckDNS for the dynamic public IP,
and a default-drop nftables firewall so sshd is never exposed to the WAN
directly -- only the WireGuard UDP port is forwarded, SSH is reachable only
through the tunnel or LAN.

Verified fully end-to-end (phone on mobile data, real handshake + SSH login).
Several bugs only surfaced through live execution and were fixed in place:
a DNS=1.1.1.1 line that broke all phone DNS once the tunnel was active, a
require_root/sudo arg-forwarding bug, hostname/dig not being installed on a
minimal Arch system, a bash RETURN-trap scoping bug, and a DuckDNS cron-dedup
that would have deleted an unrelated pre-existing Joplin DuckDNS cron entry.

Also whitelists the WireGuard/F-Droid/ConnectBot apps (plus the todo app) in
phone_focus_mode's WHITELIST so the GPS-based focus daemon doesn't disable
them. Adds "iif" (nftables keyword) to the codespell ignore-list since it
was flagged as a false-positive typo of "if".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TUSBRyujRMuGiUitGP8gET
2026-06-21 20:07:13 +02:00
038e08d2be feat: split oversized modules for 500-line limit, fix kasa coverage gap
Split diet_guard/_gatelock.py, wake_alarm/_alarm.py, and the
usage_report.py/_usage_report_parsing.py pair into focused
sub-modules so every Python file is <= 500 lines, satisfying
test_file_length.py. Install python-kasa into .venv (declared in
requirements but missing after the 3.13->3.14 venv upgrade),
fixing 8 failing smart_plug tests and restoring 100% coverage.

Also includes prior in-progress work from the working tree: the
wake_alarm Progress/View/Hardware field-grouping refactor,
brother_printer query module + tests, diet_guard foodbank/state/cli
updates, new shared coerce/logging_setup helpers, morning_routine
orchestrator tweaks, dwm window-manager config, gaming scripts, and
misc maintenance/digital-wellbeing script updates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 07:19:37 +02:00
8fdbfd8411 remove: uninstall unused ffmpeg screen-replay feature
Deleted screen_replay.sh (x11grab ring-buffer) and kill_stale_recorders.sh
(stale-ffmpeg killer). Removed autostart exec and Mod+Shift+F12 binding from
i3/config; removed replay startup from dwm-session. Both scripts archived in
testsAndMisc-archive.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 10:44:36 +02:00
2e1e370c0f refactor: extract all inline Python from shell scripts into proper .py files
Move every multi-line python heredoc/`-c` block into a dedicated .py file so
ruff, mypy, pylint, bandit, and pytest can apply to it:
- linux_configuration/zsh/calc-live.zsh → python_pkg/live_calc/calc_eval.py (100% branch cov, 46 tests)
- meta/scripts/check_ai_evidence.sh → meta/scripts/validate_evidence.py
- meta/scripts/check_agent_contract.sh → meta/scripts/validate_contract.py
- phone_focus_mode/lib/monitor.sh → phone_focus_mode/lib/monitor_report.py
- phone_focus_mode/deploy.sh → phone_focus_mode/strip_workout_hosts.py
- linux_configuration/.../analyze_repo.sh → fast_count.py

Also: add zsh-syntax pre-commit hook (zsh -n); exclude zsh from shellcheck;
add tests for all 4 non-python_pkg helpers; update CLAUDE.md Shell Style with
the no-inline-Python rule.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 10:31:48 +02:00
fcd3f7ed2f perf(idle-off): replace controller-watch fork storm with a single systemd-inhibit
turn_off_auto_idle_screen_shutdown.sh --watch-controller forked 4 xset +
1 xdotool per joystick event (blocking on a dd read, debounced by sleep
0.3) — ~21 procs/s while a controller was connected during gaming. The
xset poking was redundant: startup already disables DPMS/blanking.

Replace reset_idle_activity / watch_js_device / the polling watcher with
one long-lived `systemd-inhibit --what=idle:sleep` held only while a
/dev/input/js* device is present, re-evaluated on udev input add/remove
events (event-driven; 30s presence-poll fallback). EXIT+INT/TERM traps
release the inhibitor on every termination path.

Verified live: subtree stays {systemd-inhibit, udevadm} with zero
dd/xset/xdotool; exactly one inhibitor held; clean release on SIGTERM,
no orphans. Takes that loop from ~21 forks/s to 0.

Behavior change: keeps the session awake while a controller is connected
(not only during active input).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 18:14:07 +02:00
9a955bb424 move install_core_system.sh to linux_configuration/ root 2026-05-15 01:17:49 +02:00
765fa92543 Add install_core_system.sh; remove pc_startup and thesis_tracker
- Add linux_configuration/scripts/single_use/install_core_system.sh:
  unified installer for core modules (workout locker, hosts, shutdown
  timer) plus optional secondary modules (steam enforcer, pacman
  wrapper, i3 config, compulsive block, focus daemon)
- git rm pc_startup_visual_status.sh, setup_pc_startup_monitor.sh,
  thesis_work_tracker.sh, thesis_work_status.sh,
  setup_thesis_work_tracker.sh, README_THESIS_TRACKER.md,
  systemd/thesis-work-tracker@.service, and their two test files
- Remove now-dead setup_pc_startup_monitor.sh call from fresh-install/main.sh
2026-05-15 01:13:28 +02:00
db6276b3ff refactor(linux_configuration): move remaining dirs + scripts/ to meta/
- Move fresh-install/ → scripts/single_use/fresh-install/
- Move hosts/ → scripts/periodic_background/hosts/
- Move i3-configuration/ → scripts/periodic_background/i3-configuration/
- Delete linux_configuration/LaTeX/, nix-poc/, report/ (dead dirs)
- Move repo-root scripts/ → meta/scripts/
- Update root .pre-commit-config.yaml: scripts/ → meta/scripts/ (9 entries)
- Update run.sh ARTIFACT_INIT_SCRIPT to meta/scripts/
- Update fresh-install/main.sh: hosts/install.sh + i3-configuration/install.sh paths
- Update check_python_location.sh: add meta/scripts/ to exception list
- Fix midnight flakiness in test_recent_workout_returns_true: use timezone-aware
  local noon instead of now-1h to avoid SQL date() boundary issues
2026-05-15 00:53:01 +02:00
42a66a1419 refactor(linux_configuration/scripts): split all scripts into single_use/ and periodic_background/
- Move all linux_configuration scripts into two semantic categories:
  - single_use/: scripts run once manually (fresh install, fixes, setup)
  - periodic_background/: scripts run by systemd timers or daemons
- Preserve existing subdirectory structure within each category
- Fix lib/common.sh source paths for new directory depths
- Fix CONFIG_DIR depth in setup_periodic_system.sh and check_and_enable_services.sh
- Update all references in tests, fresh-install/main.sh, nix modules, and docs
- Fix check_polling_antipatterns.sh false positives (||, regex |, case patterns, jq strings)
- Fix pre-existing mypy exclusion path and type annotations for moved tools/ directory
- Rewrite check_polling_antipatterns.sh using awk (no bash regex loops); add require_serial: true
2026-05-15 00:32:35 +02:00