From 6b828d29aa59fbe794a9fa466e2c3f21d8031c69 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Sun, 23 Aug 2026 20:57:51 +0200 Subject: [PATCH] Extract digital-wellbeing; the directory-depth cap now enforces The last unit over the cap is now github.com/kuhyx/digital-wellbeing, cloned at ~/digital-wellbeing, and `--warn` is gone from the directory-depth-cap hook. `check_directory_depth.sh --all` exits 0 with zero violations, down from 46 at the start of the session. That was the point of the campaign. History was filtered across all three paths the unit has lived at (63 commits); the current path alone holds 2. The six suites that test this code lived in linux_configuration/tests/ rather than beside it, so they were filtered separately (24 commits) and merged in as a second root. A path filter of the unit alone would have left them here, testing code that had left the repo -- and their names would still have been in shell-tests.yml's hardcoded arrays, failing CI on missing files. linux_configuration/lib/common.sh is vendored into vendor/ rather than trimmed to the four functions actually used: set_actual_user_vars calls two of the others, so a trim breaks silently. It is in vendor/ and not lib/ because test_music_parallelism.sh copies the unit's own lib/ into a scratch worktree while stubbing the shared lib; in one directory the real common.sh overwrote the stub, and the suite silently measured the wrong library. Running the callers caught what shellcheck and grep both missed: check_and_enable_services.sh died with "DW_REPO: unbound variable" because the definition sat below its first use. It now runs clean and reports ok for every extracted-unit check. Two long-standing live defects fixed as a consequence. The pacman-wrapper drift manifest embeds ABSOLUTE paths and had named deleted scripts/ paths since the flatten, so the check reported a permanent false "stale or tampered"; it now verifies, with unchanged hashes proving the extracted bytes are identical. And the last two stale live references are gone: 8 -> 0. The two pre-commit excludes for setup_midnight_shutdown.sh are dropped rather than moved. lsattr shows nothing in the unit is chattr +i any more, and the fixer hooks leave the file alone when allowed to run. Removing 80 shell files also shrank the jscpd denominator enough that the virtualenv/tqdm clones inside the GENERATED .ci-mirror-venv/ tipped the repo to 2.09% and blocked this commit, which adds no duplication. That directory is the same vendored third-party shell the ignore list already excludes as .venv/, just under another name; jscpd walks the working tree rather than the index, so being untracked did not spare it. Excluded by name: 1.74%. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PJrqKERAo5QUeEHWS5Asgp --- .github/workflows/shell-tests.yml | 10 +- .gitignore | 1 - .pre-commit-config.yaml | 22 +- AGENTS.md | 9 +- .../extract-digital-wellbeing-2026-08-23.json | 22 + .../extract-digital-wellbeing-2026-08-23.json | 92 + linux_configuration/.githooks/pre-commit | 10 +- linux_configuration/fresh-install/main.sh | 2 +- linux_configuration/install_core_system.sh | 8 +- .../install_ungoogled_chromium_ubuntu.sh | 4 +- .../check_and_enable_services.sh | 31 +- .../digital_wellbeing/.gitignore | 1 - .../README_COMPULSIVE_BLOCK_LLM.md | 245 - .../README_MIDNIGHT_SHUTDOWN_LLM.md | 162 - .../block_compulsive_opening.sh | 182 - .../digital_wellbeing/heavy_job_lock.sh | 232 - .../digital_wellbeing/install_leechblock.sh | 119 - .../digital_wellbeing/install_ublock.sh | 225 - .../leechblock_defaults.json | 1570 - .../digital_wellbeing/lib/cco_install.sh | 238 - .../digital_wellbeing/lib/cco_report.sh | 102 - .../digital_wellbeing/lib/cco_state.sh | 149 - .../digital_wellbeing/lib/cco_wrapper.sh | 120 - .../lib/leechblock_browsers.sh | 184 - .../lib/leechblock_config.sh | 36 - .../digital_wellbeing/lib/leechblock_fetch.sh | 114 - .../lib/leechblock_firefox.sh | 108 - .../digital_wellbeing/lib/ms_config_guard.sh | 42 - .../digital_wellbeing/lib/ms_guard.sh | 131 - .../digital_wellbeing/lib/ms_monitor.sh | 238 - .../digital_wellbeing/lib/ms_override_mgr.sh | 239 - .../digital_wellbeing/lib/ms_report.sh | 182 - .../digital_wellbeing/lib/ms_scripts.sh | 157 - .../digital_wellbeing/lib/ms_setup_flow.sh | 231 - .../digital_wellbeing/lib/ms_units.sh | 247 - .../digital_wellbeing/lib/music_detect.sh | 132 - .../digital_wellbeing/lib/music_monitor.sh | 91 - .../digital_wellbeing/lib/music_status.sh | 85 - .../digital_wellbeing/lib/nl_commands.sh | 82 - .../digital_wellbeing/lib/nl_config.sh | 125 - .../digital_wellbeing/lib/nl_enter.sh | 19 - .../digital_wellbeing/lib/nl_units.sh | 100 - .../digital_wellbeing/lib/nl_unlock.sh | 19 - .../lib/payloads/night-lockdown-enter.sh.in | 279 - .../lib/payloads/night-lockdown-unlock.sh.in | 212 - .../lib/payloads/shutdown-config-unlock.sh.in | 224 - .../lib/tests/cco_harness.sh | 146 - .../lib/tests/leechblock_harness.sh | 150 - .../digital_wellbeing/lib/tests/run_all.sh | 23 - .../lib/tests/test_cco_state.sh | 151 - .../lib/tests/test_leechblock_browsers.sh | 85 - .../lib/tests/test_leechblock_fetch.sh | 88 - .../llm-notes/units-and-tasks.md | 96 - .../llm-notes/vulns-and-troubleshooting.md | 51 - .../digital_wellbeing/music_parallelism.sh | 135 - .../digital_wellbeing/package-lock.json | 214 - .../digital_wellbeing/package.json | 10 - .../pacman/96-restore-pkg-wrappers.hook | 16 - .../pacman/README_FOR_LLM.md | 138 - .../pacman/install_makepkg_wrapper.sh | 109 - .../pacman/install_pacman_wrapper.sh | 134 - .../pacman/lib/install_files.sh | 40 - .../digital_wellbeing/pacman/lib/integrity.sh | 125 - .../pacman/lib/managed_copy.sh | 55 - .../pacman/lib/protect_and_extras.sh | 70 - .../llm-notes/maintenance-and-debugging.md | 56 - .../llm-notes/verification-and-cleanup.md | 154 - .../pacman/makepkg_capped.sh | 58 - .../pacman/makepkg_wrapper.sh | 69 - .../digital_wellbeing/pacman/mkpkg.sh | 8 - .../pacman/pacman_blocked_keywords.txt | 77 - .../pacman/pacman_greylist.txt | 3 - .../pacman/pacman_lock_lib.sh | 186 - .../pacman/pacman_whitelist.txt | 248 - .../pacman/pacman_wrapper.sh | 215 - .../digital_wellbeing/pacman/pw_challenge.sh | 177 - .../digital_wellbeing/pacman/pw_cleanup.sh | 153 - .../digital_wellbeing/pacman/pw_guard.sh | 145 - .../digital_wellbeing/pacman/pw_policy.sh | 198 - .../digital_wellbeing/pacman/pw_ui.sh | 90 - .../pacman/rewrap_pkg_managers.sh | 34 - .../digital_wellbeing/pacman/words.txt | 174800 --------------- .../digital_wellbeing/remove_guest_mode.sh | 163 - .../seed_leechblock_storage.js | 125 - .../digital_wellbeing/seed_ublock_storage.js | 179 - .../setup_midnight_shutdown.sh | 109 - .../digital_wellbeing/setup_night_lockdown.sh | 127 - .../systemd/music-parallelism.service | 25 - .../virtualbox/enforce_vbox_hosts.sh | 220 - .../virtualbox/lib/vbox_disk_inject.sh | 236 - .../youtube-music-wrapper.sh | 22 - .../setup_periodic_system.sh | 7 +- .../tests/lib/pacman_wrapper_makepkg.sh | 70 - .../tests/lib/pacman_wrapper_syntax.sh | 119 - .../tests/lib/security_apps.sh | 2 +- .../tests/lib/security_pacman.sh | 4 +- .../test_hosts_guard_pacman_integration.sh | 88 - .../tests/test_makepkg_capped.sh | 31 - .../tests/test_makepkg_wrapper.sh | 126 - .../tests/test_music_parallelism.sh | 189 - .../tests/test_pacman_wrapper_security.sh | 130 - .../tests/test_security_hardening.sh | 7 + .../tests/test_shutdown_timer_monitor.sh | 222 - meta/pyproject.toml | 2 +- meta/scripts/check_directory_depth.sh | 2 +- meta/shell-coverage-allowlist.txt | 5 - 106 files changed, 182 insertions(+), 187368 deletions(-) create mode 100644 docs/superpowers/contracts/extract-digital-wellbeing-2026-08-23.json create mode 100644 docs/superpowers/evidence/extract-digital-wellbeing-2026-08-23.json delete mode 100644 linux_configuration/periodic_background/digital_wellbeing/.gitignore delete mode 100644 linux_configuration/periodic_background/digital_wellbeing/README_COMPULSIVE_BLOCK_LLM.md delete mode 100644 linux_configuration/periodic_background/digital_wellbeing/README_MIDNIGHT_SHUTDOWN_LLM.md delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/block_compulsive_opening.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/heavy_job_lock.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/install_leechblock.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/install_ublock.sh delete mode 100644 linux_configuration/periodic_background/digital_wellbeing/leechblock_defaults.json delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/cco_install.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/cco_report.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/cco_state.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/cco_wrapper.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/leechblock_browsers.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/leechblock_config.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/leechblock_fetch.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/leechblock_firefox.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/ms_config_guard.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/ms_guard.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/ms_monitor.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/ms_override_mgr.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/ms_report.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/ms_scripts.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/ms_setup_flow.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/ms_units.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/music_detect.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/music_monitor.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/music_status.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/nl_commands.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/nl_config.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/nl_enter.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/nl_units.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/nl_unlock.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/payloads/night-lockdown-enter.sh.in delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/payloads/night-lockdown-unlock.sh.in delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/payloads/shutdown-config-unlock.sh.in delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/tests/cco_harness.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/tests/leechblock_harness.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/tests/run_all.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/tests/test_cco_state.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/tests/test_leechblock_browsers.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/lib/tests/test_leechblock_fetch.sh delete mode 100644 linux_configuration/periodic_background/digital_wellbeing/llm-notes/units-and-tasks.md delete mode 100644 linux_configuration/periodic_background/digital_wellbeing/llm-notes/vulns-and-troubleshooting.md delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/music_parallelism.sh delete mode 100644 linux_configuration/periodic_background/digital_wellbeing/package-lock.json delete mode 100644 linux_configuration/periodic_background/digital_wellbeing/package.json delete mode 100644 linux_configuration/periodic_background/digital_wellbeing/pacman/96-restore-pkg-wrappers.hook delete mode 100644 linux_configuration/periodic_background/digital_wellbeing/pacman/README_FOR_LLM.md delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/pacman/install_makepkg_wrapper.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/pacman/install_pacman_wrapper.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/pacman/lib/install_files.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/pacman/lib/integrity.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/pacman/lib/managed_copy.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/pacman/lib/protect_and_extras.sh delete mode 100644 linux_configuration/periodic_background/digital_wellbeing/pacman/llm-notes/maintenance-and-debugging.md delete mode 100644 linux_configuration/periodic_background/digital_wellbeing/pacman/llm-notes/verification-and-cleanup.md delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/pacman/makepkg_capped.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/pacman/makepkg_wrapper.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/pacman/mkpkg.sh delete mode 100644 linux_configuration/periodic_background/digital_wellbeing/pacman/pacman_blocked_keywords.txt delete mode 100644 linux_configuration/periodic_background/digital_wellbeing/pacman/pacman_greylist.txt delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/pacman/pacman_lock_lib.sh delete mode 100644 linux_configuration/periodic_background/digital_wellbeing/pacman/pacman_whitelist.txt delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/pacman/pacman_wrapper.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/pacman/pw_challenge.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/pacman/pw_cleanup.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/pacman/pw_guard.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/pacman/pw_policy.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/pacman/pw_ui.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/pacman/rewrap_pkg_managers.sh delete mode 100644 linux_configuration/periodic_background/digital_wellbeing/pacman/words.txt delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/remove_guest_mode.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/seed_leechblock_storage.js delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/seed_ublock_storage.js delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/setup_midnight_shutdown.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/setup_night_lockdown.sh delete mode 100644 linux_configuration/periodic_background/digital_wellbeing/systemd/music-parallelism.service delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/virtualbox/enforce_vbox_hosts.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/virtualbox/lib/vbox_disk_inject.sh delete mode 100755 linux_configuration/periodic_background/digital_wellbeing/youtube-music-wrapper.sh delete mode 100755 linux_configuration/tests/lib/pacman_wrapper_makepkg.sh delete mode 100755 linux_configuration/tests/lib/pacman_wrapper_syntax.sh delete mode 100755 linux_configuration/tests/test_hosts_guard_pacman_integration.sh delete mode 100755 linux_configuration/tests/test_makepkg_capped.sh delete mode 100755 linux_configuration/tests/test_makepkg_wrapper.sh delete mode 100755 linux_configuration/tests/test_music_parallelism.sh delete mode 100755 linux_configuration/tests/test_pacman_wrapper_security.sh delete mode 100755 linux_configuration/tests/test_shutdown_timer_monitor.sh diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index ec781c4b..49138a2e 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -27,7 +27,6 @@ jobs: set -euo pipefail tests=( test_i3blocks_persist_common.sh - test_music_parallelism.sh test_setup_dns_blocker.sh ) failed=0 @@ -124,7 +123,9 @@ jobs: - uses: actions/checkout@v4 - # The complete Arch-dependent set. All eight verified locally in + # The complete Arch-dependent set. Five of the original eight moved to + # github.com/kuhyx/digital-wellbeing with the code they test; they run + # bare on Ubuntu there. The two that remain were verified locally in # `docker run --cap-add=SYS_PTRACE archlinux:base-devel`. # # test_security_hardening.sh inspects a configured machine (chattr flags, @@ -136,13 +137,8 @@ jobs: run: | set -euo pipefail tests=( - test_hosts_guard_pacman_integration.sh test_i3blocks_efficiency.sh - test_makepkg_capped.sh - test_makepkg_wrapper.sh - test_pacman_wrapper_security.sh test_security_hardening.sh - test_shutdown_timer_monitor.sh ) failed=0 for t in "${tests[@]}"; do diff --git a/.gitignore b/.gitignore index 9b4fbb00..755796bd 100644 --- a/.gitignore +++ b/.gitignore @@ -381,7 +381,6 @@ bottles/ # archwiki_rag local vector index: regenerated by `reindex`, machine-specific, # and full of binary sqlite/parquet blobs. -linux_configuration/periodic_background/digital_wellbeing/data/ # =========================================================================== # billsplit (Flutter) — launcher icons & platform assets MUST live in-repo diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 49317b01..6132896d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,12 +34,7 @@ repos: hooks: - id: trailing-whitespace args: [--markdown-linebreak-ext=md] - # chattr +i (self-commitment lock on the shutdown-enforcement - # script) makes this file unwritable on purpose; the fixer hooks - # can't open it for writing even as the owning user. - exclude: ^linux_configuration/periodic_background/digital_wellbeing/setup_midnight_shutdown\.sh$ - id: end-of-file-fixer - exclude: ^linux_configuration/periodic_background/digital_wellbeing/setup_midnight_shutdown\.sh$ - id: check-yaml args: [--unsafe] - id: check-json @@ -87,15 +82,14 @@ repos: pass_filenames: false always_run: true - id: directory-depth-cap - name: Report paths over the directory-depth cap - # Repo-wide, and deliberately --warn for now: 47 paths under - # periodic_background/{hosts,digital_wellbeing} are over the cap and - # can only come down by extracting those two units, which is blocked - # on a manual guard repoint. Running it warn-only keeps the number - # visible on every commit without blocking unrelated work. Drop - # --warn once both units are out; that is the last step of the - # extraction, not a separate decision. - entry: bash meta/scripts/check_directory_depth.sh --warn --all + name: Enforce the directory-depth cap + # Repo-wide and ENFORCING. It ran --warn while the 46 paths under + # periodic_background/{hosts,digital_wellbeing} were over the cap; + # both units are now their own repos (kuhyx/hosts-blocker, + # kuhyx/digital-wellbeing) and the count is 0, so the cap is real. + # Re-adding --warn would silently re-open the hole it was there to + # close: put the offending path somewhere legal instead. + entry: bash meta/scripts/check_directory_depth.sh --all language: system pass_filenames: false always_run: true diff --git a/AGENTS.md b/AGENTS.md index fea6f3df..06991922 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -42,8 +42,13 @@ Extracted to their own repos: [`steam-backlog-enforcer`](https://github.com/kuhy [`phone-focus-mode`](https://github.com/kuhyx/phone-focus-mode) (with `python_pkg/focus_policy`), [`hosts-blocker`](https://github.com/kuhyx/hosts-blocker) (lives at -`~/hosts-blocker`; monorepo callers resolve it via -`linux_configuration/lib/extracted_repos.sh`). +`~/hosts-blocker`), +[`digital-wellbeing`](https://github.com/kuhyx/digital-wellbeing) (lives at +`~/digital-wellbeing`; carries the six suites that used to sit in +`linux_configuration/tests/`). Monorepo callers resolve both via +`linux_configuration/lib/extracted_repos.sh`, which reads the invoking user's +home from `SUDO_USER` — never `$HOME`, which is `/root` under sudo and under +systemd. Archived work: [`testsAndMisc-archive`](https://github.com/kuhyx/testsAndMisc-archive). ## Adding a Flutter app diff --git a/docs/superpowers/contracts/extract-digital-wellbeing-2026-08-23.json b/docs/superpowers/contracts/extract-digital-wellbeing-2026-08-23.json new file mode 100644 index 00000000..cd4abd98 --- /dev/null +++ b/docs/superpowers/contracts/extract-digital-wellbeing-2026-08-23.json @@ -0,0 +1,22 @@ +{ + "title": "Extract digital_wellbeing, move its six test suites with it, and make the directory-depth cap enforce", + "objective": "Clear the last 41 directory-depth violations by moving the unit to github.com/kuhyx/digital-wellbeing, then delete --warn from the depth hook so the cap is real -- the point of the whole campaign.", + "acceptance_criteria": [ + "check_directory_depth.sh --all exits 0 with --warn removed from the hook entry.", + "The extracted repo's history spans all three paths the unit has lived at (63 commits), plus the 24 from the separately-filtered test suites.", + "All 80 tracked files are byte-identical to the monorepo copies; node_modules and __pycache__ are excluded.", + "All six moved suites plus every lib/tests/run_all.sh pass from ~/digital-wellbeing, with no root, pacman or Arch userland -- verified under env -i.", + "Every moved test name is deleted from BOTH hardcoded arrays in .github/workflows/shell-tests.yml; every name still listed there resolves to a file that exists.", + "check_and_enable_services.sh runs to completion and reports ok for pacman_wrapper, makepkg_wrapper, midnight_shutdown, startup_monitor, periodic_systems and hosts.", + "The pacman-wrapper drift manifest verifies with sha256sum -c after regeneration.", + "report_live_state.sh --check reports ZERO stale linux_configuration/scripts/ references (down from 8).", + "No lint suppression is added anywhere." + ], + "out_of_scope": [ + "Fixing the unwrapped discord/signal-desktop binaries -- pre-existing live state, identical at baseline (18 passed / 1 failed / 5 skipped both before and after).", + "The workout_locker error, which concerns the separate screen-locker repo.", + "The Nextcloud dedup, measured and recommended against in the handoff.", + "Reformatting vendor/common.sh, which is a verbatim copy." + ], + "verifier": "./meta/scripts/check_directory_depth.sh --all exits 0; ~/digital-wellbeing/tests/run_all.sh passes all seven suites; grep for the six moved test names in shell-tests.yml returns 0; sudo check_and_enable_services.sh completes with no unbound-variable error and no missing-script error; sudo sha256sum -c --status /var/lib/pacman-wrapper/source.sha256 exits 0; report_live_state.sh --check greps 0 for linux_configuration/scripts/; shellcheck clean on all nine changed shell files." +} diff --git a/docs/superpowers/evidence/extract-digital-wellbeing-2026-08-23.json b/docs/superpowers/evidence/extract-digital-wellbeing-2026-08-23.json new file mode 100644 index 00000000..e48de6d6 --- /dev/null +++ b/docs/superpowers/evidence/extract-digital-wellbeing-2026-08-23.json @@ -0,0 +1,92 @@ +{ + "intent": "Extract the last over-cap unit into github.com/kuhyx/digital-wellbeing (cloned at ~/digital-wellbeing), then delete --warn from the directory-depth-cap hook so the cap enforces for real. This is the closing step of the restructure campaign.", + "scope": [ + "REMOVED: linux_configuration/periodic_background/digital_wellbeing/ (80 tracked files)", + "REMOVED: 6 test suites + 2 test libs from linux_configuration/tests/", + "REWIRED: check_and_enable_services.sh, setup_periodic_system.sh, install_core_system.sh, fresh-install/main.sh, installers/install_ungoogled_chromium_ubuntu.sh, tests/test_security_hardening.sh, tests/lib/security_apps.sh, tests/lib/security_pacman.sh", + "CONFIG: .pre-commit-config.yaml (--warn removed, 2 stale excludes dropped), .github/workflows/shell-tests.yml, meta/shell-coverage-allowlist.txt, meta/pyproject.toml, .gitignore, meta/scripts/check_directory_depth.sh", + "LIVE: /usr/local/bin/periodic-system-maintenance.sh", + "NON-GOAL: the unwrapped discord/signal-desktop binaries (pre-existing) and the workout_locker error (different repo)" + ], + "changes": [ + "Filtered history across all three paths the unit has lived at (63 commits). The current path alone holds 2 -- the same trap as hosts-blocker, and the reason the count was checked before filtering rather than after.", + "The six suites that test this code lived in linux_configuration/tests/, not beside it. They were filtered separately (24 commits) and merged as a second root, then their hardcoded periodic_background/digital_wellbeing/ prefixes rewritten for the new root-level layout. A path filter of the unit alone would have left them in the monorepo testing code that had left it.", + "Vendored linux_configuration/lib/common.sh into vendor/ rather than trimming it: set_actual_user_vars calls get_actual_user and get_actual_user_home, so a 4-function trim breaks silently. It went to vendor/ and not lib/ because test_music_parallelism.sh copies the unit's lib/ wholesale into a scratch worktree while stubbing the shared lib -- with both in one directory the real common.sh overwrote the stub and the test measured the wrong library.", + "Generalised that test's layout-drift guard from a required ../ prefix to (../)*; the script now sits at the repo root so the prefix is empty. The guard caught this move, which is what it was written for.", + "Dropped the two .pre-commit-config.yaml excludes for setup_midnight_shutdown.sh instead of moving them: lsattr shows nothing in the unit is chattr +i any more, and the fixer hooks leave the file untouched when allowed to run.", + "Removed --warn from the directory-depth-cap hook and reworded its comment from 'deliberately --warn for now' to state that re-adding it re-opens the hole." + ], + "verification": [ + { + "command": "./meta/scripts/check_directory_depth.sh --all", + "result": "pass", + "evidence": "'all checked paths are within 2 directories', exit 0. 46 -> 41 (after hosts) -> 0. The cap now enforces without --warn." + }, + { + "command": "per-root commit counts before filtering", + "result": "pass", + "evidence": "current path 2, scripts/periodic_background/ 41, scripts/digital_wellbeing/ 23; 63 on main. The 94 an --all union reported included unreachable refs and one merge commit that filter-repo correctly prunes." + }, + { + "command": "cmp each of the 80 tracked files against the monorepo copy", + "result": "pass", + "evidence": "ALL 80 TRACKED FILES IDENTICAL. node_modules (340 files) and __pycache__ correctly excluded -- the repo has 80 tracked files, not the 423 on disk." + }, + { + "command": "~/digital-wellbeing/tests/run_all.sh", + "result": "pass", + "evidence": "All six regression suites plus the discovered lib/tests/run_all.sh pass: 'All digital-wellbeing suites passed.'" + }, + { + "command": "env -i PATH=/usr/bin:/bin HOME=/tmp bash ./tests/run_all.sh", + "result": "pass", + "evidence": "Passes under a minimal environment, and no suite needs root or invokes pacman -- so the new repo needs no arch-tests job even though five of these ran in the monorepo's Arch container." + }, + { + "command": "sudo check_and_enable_services.sh (first attempt)", + "result": "fail", + "evidence": "'line 61: DW_REPO: unbound variable' -- the definition sat below its first use. shellcheck and grep both passed the file; only running it caught this." + }, + { + "command": "sudo check_and_enable_services.sh (after moving the block)", + "result": "pass", + "evidence": "Runs to completion: pacman_wrapper ok, makepkg_wrapper ok, midnight_shutdown ok, startup_monitor ok, periodic_systems ok, hosts ok, guest_mode_removal ok. Applied 3 fix(es)." + }, + { + "command": "sudo sha256sum -c --status /var/lib/pacman-wrapper/source.sha256", + "result": "pass", + "evidence": "Exit 0. The manifest embeds ABSOLUTE paths and had named deleted scripts/ paths since the flatten, so the drift check reported a permanent false 'stale or tampered'. It now names ~/digital-wellbeing and verifies -- with unchanged hashes, confirming the extracted bytes are identical." + }, + { + "command": "report_live_state.sh --check | grep -c linux_configuration/scripts/", + "result": "pass", + "evidence": "0, down from the 8 pre-existing stale references at session start. Removals only; none added at any point." + }, + { + "command": "grep the six moved test names in .github/workflows/shell-tests.yml", + "result": "pass", + "evidence": "0 matches, and all four names still listed (test_i3blocks_persist_common, test_setup_dns_blocker, test_i3blocks_efficiency, test_security_hardening) resolve to files that exist." + }, + { + "command": "test_security_hardening.sh, current tree vs a detached worktree at HEAD", + "result": "pass", + "evidence": "18 passed / 1 failed / 5 skipped in BOTH. The one failure is 'Compulsive block wrappers installed' -- discord and signal-desktop lack their /usr/bin wrapper. Live-system state, identical before and after." + }, + { + "command": "shellcheck on all nine changed shell files", + "result": "pass", + "evidence": "ALL CLEAN, no severity filter." + } + ], + "risks": [ + "vendor/common.sh duplicates 163 lines of the monorepo lib. The two copies may drift; that is accepted, and the README says so, so a future session does not 're-fix' it by re-coupling the repos.", + "The pacman wrapper's drift manifest is regenerated by the checker's own fix path. A machine that clones digital-wellbeing to a non-default location must re-run install_pacman_wrapper.sh or the manifest will name the wrong paths.", + "install_core_system.sh on a fresh machine now hard-stops at three modules until both extracted repos are cloned. Deliberate and loud; INSTALLER_FIX_TASK.md tracks fresh-machine installer defects.", + "The depth cap is now enforcing, so any future path more than two directories deep is a hard commit failure rather than a warning." + ], + "rollback": [ + "git revert this commit to restore the unit, the six suites, the two test libs and every rewired caller, and to put --warn back on the depth hook.", + "Restore /var/tmp/periodic-system-maintenance.sh.pre-dw to /usr/local/bin/.", + "After rollback validate: check_directory_depth.sh --all reports 41 violations again, and sudo check_and_enable_services.sh still completes without an unbound-variable error." + ] +} diff --git a/linux_configuration/.githooks/pre-commit b/linux_configuration/.githooks/pre-commit index 3a25c149..78307cc0 100755 --- a/linux_configuration/.githooks/pre-commit +++ b/linux_configuration/.githooks/pre-commit @@ -81,8 +81,16 @@ fi # --threshold 2: fail if more than 2% duplication # Excluded from the scan, and why: # .venv/ vendored third-party shell (virtualenv, tqdm completions). +# .ci-mirror-venv/ # Not ours, and it alone pushed the repo over the bar: the # same scan reads 2.50% with it and 1.67% without. +# .ci-mirror-venv/ is the SAME third-party shell under a +# different name -- the clean-venv the ci-mirror pre-push gate +# builds. It is untracked and generated, but jscpd walks the +# working tree, not the index, so it was still being scanned. +# Extracting digital_wellbeing shrank the denominator enough +# for those virtualenv/tqdm clones to push the total to 2.09% +# and block a commit that added no duplication at all. # lib/tests/ a fixture IS repetitive by design; the 2% rule exists to # stop duplicated production logic, not shared test setup. # Needed headroom for the Phase 3 coverage campaign. @@ -92,7 +100,7 @@ jscpd_output=$("$JSCPD_BIN" \ --min-tokens 25 \ --threshold 2 \ --reporters "console" \ - --ignore "**/node_modules/**,**/.git/**,**/misc/**,**/*.txt,**/.venv/**,**/lib/tests/**,**/tests/**" \ + --ignore "**/node_modules/**,**/.git/**,**/misc/**,**/*.txt,**/.venv/**,**/.ci-mirror-venv/**,**/lib/tests/**,**/tests/**" \ . 2>&1) || jscpd_exit=$? if [[ ${jscpd_exit:-0} -ne 0 ]]; then diff --git a/linux_configuration/fresh-install/main.sh b/linux_configuration/fresh-install/main.sh index 6f481e3e..213bdc3e 100755 --- a/linux_configuration/fresh-install/main.sh +++ b/linux_configuration/fresh-install/main.sh @@ -179,7 +179,7 @@ fi cd ~/linux-configuration sudo "$HOME/hosts-blocker/install.sh" i3/install.sh -periodic_background/digital_wellbeing/pacman/install_pacman_wrapper.sh +"$HOME/digital-wellbeing/pacman/install_pacman_wrapper.sh" fixes/nvidia_troubleshoot.sh sudo features/setup_activitywatch.sh sudo utils/setup_media_organizer.sh diff --git a/linux_configuration/install_core_system.sh b/linux_configuration/install_core_system.sh index 3811234c..81c11494 100755 --- a/linux_configuration/install_core_system.sh +++ b/linux_configuration/install_core_system.sh @@ -8,7 +8,7 @@ # # SECONDARY modules (prompted unless --all / --none given): # 4. Steam backlog enforcer – python_pkg/steam_backlog_enforcer/ -# 5. Pacman wrapper – periodic_background/digital_wellbeing/pacman/ +# 5. Pacman wrapper – github.com/kuhyx/digital-wellbeing (~/digital-wellbeing) # 6. i3 configuration – i3/ # 7. Compulsive opening block – block_compulsive_opening.sh # @@ -135,7 +135,7 @@ run_installer "Hosts blocking" \ bash "$(extracted_repo_dir hosts-blocker)/install.sh" run_installer "Midnight shutdown timer" \ - bash "$LINUX_CONFIG/periodic_background/digital_wellbeing/setup_midnight_shutdown.sh" + bash "$(extracted_repo_dir digital-wellbeing)/setup_midnight_shutdown.sh" # ═══════════════════════════════════════════════════════════════════════════════ # SECONDARY MODULES (prompted unless --all / --none) @@ -146,13 +146,13 @@ ask_install "Steam backlog enforcer" \ sudo bash "$REPO_ROOT/python_pkg/steam_backlog_enforcer/install.sh" ask_install "Pacman wrapper" \ - bash "$LINUX_CONFIG/periodic_background/digital_wellbeing/pacman/install_pacman_wrapper.sh" + bash "$(extracted_repo_dir digital-wellbeing)/pacman/install_pacman_wrapper.sh" ask_install "i3 configuration" \ bash "$LINUX_CONFIG/i3/install.sh" ask_install "Compulsive opening blockade" \ - sudo bash "$LINUX_CONFIG/periodic_background/digital_wellbeing/block_compulsive_opening.sh" install + sudo bash "$(extracted_repo_dir digital-wellbeing)/block_compulsive_opening.sh" install # ═══════════════════════════════════════════════════════════════════════════════ print_summary diff --git a/linux_configuration/installers/install_ungoogled_chromium_ubuntu.sh b/linux_configuration/installers/install_ungoogled_chromium_ubuntu.sh index 1c70edfe..a572dd05 100755 --- a/linux_configuration/installers/install_ungoogled_chromium_ubuntu.sh +++ b/linux_configuration/installers/install_ungoogled_chromium_ubuntu.sh @@ -40,7 +40,9 @@ set -Eeuo pipefail SCRIPT_NAME=${0##*/} SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" -DIGITAL_WELLBEING_DIR="$SCRIPT_DIR/../periodic_background/digital_wellbeing" +# shellcheck source=../lib/extracted_repos.sh +source "$SCRIPT_DIR/../lib/extracted_repos.sh" +DIGITAL_WELLBEING_DIR="$(extracted_repo_dir digital-wellbeing)" SEEDER_SCRIPT="$DIGITAL_WELLBEING_DIR/seed_ublock_storage.js" UBLOCK_EXT_PATH="/usr/share/chromium/extensions/ublock-origin" diff --git a/linux_configuration/periodic_background/check_and_enable_services.sh b/linux_configuration/periodic_background/check_and_enable_services.sh index ef8fa604..f23d825f 100755 --- a/linux_configuration/periodic_background/check_and_enable_services.sh +++ b/linux_configuration/periodic_background/check_and_enable_services.sh @@ -57,38 +57,41 @@ NC=$'\033[0m' # No Color SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" CONFIG_DIR="$(dirname "$(dirname "$SCRIPT_DIR")")" +# Resolve the invoking user's home: this script re-execs itself via sudo, so +# $HOME would be /root here. +REAL_USER="${SUDO_USER:-${USER:-$(id -un)}}" +REAL_HOME="$(getent passwd "$REAL_USER" 2>/dev/null | cut -d: -f6)" +[[ -n $REAL_HOME ]] || REAL_HOME="/home/$REAL_USER" +HOSTS_BLOCKER_REPO="${HOSTS_BLOCKER_DIR:-$REAL_HOME/hosts-blocker}" +# digital-wellbeing was EXTRACTED too (github.com/kuhyx/digital-wellbeing). +DW_REPO="${DIGITAL_WELLBEING_DIR:-$REAL_HOME/digital-wellbeing}" + # Script paths -PACMAN_WRAPPER_INSTALL="$CONFIG_DIR/periodic_background/digital_wellbeing/pacman/install_pacman_wrapper.sh" -MAKEPKG_WRAPPER_INSTALL="$CONFIG_DIR/periodic_background/digital_wellbeing/pacman/install_makepkg_wrapper.sh" +PACMAN_WRAPPER_INSTALL="$DW_REPO/pacman/install_pacman_wrapper.sh" +MAKEPKG_WRAPPER_INSTALL="$DW_REPO/pacman/install_makepkg_wrapper.sh" # Drift manifests written by the two installers above (see deployment_drift). PACMAN_WRAPPER_MANIFEST="/var/lib/pacman-wrapper/source.sha256" MAKEPKG_WRAPPER_MANIFEST="/var/lib/pacman-wrapper/makepkg-source.sha256" -MIDNIGHT_SHUTDOWN_SCRIPT="$CONFIG_DIR/periodic_background/digital_wellbeing/setup_midnight_shutdown.sh" -STARTUP_MONITOR_SCRIPT="$CONFIG_DIR/periodic_background/digital_wellbeing/setup_pc_startup_monitor.sh" +MIDNIGHT_SHUTDOWN_SCRIPT="$DW_REPO/setup_midnight_shutdown.sh" +STARTUP_MONITOR_SCRIPT="$DW_REPO/setup_pc_startup_monitor.sh" PERIODIC_SYSTEM_SCRIPT="$CONFIG_DIR/periodic_background/setup_periodic_system.sh" # hosts-blocker was EXTRACTED (github.com/kuhyx/hosts-blocker). Resolved # below, after REAL_HOME is computed, for the same reason screen-locker is. HOSTS_INSTALL_SCRIPT="" GUARD_LIB_MIGRATE_SCRIPT="$CONFIG_DIR/./fixes/migrate_hosts_guard_to_guard_lib.sh" -COMPULSIVE_BLOCK_SCRIPT="$CONFIG_DIR/periodic_background/digital_wellbeing/block_compulsive_opening.sh" -LEECHBLOCK_SCRIPT="$CONFIG_DIR/periodic_background/digital_wellbeing/install_leechblock.sh" -REMOVE_GUEST_MODE_SCRIPT="$CONFIG_DIR/periodic_background/digital_wellbeing/remove_guest_mode.sh" -VBOX_HOSTS_SCRIPT="$CONFIG_DIR/periodic_background/digital_wellbeing/virtualbox/enforce_vbox_hosts.sh" +COMPULSIVE_BLOCK_SCRIPT="$DW_REPO/block_compulsive_opening.sh" +LEECHBLOCK_SCRIPT="$DW_REPO/install_leechblock.sh" +REMOVE_GUEST_MODE_SCRIPT="$DW_REPO/remove_guest_mode.sh" +VBOX_HOSTS_SCRIPT="$DW_REPO/virtualbox/enforce_vbox_hosts.sh" # screen-locker was EXTRACTED out of this monorepo into its own repo # (github.com/kuhyx/screen-locker, checked out at ~/screen-locker), so these # paths deliberately live outside testsAndMisc. They used to point at # python_pkg/screen_locker/, which stopped existing at extraction time — the # result was check_workout_locker reporting a red "error" for a service that was # installed and enabled the whole time, while its "fix" silently did nothing. -# Resolve the invoking user's home: this script re-execs itself via sudo, so -# $HOME would be /root here. -REAL_USER="${SUDO_USER:-${USER:-$(id -un)}}" -REAL_HOME="$(getent passwd "$REAL_USER" 2>/dev/null | cut -d: -f6)" -[[ -n $REAL_HOME ]] || REAL_HOME="/home/$REAL_USER" WORKOUT_LOCKER_REPO="$REAL_HOME/screen-locker" WORKOUT_LOCKER_INSTALL_SCRIPT="$WORKOUT_LOCKER_REPO/install_systemd.sh" WORKOUT_LOCKER_SCRIPT="$WORKOUT_LOCKER_REPO/screen_locker/screen_lock.py" -HOSTS_BLOCKER_REPO="${HOSTS_BLOCKER_DIR:-$REAL_HOME/hosts-blocker}" HOSTS_INSTALL_SCRIPT="$HOSTS_BLOCKER_REPO/install.sh" # A MISSING repair script means THIS TOOL is broken: it silently stops repairing diff --git a/linux_configuration/periodic_background/digital_wellbeing/.gitignore b/linux_configuration/periodic_background/digital_wellbeing/.gitignore deleted file mode 100644 index c2658d7d..00000000 --- a/linux_configuration/periodic_background/digital_wellbeing/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/linux_configuration/periodic_background/digital_wellbeing/README_COMPULSIVE_BLOCK_LLM.md b/linux_configuration/periodic_background/digital_wellbeing/README_COMPULSIVE_BLOCK_LLM.md deleted file mode 100644 index c91dbdd1..00000000 --- a/linux_configuration/periodic_background/digital_wellbeing/README_COMPULSIVE_BLOCK_LLM.md +++ /dev/null @@ -1,245 +0,0 @@ -# Block Compulsive Opening - LLM Reference Guide - -> **For AI assistants**: This document explains the compulsive opening blocker so you can make correct modifications. - -## System Purpose - -Limit messaging apps (Beeper, Signal, Discord) to **one launch per hour** to reduce compulsive checking behavior. - -## How It Works - -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ LAUNCH INTERCEPTION │ -├─────────────────────────────────────────────────────────────────────┤ -│ │ -│ User clicks "Discord" in app launcher │ -│ ↓ │ -│ /usr/bin/discord (wrapper script) │ -│ ↓ │ -│ exec /usr/local/bin/block-compulsive-opening.sh wrapper discord │ -│ ↓ │ -│ Check: ~/.local/state/compulsive-block/discord.lastopen │ -│ ↓ │ -│ ┌─────────────────┴─────────────────┐ │ -│ │ │ │ -│ ▼ Not opened this hour ▼ Already opened │ -│ Record opening time Show notification │ -│ Launch real binary Exit with error │ -│ /opt/discord/Discord │ -│ │ -└─────────────────────────────────────────────────────────────────────┘ -``` - -## File Locations - -| File | Purpose | -| ------------------------------------------------------ | --------------------------- | -| `/usr/local/bin/block-compulsive-opening.sh` | Installed main script | -| `/usr/bin/beeper` | Wrapper (replaces original) | -| `/usr/bin/signal-desktop` | Wrapper (replaces original) | -| `/usr/bin/discord` | Wrapper (replaces original) | -| `/usr/bin/*.orig` or `SYMLINK:*` | Original binaries/links | -| `~/.local/state/compulsive-block/*.lastopen` | Per-app hour tracking | -| `~/.local/state/compulsive-block/compulsive-block.log` | Activity log | -| `/etc/pacman.d/hooks/95-compulsive-block-rewrap.hook` | Auto-rewrap hook | - -## Managed Applications - -```bash -declare -A APPS=( - ["beeper"]="/usr/bin/beeper" - ["signal-desktop"]="/usr/bin/signal-desktop" - ["discord"]="/usr/bin/discord" -) - -declare -A REAL_BINARIES=( - ["beeper"]="/opt/beeper/beepertexts" - ["signal-desktop"]="/usr/lib/signal-desktop/signal-desktop" - ["discord"]="/opt/discord/Discord" -) -``` - -## State Tracking - -Hour key format: `YYYY-MM-DD-HH` (e.g., `2026-02-02-14`) - -State file content: Just the hour key string - -```bash -# Check if opened this hour -cat ~/.local/state/compulsive-block/discord.lastopen -# Output: 2026-02-02-14 - -# Current hour -date '+%Y-%m-%d-%H' -# Output: 2026-02-02-15 (different = can open again) -``` - -## Wrapper Installation Process - -When `install_all()` runs: - -1. Copies script to `/usr/local/bin/block-compulsive-opening.sh` -2. For each app: - - If original is a symlink: Save `SYMLINK:/target/path` to `.orig` - - If original is a file: Move to `.orig` - - Create wrapper script at original location: - ```bash - #!/bin/bash - exec /usr/local/bin/block-compulsive-opening.sh wrapper "discord" "$@" - ``` -3. Install pacman hook for auto-rewrap - -## Pacman Hook - -After beeper/signal/discord package updates, the hook re-wraps them: - -```ini -[Trigger] -Operation = Upgrade -Operation = Install -Type = Package -Target = beeper -Target = signal-desktop -Target = discord - -[Action] -When = PostTransaction -Exec = /usr/local/bin/block-compulsive-opening.sh rewrap-quiet -``` - -The `rewrap-quiet` command: - -- Checks if wrapper was overwritten (doesn't contain "block-compulsive-opening") -- If overwritten: removes stale `.orig`, re-installs wrapper -- Logs to activity log - -## Commands - -```bash -# Install all wrappers (requires root) -sudo ./block_compulsive_opening.sh install - -# Uninstall all wrappers (requires root) -sudo ./block_compulsive_opening.sh uninstall - -# Check status of all apps -./block_compulsive_opening.sh status - -# Reset a specific app (allow opening again this hour) -./block_compulsive_opening.sh reset discord - -# Reset all apps -./block_compulsive_opening.sh reset-all -``` - -## Log Format - -``` -2026-02-02 14:30:15 - ALLOWED: discord opened (first time this hour: 2026-02-02-14) -2026-02-02 14:30:15 - LAUNCHED: discord with PID 12345 (auto-close in 10m) -2026-02-02 14:38:15 - (notification: "Session will end in 2 minutes") -2026-02-02 14:40:15 - AUTO-CLOSED: discord (PID 12345) after 10m -2026-02-02 14:45:22 - BLOCKED: discord launch prevented (already opened this hour: 2026-02-02-14) -2026-02-02 15:01:03 - ALLOWED: discord opened (first time this hour: 2026-02-02-15) -2026-02-02 15:30:00 - RESET: discord state cleared by user -``` - -## Auto-Close Timer (Session Limit) - -Apps are automatically closed after **10 minutes** to prevent indefinite usage: - -1. When app launches, a background daemon is spawned -2. At **8 minutes**: Warning notification "Session will end in 2 minutes" -3. At **10 minutes**: App is closed with SIGTERM, then SIGKILL if needed -4. State file `~/.local/state/compulsive-block/.running` tracks PID and start time - -**Configuration variables** (in script): - -```bash -AUTO_CLOSE_TIMEOUT_MINUTES=10 # Total session length -AUTO_CLOSE_WARNING_MINUTES=2 # Warning before close -``` - -## Adding a New App - -1. Add to `APPS` associative array: - -```bash -declare -A APPS=( - # ... existing apps ... - ["newapp"]="/usr/bin/newapp" -) -``` - -2. Add to `REAL_BINARIES`: - -```bash -declare -A REAL_BINARIES=( - # ... existing apps ... - ["newapp"]="/opt/newapp/actual-binary" -) -``` - -3. Add to pacman hook targets (if installed via pacman): - -```ini -Target = newapp -``` - -4. Reinstall: - -```bash -sudo ./block_compulsive_opening.sh install -``` - -## Debugging - -### Check if wrapper is installed - -```bash -cat /usr/bin/discord -# Should show wrapper script, not binary - -ls -la /usr/bin/discord.orig -# Should exist (or check for SYMLINK: content) -``` - -### Check current state - -```bash -./block_compulsive_opening.sh status -# Shows: which apps are wrapped, last open times, current hour -``` - -### Test manually - -```bash -# Simulate wrapper call -/usr/local/bin/block-compulsive-opening.sh wrapper discord -``` - -### View logs - -```bash -tail -f ~/.local/state/compulsive-block/compulsive-block.log -``` - -## Notification Behavior - -When blocked, shows desktop notification: - -- Title: "🚫 discord Blocked" -- Message: "Already opened this hour. Wait until the next hour." -- Urgency: critical -- Timeout: 5000ms - -Uses `notify-send` (falls back silently if not available). - -## DO NOT - -1. ❌ Delete `.orig` files (cannot restore original binaries) -2. ❌ Manually edit wrapper scripts at `/usr/bin/` (will be overwritten) -3. ❌ Assume app is "blocked" once notification shows (it ran, just not again) -4. ❌ Remove pacman hook without understanding auto-rewrap won't work diff --git a/linux_configuration/periodic_background/digital_wellbeing/README_MIDNIGHT_SHUTDOWN_LLM.md b/linux_configuration/periodic_background/digital_wellbeing/README_MIDNIGHT_SHUTDOWN_LLM.md deleted file mode 100644 index 181e47e6..00000000 --- a/linux_configuration/periodic_background/digital_wellbeing/README_MIDNIGHT_SHUTDOWN_LLM.md +++ /dev/null @@ -1,162 +0,0 @@ -# Midnight Shutdown System - LLM Reference Guide - -> **For AI assistants**: This document explains the automatic shutdown system so you can make correct modifications. - -## System Purpose - -Automatically shut down the PC during configured time windows to enforce healthy sleep schedules: - -- **Monday-Wednesday**: Shutdown at 24:00 (midnight) -- **Thursday-Sunday**: Shutdown at 24:00 (midnight) -- **Morning**: Safe time starts at 00:00 (effectively no morning block) - -The times above are defaults; actual values in `/etc/shutdown-schedule.conf`. - -## Architecture Overview - -``` -┌─────────────────────────────────────────────────────────────────────┐ -│ SHUTDOWN SYSTEM LAYERS │ -├─────────────────────────────────────────────────────────────────────┤ -│ │ -│ Layer 1: Systemd Timer │ -│ ───────────────────── │ -│ day-specific-shutdown.timer fires every minute │ -│ day-specific-shutdown.service runs the check script │ -│ │ -│ Layer 2: Check Script │ -│ ──────────────────── │ -│ /usr/local/bin/day-specific-shutdown-check.sh │ -│ Reads config, checks current time, initiates shutdown if in window │ -│ │ -│ Layer 3: Config Protection │ -│ ──────────────────────── │ -│ /etc/shutdown-schedule.conf has chattr +i │ -│ Canonical copy at /usr/local/share/locked-shutdown-schedule.conf │ -│ Path watcher auto-restores if tampered │ -│ │ -│ Layer 4: Timer Monitor │ -│ ───────────────────── │ -│ shutdown-timer-monitor.service watches timer status │ -│ Re-enables timer if user tries to disable it │ -│ │ -│ Layer 5: Script Protection │ -│ ──────────────────────── │ -│ Setup script blocks making schedule MORE LENIENT │ -│ Can only make it STRICTER without the unlock script │ -│ │ -└─────────────────────────────────────────────────────────────────────┘ -``` - -## File Locations - -| File | Purpose | Protection | -| ----------------------------------------------------- | ------------------- | ----------------------- | -| `/etc/shutdown-schedule.conf` | Runtime config | chattr +i, path watcher | -| `/usr/local/share/locked-shutdown-schedule.conf` | Canonical copy | chattr +i | -| `/usr/local/bin/day-specific-shutdown-check.sh` | Shutdown logic | None | -| `/usr/local/bin/day-specific-shutdown-manager.sh` | Status/management | None | -| `/usr/local/bin/shutdown-timer-monitor.sh` | Timer re-enabler | None | -| `/usr/local/sbin/enforce-shutdown-schedule.sh` | Config restoration | None | -| `/usr/local/sbin/unlock-shutdown-schedule` | Delayed config edit | None | -| `/etc/systemd/system/day-specific-shutdown.timer` | Timer unit | systemd | -| `/etc/systemd/system/day-specific-shutdown.service` | Service unit | systemd | -| `/etc/systemd/system/shutdown-schedule-guard.path` | Config watcher | systemd | -| `/etc/systemd/system/shutdown-schedule-guard.service` | Enforcement | systemd | -| `/etc/systemd/system/shutdown-timer-monitor.service` | Timer guardian | systemd | -| `/var/log/shutdown-schedule-guard.log` | Tampering log | None | - -## Config File Format - -```bash -# /etc/shutdown-schedule.conf - -# Shutdown hour for Monday-Wednesday (24-hour format) -MON_WED_HOUR=21 - -# Shutdown hour for Thursday-Sunday (24-hour format) -THU_SUN_HOUR=22 - -# Morning end hour (shutdown window ends at this hour) -MORNING_END_HOUR=5 -``` - -**Interpretation**: - -- Mon-Wed: Shutdown if current hour >= 21 OR current hour < 5 -- Thu-Sun: Shutdown if current hour >= 22 OR current hour < 5 - -## Schedule Protection Logic - -The setup script (`setup_midnight_shutdown.sh`) has constants at the top: - -```bash -SCHEDULE_MON_WED_HOUR=24 -SCHEDULE_THU_SUN_HOUR=24 -SCHEDULE_MORNING_END_HOUR=0 -``` - -When re-run, it compares these to the canonical config: - -| Change Type | Action | -| -------------------------- | ------------------------------------ | -| Making shutdown EARLIER | ✅ Allowed without unlock | -| Making shutdown LATER | ❌ Blocked, requires unlock | -| Making morning end EARLIER | ❌ Always blocked | -| Making morning end LATER | ✅ Allowed (extends shutdown window) | - -Example blocked attempt: - -``` -╔══════════════════════════════════════════════════════════════════╗ -║ ❌ SCHEDULE MODIFICATION BLOCKED - CHEATING DETECTED! ❌ ║ -╚══════════════════════════════════════════════════════════════════╝ - -You modified the script to make the shutdown schedule MORE LENIENT: - • Mon-Wed shutdown: 21:00 → 23:00 (later) - -Nice try! But this is exactly the kind of late-night bargaining -that this protection is designed to prevent. 😉 -``` - -## Unlock Script Behavior - -`/usr/local/sbin/unlock-shutdown-schedule`: - -1. Stops `shutdown-schedule-guard.path` -2. Removes chattr from both config files -3. Opens editor on temp copy -4. Checks what changed: - - **Stricter (earlier)**: No delay, applies immediately - - **Lenient (later)**: 45-second countdown, then applies - - **Lower morning end**: **ALWAYS BLOCKED** (cannot shorten window) -5. Updates both config and canonical -6. Re-applies chattr +i -7. Restarts path watcher - -## Integration Points - -### i3blocks Countdown - -`i3blocks/shutdown_countdown.sh` reads the config to show time remaining: - -```bash -source /etc/shutdown-schedule.conf -# Calculates and displays "Shutdown in X:XX" -``` - -### Screen Locker - -`screen_lock.py` can adjust shutdown time: - -- **Sick day**: Moves shutdown 1.5 hours EARLIER (penalty) -- **Workout completed**: Moves shutdown 1.5 hours LATER (reward) - -Uses `adjust_shutdown_schedule.sh` helper script. - -## More detail - -Split out to stay under the 250-line cap. - -- [Systemd units, check-script logic and common tasks](llm-notes/units-and-tasks.md) -- [Known vulnerabilities, troubleshooting and hard stops](llm-notes/vulns-and-troubleshooting.md) diff --git a/linux_configuration/periodic_background/digital_wellbeing/block_compulsive_opening.sh b/linux_configuration/periodic_background/digital_wellbeing/block_compulsive_opening.sh deleted file mode 100755 index 5fe9ff00..00000000 --- a/linux_configuration/periodic_background/digital_wellbeing/block_compulsive_opening.sh +++ /dev/null @@ -1,182 +0,0 @@ -#!/bin/bash -# Block Compulsive Opening Script -# Limits messaging apps (Beeper, Signal, Discord) to one launch per hour -# -# Each app can only be opened once per hour. If already opened this hour, -# subsequent launch attempts are blocked with a notification. -# -# Installation moves real binaries to *.real and symlinks to wrapper scripts. - -set -euo pipefail - -# The phases live in four libs. They are found in TWO layouts, because this -# script runs from both: -# -# repo: /lib/cco_*.sh -# deployed: /usr/local/bin/cco_*.sh (flat, beside the entry script) -# -# install_all copies the libs flat next to the installed entry script rather -# than creating /usr/local/bin/lib, matching how pacman_lock_lib.sh and -# heavy_job_lock.sh are already deployed there. -# -# readlink -f because /usr/bin/ wrappers exec this through its absolute -# path and the installed copy may itself be reached via a symlink. -# -# This fails CLOSED: a missing lib aborts. The pacman fail-open pattern is -# wrong here — a half-loaded blocker that silently stops blocking is the exact -# failure this tool exists to prevent, and unlike pacman you do not need this -# script to repair it. -_CCO_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" -if [[ -d "$_CCO_DIR/lib" ]]; then - _CCO_LIB_DIR="$_CCO_DIR/lib" -else - _CCO_LIB_DIR="$_CCO_DIR" -fi - -for _cco_lib in cco_state cco_wrapper cco_install cco_report; do - if [[ ! -r "$_CCO_LIB_DIR/${_cco_lib}.sh" ]]; then - echo "Error: missing library ${_cco_lib}.sh in $_CCO_LIB_DIR" >&2 - echo "The installation is incomplete; re-run: sudo $0 install" >&2 - exit 1 - fi -done -unset _cco_lib - -# shellcheck source=lib/cco_state.sh -. "$_CCO_LIB_DIR/cco_state.sh" -# shellcheck source=lib/cco_wrapper.sh -. "$_CCO_LIB_DIR/cco_wrapper.sh" -# shellcheck source=lib/cco_install.sh -. "$_CCO_LIB_DIR/cco_install.sh" -# shellcheck source=lib/cco_report.sh -. "$_CCO_LIB_DIR/cco_report.sh" - -# Send desktop notification (inlined from common.sh to avoid dependency issues -# when script is installed to /usr/local/bin) -notify() { - local title="$1" - local message="$2" - local urgency="${3:-normal}" - local timeout="${4:-5000}" - - if command -v notify-send &>/dev/null; then - notify-send -u "$urgency" -t "$timeout" "$title" "$message" 2>/dev/null || true - fi -} - -# Configuration -STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/compulsive-block" -LOG_FILE="$STATE_DIR/compulsive-block.log" - -# Auto-close timeout in minutes (apps forcefully closed after this) -AUTO_CLOSE_TIMEOUT_MINUTES=10 -# Warning before auto-close (in minutes before timeout) -AUTO_CLOSE_WARNING_MINUTES=2 - -# Per-app timeout overrides (apps not listed use AUTO_CLOSE_TIMEOUT_MINUTES) -declare -A APP_TIMEOUT_MINUTES=( - ["beeper"]=20 - ["signal-desktop"]=20 -) - -# Apps to limit (name -> binary path) -# These are the primary wrapper locations (what the user calls) -declare -A APPS=( - ["beeper"]="/usr/bin/beeper" - ["signal-desktop"]="/usr/bin/signal-desktop" - ["discord"]="/usr/bin/discord" -) - -# The wrapper runs as the user ($SUDO_USER unset); the installer runs as root -# under sudo ($SUDO_USER set) — resolve the same home in both cases. -_cco_user="${SUDO_USER:-${USER:-$(id -un)}}" -_cco_home="$(getent passwd "$_cco_user" 2>/dev/null | cut -d: -f6)" -[[ -n $_cco_home ]] || _cco_home="/home/$_cco_user" - -# Actual executable paths (the real binaries to exec after wrapper check) -# These are where the real code lives -# -# discord: the `discord` package ships ONLY /usr/bin/discord — a bootstrap shell -# script that downloads the app into ~/.config/discord/ and execs -# ~/.config/discord/Discord (a symlink to the current app-/Discord that -# Discord's own updater maintains). /opt/discord/Discord belongs to a DIFFERENT -# discord package and does not exist here, so the old value made install_wrapper's -# `[[ -x $real_binary ]]` gate fail forever ("discord real binary not found") and -# discord was never limited. We exec the preserved LAUNCHER (.orig) rather than -# the app directly, so Discord's bootstrap/self-update still runs — pointing -# straight at ~/.config/discord/Discord would break launching whenever the -# updater moved that symlink. -declare -A REAL_BINARIES=( - ["beeper"]="/opt/beeper/beepertexts" - ["signal-desktop"]="/usr/lib/signal-desktop/signal-desktop" - ["discord"]="/usr/bin/discord.orig" -) - -# Pattern for detecting a RUNNING instance (pgrep -f). Defaults to the exec -# target, which is right when that target is the app itself. It is wrong when the -# exec target is a launcher that exec()s away: /usr/bin/discord.orig replaces -# itself with ~/.config/discord//Discord, so nothing would ever match -# ".orig" and the running-state tracking would look stale immediately. -declare -A PROCESS_MATCH=( - ["discord"]="$_cco_home/.config/discord/" -) - -# Main entry point -main() { - case "${1:-help}" in - install) - if [[ $EUID -ne 0 ]]; then - echo "Error: install requires root privileges" - echo "Run: sudo $0 install" - exit 1 - fi - install_all - ;; - uninstall) - if [[ $EUID -ne 0 ]]; then - echo "Error: uninstall requires root privileges" - echo "Run: sudo $0 uninstall" - exit 1 - fi - uninstall_all - ;; - status) - show_status - ;; - reset) - if [[ -z ${2:-} ]]; then - echo "Error: specify app to reset" - echo "Apps: ${!APPS[*]}" - exit 1 - fi - reset_app "$2" - ;; - reset-all) - reset_all - ;; - rewrap-quiet) - # Called by pacman hook - quietly re-wrap apps after package updates - if [[ $EUID -ne 0 ]]; then - exit 1 - fi - rewrap_quiet - ;; - wrapper) - if [[ -z ${2:-} ]]; then - echo "Error: wrapper requires app name" - exit 1 - fi - wrapper_main "${@:2}" - ;; - help | -h | --help) - show_usage - ;; - *) - echo "Unknown command: $1" - show_usage - exit 1 - ;; - esac -} - -main "$@" diff --git a/linux_configuration/periodic_background/digital_wellbeing/heavy_job_lock.sh b/linux_configuration/periodic_background/digital_wellbeing/heavy_job_lock.sh deleted file mode 100755 index 1c98fc2c..00000000 --- a/linux_configuration/periodic_background/digital_wellbeing/heavy_job_lock.sh +++ /dev/null @@ -1,232 +0,0 @@ -#!/bin/bash - -# ============================================================================ -# heavy_job_lock.sh - one heavy job at a time on this machine -# -# This box has 7.6 GB. On 2026-08-03 a Gradle/Flutter APK build, a full -# `pytest --cov` run and Chromium overlapped; an earlier overlap the same week -# got a whole session OOM-killed (exit 137/143). The rule "one heavy job at a -# time" was written down in ~/.claude/memories/mistakes.md - but a rule that -# only works when a human remembers it is deployment hygiene, not a fix. This -# makes the overlap impossible instead. -# -# Usage as a CLI: -# heavy_job_lock.sh --name gradle -- flutter build apk --debug -# heavy_job_lock.sh --name pytest --fail-fast -- pytest --cov -# -# Usage as a library: -# source /path/to/heavy_job_lock.sh -# with_heavy_lock gradle -- ./gradlew assembleDebug -# -# Setup (once, as root - /run is tmpfs so the lock needs recreating on boot): -# sudo heavy_job_lock.sh --install -# -# Design notes: -# - ONE system-wide lock shared between root and the user. pacman runs as -# root while builds run as the user, so a per-user /run/user/$UID lock -# would give them separate locks and no mutual exclusion at all. -# - The lock file is mode 0666 (created by root via tmpfiles.d) precisely so -# an unprivileged build can take the same lock a root pacman takes. -# - FAIL OPEN, always. A lock this central must never be able to brick -# package management or builds: if the lock file cannot be created, or the -# wait times out, we warn and run the command anyway. Blocking work forever -# is a worse failure than the memory contention we are avoiding. -# ============================================================================ - -HEAVY_JOB_LOCK_FILE="${HEAVY_JOB_LOCK_FILE:-/run/heavy-job.lock}" -# Long enough for a real Gradle build to finish ahead of us, short enough that -# a wedged holder cannot stall a machine indefinitely. -HEAVY_JOB_LOCK_TIMEOUT="${HEAVY_JOB_LOCK_TIMEOUT:-1800}" -HEAVY_JOB_TMPFILES_CONF="/etc/tmpfiles.d/heavy-job-lock.conf" - -_hjl_warn() { printf '[heavy-job-lock] %s\n' "$*" >&2; } - -# Who holds it right now? Best-effort: the holder writes " " -# after acquiring, so a refusal can name the culprit instead of just failing. -_hjl_holder() { - local holder="" - [[ -r $HEAVY_JOB_LOCK_FILE ]] && read -r holder <"$HEAVY_JOB_LOCK_FILE" 2>/dev/null - printf '%s' "${holder:-unknown}" -} - -# Create the lock file world-writable. Only meaningful as root; unprivileged -# callers just discover they cannot and fail open. -_hjl_ensure_lock_file() { - [[ -e $HEAVY_JOB_LOCK_FILE ]] && return 0 - if [[ $EUID -eq 0 ]]; then - install -m 0666 /dev/null "$HEAVY_JOB_LOCK_FILE" 2>/dev/null && return 0 - else - # Braces around the redirect: a failing redirection is reported by the - # shell itself, before the command runs, so `cmd 2>/dev/null` does not - # suppress it. The group does. - { : >"$HEAVY_JOB_LOCK_FILE"; } 2>/dev/null && - chmod 0666 "$HEAVY_JOB_LOCK_FILE" 2>/dev/null && return 0 - fi - return 1 -} - -# with_heavy_lock [--fail-fast] -- -with_heavy_lock() { - local name="$1" - shift - local fail_fast=0 - - while [[ $# -gt 0 ]]; do - case "$1" in - --fail-fast) - fail_fast=1 - shift - ;; - --) - shift - break - ;; - *) break ;; - esac - done - - if [[ $# -eq 0 ]]; then - _hjl_warn "with_heavy_lock: no command given" - return 2 - fi - - if ! _hjl_ensure_lock_file; then - _hjl_warn "cannot create $HEAVY_JOB_LOCK_FILE - running '$name' UNSERIALISED" - _hjl_warn "run 'sudo $0 --install' once to enable serialisation" - "$@" - return $? - fi - - local lock_fd - # `<>` (read-write), NOT `>`. Redirecting with `>` truncates the file at - # open time, so a second caller wiped the holder's " " - # line before it could read it — every refusal said "held by unknown". - exec {lock_fd}<>"$HEAVY_JOB_LOCK_FILE" || { - _hjl_warn "cannot open lock - running '$name' unserialised" - "$@" - return $? - } - - local acquired=0 - if ((fail_fast == 1)); then - if flock --nonblock "$lock_fd"; then - acquired=1 - else - _hjl_warn "refusing to start '$name': held by $(_hjl_holder)" - exec {lock_fd}>&- - return 75 # EX_TEMPFAIL - fi - else - local holder - holder="$(_hjl_holder)" - if ! flock --nonblock "$lock_fd"; then - _hjl_warn "waiting for '$holder' to finish before starting '$name' (timeout ${HEAVY_JOB_LOCK_TIMEOUT}s)" - fi - if flock --timeout "$HEAVY_JOB_LOCK_TIMEOUT" "$lock_fd"; then - acquired=1 - else - # Fail open: see the header. A stuck holder must not permanently - # block builds or package management. - _hjl_warn "timed out after ${HEAVY_JOB_LOCK_TIMEOUT}s waiting for $(_hjl_holder)" - _hjl_warn "proceeding UNSERIALISED with '$name'" - fi - fi - - if ((acquired == 1)); then - printf '%s %s %s\n' "$name" "$$" "$(date -Is 2>/dev/null || echo unknown)" \ - >"$HEAVY_JOB_LOCK_FILE" 2>/dev/null || true - fi - - local rc=0 - "$@" || rc=$? - - # Closing the fd releases the lock; do it explicitly so the holder line is - # not left implying we still hold it. - exec {lock_fd}>&- - return "$rc" -} - -# ---------------------------------------------------------------------------- -# CLI (skipped when this file is sourced as a library) -# ---------------------------------------------------------------------------- -_hjl_install() { - if [[ $EUID -ne 0 ]]; then - _hjl_warn "--install must run as root" - return 1 - fi - # /run is tmpfs: without a tmpfiles.d entry the lock vanishes every boot and - # the first unprivileged caller silently falls back to unserialised. - printf 'f %s 0666 root root -\n' "$HEAVY_JOB_LOCK_FILE" >"$HEAVY_JOB_TMPFILES_CONF" - systemd-tmpfiles --create "$HEAVY_JOB_TMPFILES_CONF" 2>/dev/null || true - _hjl_ensure_lock_file || { - _hjl_warn "failed to create $HEAVY_JOB_LOCK_FILE" - return 1 - } - printf 'installed %s and %s\n' "$HEAVY_JOB_TMPFILES_CONF" "$HEAVY_JOB_LOCK_FILE" -} - -_hjl_usage() { - cat < [--fail-fast] -- - $(basename "$0") --install # once, as root - $(basename "$0") --status - -Serialises heavy jobs (builds, coverage runs, pacman transactions) so only one -runs at a time on this memory-constrained machine. Always fails open. - -Options: - --name