Re-point paths for the ~ reorganisation (home-tidy)

Also bumps the 16 meta/requirements pins the freshness gate flagged
(anthropic 1.5.0, numpy 2.5.3, pytest-randomly 5, flake8-bugbear 26.9.9,
ruff 0.16.7, pyright 1.1.414, selenium 4.49.0, hypothesis 6.168.0, lxml
6.1.3, pipdeptree 4.2.5, the two types-* stubs) and Flutter 3.47.3 in
meta/.fvmrc. One test still expected the pre-move ~/mcp-servers path.

The pre-commit hook fed .zsh files to shfmt and shellcheck, neither of
which parses zsh (SC2148/SC1071), so no zsh file could be committed since
the hook was tightened on 2026-08-22; those now get `zsh -n` instead. The
hook's own shfmt pass also staged its reformat of 8 shell files.

CI_GREEN_SKIP because main is red on exactly the dependency-freshness
finding this commit fixes, which is the documented use of that flag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwtowrowDXx6ccmWVPkCaF
This commit is contained in:
Krzysztof Rudnicki 2026-09-11 20:50:19 +02:00
parent 0cea82cab5
commit f15f00f3fe
106 changed files with 765 additions and 652 deletions

View File

@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
# The md-naming / file-length hooks are thin shims that exec the shared # The md-naming / file-length hooks are thin shims that exec the shared
# gate out of the utils repo. A runner has no ~/utils, so without this # gate out of the utils repo. A runner has no ~/src/utils, so without this
# checkout they fail with "shared gate not found" -- which is exactly # checkout they fail with "shared gate not found" -- which is exactly
# how this workflow went red on 2026-08-24. # how this workflow went red on 2026-08-24.
UTILS_ROOT: ${{ github.workspace }}/.utils UTILS_ROOT: ${{ github.workspace }}/.utils

View File

@ -585,7 +585,7 @@ repos:
# Four markdown namespaces (README / CLAUDE* / DOCS* / TODO*), plus the # Four markdown namespaces (README / CLAUDE* / DOCS* / TODO*), plus the
# rule that a TODO carries "REMOVE ME AFTER FINISH" and nothing else may. # rule that a TODO carries "REMOVE ME AFTER FINISH" and nothing else may.
# Shared gate in ~/utils owns the namespaces and the exemption list, so # Shared gate in ~/src/utils owns the namespaces and the exemption list, so
# one repo's idea of a valid name cannot drift from every other repo's. # one repo's idea of a valid name cannot drift from every other repo's.
- id: md-naming - id: md-naming
name: markdown naming convention name: markdown naming convention

View File

@ -32,19 +32,19 @@ Extracted to their own repos: [`steam-backlog-enforcer`](https://github.com/kuhy
[`dufs-cloud`](https://github.com/kuhyx/dufs-cloud), [`dufs-cloud`](https://github.com/kuhyx/dufs-cloud),
[`build-your-x`](https://github.com/kuhyx/build-your-x) (lives at `~/build_your_x`), [`build-your-x`](https://github.com/kuhyx/build-your-x) (lives at `~/build_your_x`),
[`reverse-survivors`](https://github.com/kuhyx/reverse-survivors), [`reverse-survivors`](https://github.com/kuhyx/reverse-survivors),
[`kcd2-dice-solver`](https://github.com/kuhyx/kcd2-dice-solver) (lives at `~/kcd2-dice-solver`), [`kcd2-dice-solver`](https://github.com/kuhyx/kcd2-dice-solver) (lives at `~/src/kcd2-dice-solver`),
[`focus-owner`](https://github.com/kuhyx/focus-owner), [`focus-owner`](https://github.com/kuhyx/focus-owner),
[`mtk-root`](https://github.com/kuhyx/mtk-root), [`mtk-root`](https://github.com/kuhyx/mtk-root),
[`system-maintenance`](https://github.com/kuhyx/system-maintenance) (lives at [`system-maintenance`](https://github.com/kuhyx/system-maintenance) (lives at
`~/system-maintenance`), `~/src/system-maintenance`),
[`android-guardian`](https://github.com/kuhyx/android-guardian) (lives at [`android-guardian`](https://github.com/kuhyx/android-guardian) (lives at
`~/android-guardian`), `~/android-guardian`),
[`phone-focus-mode`](https://github.com/kuhyx/phone-focus-mode) (with [`phone-focus-mode`](https://github.com/kuhyx/phone-focus-mode) (with
`python_pkg/focus_policy`), `python_pkg/focus_policy`),
[`hosts-blocker`](https://github.com/kuhyx/hosts-blocker) (lives at [`hosts-blocker`](https://github.com/kuhyx/hosts-blocker) (lives at
`~/hosts-blocker`), `~/src/hosts-blocker`),
[`digital-wellbeing`](https://github.com/kuhyx/digital-wellbeing) (lives at [`digital-wellbeing`](https://github.com/kuhyx/digital-wellbeing) (lives at
`~/digital-wellbeing`; carries the six suites that used to sit in `~/src/digital-wellbeing`; carries the six suites that used to sit in
`linux_configuration/tests/`). Monorepo callers resolve both via `linux_configuration/tests/`). Monorepo callers resolve both via
`linux_configuration/lib/extracted_repos.sh`, which reads the invoking user's `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 home from `SUDO_USER` — never `$HOME`, which is `/root` under sudo and under

View File

@ -4,10 +4,10 @@ Paste everything below into a **fresh** Claude session (`/clear` first).
--- ---
`~/testsAndMisc/linux_configuration/install_core_system.sh` is the documented `~/src/testsAndMisc/linux_configuration/install_core_system.sh` is the documented
way to set this machine up from scratch. It does not work. Three defects were way to set this machine up from scratch. It does not work. Three defects were
found on 2026-08-22 by running the real installer in a disposable Arch VM found on 2026-08-22 by running the real installer in a disposable Arch VM
(`~/utils/vmbox`) rather than by reading it — 2 of its 7 modules cannot install (`~/src/utils/vmbox`) rather than by reading it — 2 of its 7 modules cannot install
at all, and a third installs something that silently does nothing. at all, and a third installs something that silently does nothing.
**Do not trust this file's line numbers without re-checking them.** They were **Do not trust this file's line numbers without re-checking them.** They were
@ -37,14 +37,14 @@ machine already exists in the repos, but the documented install path never runs
Both packages were extracted into their own repos. Verified on disk 2026-08-22: Both packages were extracted into their own repos. Verified on disk 2026-08-22:
- `~/screen-locker/install_systemd.sh` (5637 b, executable) - `~/src/screen-locker/install_systemd.sh` (5637 b, executable)
- `~/steam-backlog-enforcer/install.sh` (3248 b, executable) - `~/src/steam-backlog-enforcer/install.sh` (3248 b, executable)
"Workout screen locker" is a **CORE** module, so a fresh machine fails on the "Workout screen locker" is a **CORE** module, so a fresh machine fails on the
first thing the installer does. first thing the installer does.
Note this makes the installer **cross-repo**: `~/testsAndMisc` no longer owns Note this makes the installer **cross-repo**: `~/src/testsAndMisc` no longer owns
that code. That is design question (b) below — do not just hardcode `~/screen-locker` that code. That is design question (b) below — do not just hardcode `~/src/screen-locker`
and call it done without putting the question to kuhy. and call it done without putting the question to kuhy.
## Defect 2 — guard-lib is required but never installed ## Defect 2 — guard-lib is required but never installed
@ -52,14 +52,14 @@ and call it done without putting the question to kuhy.
`scripts/periodic_background/digital_wellbeing/setup_midnight_shutdown.sh` `scripts/periodic_background/digital_wellbeing/setup_midnight_shutdown.sh`
depends on `guardctl` and dies with `guardctl not found on PATH`. Nothing in depends on `guardctl` and dies with `guardctl not found on PATH`. Nothing in
`install_core_system.sh` installs guard-lib, which lives at `install_core_system.sh` installs guard-lib, which lives at
`~/utils/guard-lib/install.sh` — a **third** sibling repo. `~/src/utils/guard-lib/install.sh` — a **third** sibling repo.
So the CORE module "Midnight shutdown timer" always fails on a fresh machine. So the CORE module "Midnight shutdown timer" always fails on a fresh machine.
## Defect 3 — the hosts file-guards are installed only by a one-shot script ## Defect 3 — the hosts file-guards are installed only by a one-shot script
`install.sh` in the extracted `hosts-blocker` repo `install.sh` in the extracted `hosts-blocker` repo
(github.com/kuhyx/hosts-blocker, checked out at `~/hosts-blocker`; it was (github.com/kuhyx/hosts-blocker, checked out at `~/src/hosts-blocker`; it was
`scripts/periodic_background/hosts/install.sh` when this was written) `scripts/periodic_background/hosts/install.sh` when this was written)
never installs the never installs the
hosts/nsswitch/resolved file-guards. Its own comment block around **line 109** hosts/nsswitch/resolved file-guards. Its own comment block around **line 109**
@ -90,10 +90,10 @@ diff; the second stops `single_use/fixes/` from being load-bearing, which is
arguably what "single use" is supposed to mean. arguably what "single use" is supposed to mean.
**(b) How should a testsAndMisc installer reach code in sibling repos?** **(b) How should a testsAndMisc installer reach code in sibling repos?**
`~/screen-locker`, `~/steam-backlog-enforcer` and `~/utils/guard-lib` are `~/src/screen-locker`, `~/src/steam-backlog-enforcer` and `~/src/utils/guard-lib` are
separate repos now. Options, none obviously right: separate repos now. Options, none obviously right:
1. **Expect-adjacent** — check for `~/screen-locker` etc., fail with a clear 1. **Expect-adjacent** — check for `~/src/screen-locker` etc., fail with a clear
message naming the repo to clone. Simple; the installer stops being message naming the repo to clone. Simple; the installer stops being
self-contained. self-contained.
2. **Clone if missing** — the installer clones the siblings it needs. Works on 2. **Clone if missing** — the installer clones the siblings it needs. Works on
@ -112,7 +112,7 @@ an actionable message rather than silently skipping a module.
The fix is done when, in a **fresh** vmbox guest: The fix is done when, in a **fresh** vmbox guest:
```bash ```bash
vm share ~/testsAndMisc && vm share ~/utils vm share ~/src/testsAndMisc && vm share ~/src/utils
vm new inst vm new inst
vm run inst 'git clone --no-hardlinks -q /mnt/hostrepo/testsAndMisc ~/tam' vm run inst 'git clone --no-hardlinks -q /mnt/hostrepo/testsAndMisc ~/tam'
# plus whatever cloning question (b)'s answer implies for the sibling repos # plus whatever cloning question (b)'s answer implies for the sibling repos
@ -134,16 +134,16 @@ vm run inst 'cd ~/tam && echo y | bash linux_configuration/install_core_system.s
- **Sandbox first, always.** This is a root installer that writes `/etc`, - **Sandbox first, always.** This is a root installer that writes `/etc`,
systemd units, pacman hooks and `chattr +i`. Never run it on the host to systemd units, pacman hooks and `chattr +i`. Never run it on the host to
test it. `~/utils/vmbox`: `vm new` / `vm run` / `vm reset`. A sandbox pass test it. `~/src/utils/vmbox`: `vm new` / `vm run` / `vm reset`. A sandbox pass
does **not** prove the host is fine — report it that way. does **not** prove the host is fine — report it that way.
- Installers prompt; `vm run` closes stdin, so pipe `echo y |`. - Installers prompt; `vm run` closes stdin, so pipe `echo y |`.
- vmbox is fast: `vm run` ~14s cold / ~3s warm, `vm reset` ~2s. Reuse one - vmbox is fast: `vm run` ~14s cold / ~3s warm, `vm reset` ~2s. Reuse one
sandbox, do not rebuild per command. sandbox, do not rebuild per command.
- `~/utils/vmbox/SESSION_RESULTS.md` has the full 2026-08-22 measurements. - `~/src/utils/vmbox/SESSION_RESULTS.md` has the full 2026-08-22 measurements.
- The 250-line file cap applies (`~/utils/file_length`). `install.sh` is at - The 250-line file cap applies (`~/src/utils/file_length`). `install.sh` is at
130 lines and the migration at 169 — merging them wholesale will breach it, 130 lines and the migration at 169 — merging them wholesale will breach it,
which is worth weighing in design question (a). which is worth weighing in design question (a).
- `~/testsAndMisc` requires a `docs/superpowers/evidence/*.json` artifact for - `~/src/testsAndMisc` requires a `docs/superpowers/evidence/*.json` artifact for
any code change (pre-commit gate `ai-evidence-contract`); copy any code change (pre-commit gate `ai-evidence-contract`); copy
`docs/superpowers/evidence/template.json`. `docs/superpowers/evidence/template.json`.

View File

@ -29,10 +29,10 @@ Verify any of it:
## What landed this session ## What landed this session
**`hosts-blocker`** — [github.com/kuhyx/hosts-blocker](https://github.com/kuhyx/hosts-blocker), **`hosts-blocker`** — [github.com/kuhyx/hosts-blocker](https://github.com/kuhyx/hosts-blocker),
cloned at `~/hosts-blocker`. 52 commits. cloned at `~/src/hosts-blocker`. 52 commits.
**`digital-wellbeing`** — [github.com/kuhyx/digital-wellbeing](https://github.com/kuhyx/digital-wellbeing), **`digital-wellbeing`** — [github.com/kuhyx/digital-wellbeing](https://github.com/kuhyx/digital-wellbeing),
cloned at `~/digital-wellbeing`. 89 commits, and it carries the six suites that cloned at `~/src/digital-wellbeing`. 89 commits, and it carries the six suites that
used to sit in `linux_configuration/tests/`. used to sit in `linux_configuration/tests/`.
**The cap enforces.** `--warn` is gone from the `directory-depth-cap` hook, and **The cap enforces.** `--warn` is gone from the `directory-depth-cap` hook, and
@ -76,7 +76,7 @@ copies were the only ones on the machine.
- **`syncyomi-guard` is red** since 00:00 on 2026-08-23, working as designed: - **`syncyomi-guard` is red** since 00:00 on 2026-08-23, working as designed:
the library collapsed from 2185 manga to 8. See the the library collapsed from 2185 manga to 8. See the
`syncyomi-restore-silent-partial` memory; do NOT checkpoint the WAL before `syncyomi-restore-silent-partial` memory; do NOT checkpoint the WAL before
recovering. Newest snapshot: `~/syncyomi/snapshots/`, 2026-08-15. recovering. Newest snapshot: `~/services/syncyomi/snapshots/`, 2026-08-15.
## Traps worth keeping ## Traps worth keeping

View File

@ -9,7 +9,7 @@ not measured from a rendered page.
--- ---
## kcd2_dice_solver (`~/testsAndMisc/kcd2_dice_solver`, Vite/TS) ## kcd2_dice_solver (`~/src/testsAndMisc/kcd2_dice_solver`, Vite/TS)
Design tokens live in `src/index.css` (422 lines), a single `:root` block of Design tokens live in `src/index.css` (422 lines), a single `:root` block of
CSS custom properties consumed by all 5 component files. This is the correct CSS custom properties consumed by all 5 component files. This is the correct
@ -60,7 +60,7 @@ system (see Rule 4).
--- ---
## bucket_catch frontend (`~/testsAndMisc/bucket_catch/packages/frontend`, Vite/TS) ## bucket_catch frontend (`~/src/testsAndMisc/bucket_catch/packages/frontend`, Vite/TS)
**Confirmed: no shared design tokens exist.** `src/index.css` is a 12-line **Confirmed: no shared design tokens exist.** `src/index.css` is a 12-line
reset only (box-sizing + html/body/#root margin/font/background/color) — no reset only (box-sizing + html/body/#root margin/font/background/color) — no

View File

@ -27,7 +27,7 @@ Backed by repeated, concrete evidence across multiple sessions/repos.
### MCP Inspector (mcp, typescript, 10.6k★) ### MCP Inspector (mcp, typescript, 10.6k★)
You author and maintain **7+ MCP servers** (`i3wm-mcp`, `yay-mcp`, `aseprite-mcp`, `reaper-mcp`, `opengameart-mcp`, You author and maintain **7+ MCP servers** (`i3wm-mcp`, `yay-mcp`, `aseprite-mcp`, `reaper-mcp`, `opengameart-mcp`,
`freesound-mcp`, plus 23 more under `~/mcp-servers/servers/`) per `project-mcp-server-fleet.md` and the deployment `freesound-mcp`, plus 23 more under `~/src/mcp-servers/servers/`) per `project-mcp-server-fleet.md` and the deployment
skill `project-mcp-per-repo-pattern.md`. Nothing in your memory or tooling survey shows a dedicated tool/resource/prompt skill `project-mcp-per-repo-pattern.md`. Nothing in your memory or tooling survey shows a dedicated tool/resource/prompt
debugger for the servers you _build_ — you've been testing them indirectly through Claude Code sessions themselves. debugger for the servers you _build_ — you've been testing them indirectly through Claude Code sessions themselves.
MCP Inspector is a GUI built exactly for this gap: exercising tools/resources/prompts in isolation before wiring a new MCP Inspector is a GUI built exactly for this gap: exercising tools/resources/prompts in isolation before wiring a new
@ -44,7 +44,7 @@ which your own hook (`track_reads_pretool.sh`, since disabled) was already tryin
### Docker MCP Gateway (mcp, sandbox, cli, 1.5k★) ### Docker MCP Gateway (mcp, sandbox, cli, 1.5k★)
You run 23+ Dockerfiles for self-hosted MCP server containers (`~/mcp-servers/servers/*`) per the repo survey, on top You run 23+ Dockerfiles for self-hosted MCP server containers (`~/src/mcp-servers/servers/*`) per the repo survey, on top
of an already-Docker-heavy self-hosted stack (Gitea, SearXNG, Syncyomi, Joplin Server, all `docker-compose.yml`-based). of an already-Docker-heavy self-hosted stack (Gitea, SearXNG, Syncyomi, Joplin Server, all `docker-compose.yml`-based).
A container-aware MCP gateway is a direct fit for consolidating discovery/auth across a fleet this size, rather than A container-aware MCP gateway is a direct fit for consolidating discovery/auth across a fleet this size, rather than
each server being wired in ad hoc. Speculative on the _specific_ tool, high-confidence on the _need_ — you already each server being wired in ad hoc. Speculative on the _specific_ tool, high-confidence on the _need_ — you already
@ -153,7 +153,7 @@ demonstrated preference for deterministic scripts you can read, diff, and put un
### browser-use, Stagehand, Steel (standalone browser-agent harnesses) ### browser-use, Stagehand, Steel (standalone browser-agent harnesses)
Rejected as _redundant_, not irrelevant — you already have `claude-in-chrome` MCP tools wired in and actively used this Rejected as _redundant_, not irrelevant — you already have `claude-in-chrome` MCP tools wired in and actively used this
session, plus `servo-fetch` (Servo-engine fetch/crawl/screenshot MCP) under `~/mcp-servers/servers/`. Both solve the session, plus `servo-fetch` (Servo-engine fetch/crawl/screenshot MCP) under `~/src/mcp-servers/servers/`. Both solve the
"drive a real browser from an agent" problem you'd otherwise reach for these tools to solve. No session shows friction "drive a real browser from an agent" problem you'd otherwise reach for these tools to solve. No session shows friction
with the browser tooling you already have that a swap would fix. with the browser tooling you already have that a swap would fix.

View File

@ -75,7 +75,7 @@ touching anything.
one.** This is the highest-value artifact on the phone. one.** This is the highest-value artifact on the phone.
```bash ```bash
adb -s 23181JEGR08034 shell 'find /sdcard -iname "*.kdbx" 2>/dev/null' adb -s 23181JEGR08034 shell 'find /sdcard -iname "*.kdbx" 2>/dev/null'
adb -s 23181JEGR08034 pull <path> ~/phone-backup/ adb -s 23181JEGR08034 pull <path> ~/data/phone-backup/
``` ```
3. **Write down on paper**: mBank customer ID + password, Revolut passcode, 3. **Write down on paper**: mBank customer ID + password, Revolut passcode,
Signal PIN, KeePassDX master password, both Google passwords. Signal PIN, KeePassDX master password, both Google passwords.
@ -87,13 +87,13 @@ touching anything.
5. **Signal**: Settings → Backups → On-device backups. **Record the 30-digit 5. **Signal**: Settings → Backups → On-device backups. **Record the 30-digit
passphrase on paper** — it is not your Signal PIN. passphrase on paper** — it is not your Signal PIN.
```bash ```bash
adb -s 23181JEGR08034 pull /sdcard/Signal/Backups/ ~/phone-backup/signal/ adb -s 23181JEGR08034 pull /sdcard/Signal/Backups/ ~/data/phone-backup/signal/
``` ```
Registration Lock: forgetting the PIN locks you out for up to 7 days. Registration Lock: forgetting the PIN locks you out for up to 7 days.
6. **Photos and downloads**: 6. **Photos and downloads**:
```bash ```bash
adb -s 23181JEGR08034 pull /sdcard/DCIM/ ~/phone-backup/DCIM/ adb -s 23181JEGR08034 pull /sdcard/DCIM/ ~/data/phone-backup/DCIM/
adb -s 23181JEGR08034 pull /sdcard/Download/ ~/phone-backup/Download/ adb -s 23181JEGR08034 pull /sdcard/Download/ ~/data/phone-backup/Download/
``` ```
7. **mBank**: in the transactional service, note how to remove a paired device. 7. **mBank**: in the transactional service, note how to remove a paired device.
The old phone stays listed as active after a reset and must be detached. The old phone stays listed as active after a reset and must be detached.

View File

@ -36,7 +36,7 @@ To remove it entirely:
```bash ```bash
sudo systemctl disable --now stunnel-dot.service dot-cert-sync.timer sudo systemctl disable --now stunnel-dot.service dot-cert-sync.timer
sudo rm -f /etc/stunnel/dot-resolver.conf /etc/systemd/system/stunnel-dot.service sudo rm -f /etc/stunnel/dot-resolver.conf /etc/systemd/system/stunnel-dot.service
rm -f ~/gitea/sites/dns.caddy && docker exec gitea-caddy caddy reload \ rm -f ~/services/gitea/sites/dns.caddy && docker exec gitea-caddy caddy reload \
--config /etc/caddy/Caddyfile --adapter caddyfile --config /etc/caddy/Caddyfile --adapter caddyfile
``` ```

View File

@ -17,56 +17,56 @@ its timer next fires.
``` ```
## systemd units ## systemd units
/etc/systemd/system/dns-blocklist-refresh.service /etc/systemd/system/dns-blocklist-refresh.service
7:ExecStart=/home/kuhy/testsAndMisc/linux_configuration/features/setup_dns_blocker.sh refresh 7:ExecStart=/home/kuhy/src/testsAndMisc/linux_configuration/features/setup_dns_blocker.sh refresh
/etc/systemd/system/media-organizer.service /etc/systemd/system/media-organizer.service
10:ExecStart=/home/kuhy/testsAndMisc/linux_configuration/utils/organize_downloads.sh 10:ExecStart=/home/kuhy/src/testsAndMisc/linux_configuration/utils/organize_downloads.sh
/home/kuhy/.config/systemd/user/endurain-import.service /home/kuhy/.config/systemd/user/endurain-import.service
3:Documentation=file:///home/kuhy/testsAndMisc/endurain/README.md 3:Documentation=file:///home/kuhy/src/testsAndMisc/endurain/README.md
11:WorkingDirectory=/home/kuhy/testsAndMisc 11:WorkingDirectory=/home/kuhy/src/testsAndMisc
21:ExecStart=/usr/bin/bash -c 'ENDURAIN_API_KEY="$(cat /home/kuhy/testsAndMisc/endurain/.api_key)" /home/kuhy/testsAndMisc/.venv/bin/python -m python_pkg.endurain_import' 21:ExecStart=/usr/bin/bash -c 'ENDURAIN_API_KEY="$(cat /home/kuhy/src/testsAndMisc/endurain/.api_key)" /home/kuhy/src/testsAndMisc/.venv/bin/python -m python_pkg.endurain_import'
/home/kuhy/.config/systemd/user/morning-routine.service /home/kuhy/.config/systemd/user/morning-routine.service
13:Environment=PYTHONPATH=%h/testsAndMisc 13:Environment=PYTHONPATH=%h/src/testsAndMisc
16:WorkingDirectory=%h/testsAndMisc 16:WorkingDirectory=%h/src/testsAndMisc
/home/kuhy/.config/systemd/user/syncyomi-guard.service /home/kuhy/.config/systemd/user/syncyomi-guard.service
8:WorkingDirectory=/home/kuhy/testsAndMisc 8:WorkingDirectory=/home/kuhy/src/testsAndMisc
9:Environment=PYTHONPATH=/home/kuhy/testsAndMisc 9:Environment=PYTHONPATH=/home/kuhy/src/testsAndMisc
## /etc references ## /etc references
/etc/guard-lib/targets/nsswitch.conf /etc/guard-lib/targets/nsswitch.conf
4:PLUGIN="/home/kuhy/testsAndMisc/linux_configuration/periodic_background/hosts/guard/plugins/nsswitch-plugin.sh" 4:PLUGIN="/home/kuhy/src/testsAndMisc/linux_configuration/periodic_background/hosts/guard/plugins/nsswitch-plugin.sh"
/etc/guard-lib/targets/resolved.conf /etc/guard-lib/targets/resolved.conf
4:PLUGIN="/home/kuhy/testsAndMisc/linux_configuration/periodic_background/hosts/guard/plugins/resolved-plugin.sh" 4:PLUGIN="/home/kuhy/src/testsAndMisc/linux_configuration/periodic_background/hosts/guard/plugins/resolved-plugin.sh"
## installed scripts referencing the repo ## installed scripts referencing the repo
/usr/local/bin/hosts-file-monitor.sh /usr/local/bin/hosts-file-monitor.sh
10:HOSTS_INSTALL_SCRIPT="/home/kuhy/testsAndMisc/linux_configuration/periodic_background/hosts/install.sh" 10:HOSTS_INSTALL_SCRIPT="/home/kuhy/src/testsAndMisc/linux_configuration/periodic_background/hosts/install.sh"
/usr/local/bin/periodic-system-maintenance.sh /usr/local/bin/periodic-system-maintenance.sh
26:PACMAN_WRAPPER_INSTALL="/home/kuhy/testsAndMisc/linux_configuration/periodic_background/digital_wellbeing/pacman/install_pacman_wrapper.sh" 26:PACMAN_WRAPPER_INSTALL="/home/kuhy/src/testsAndMisc/linux_configuration/periodic_background/digital_wellbeing/pacman/install_pacman_wrapper.sh"
27:MAKEPKG_WRAPPER_INSTALL="/home/kuhy/testsAndMisc/linux_configuration/periodic_background/digital_wellbeing/pacman/install_makepkg_wrapper.sh" 27:MAKEPKG_WRAPPER_INSTALL="/home/kuhy/src/testsAndMisc/linux_configuration/periodic_background/digital_wellbeing/pacman/install_makepkg_wrapper.sh"
28:HOSTS_INSTALL_SCRIPT="/home/kuhy/testsAndMisc/linux_configuration/periodic_background/hosts/install.sh" 28:HOSTS_INSTALL_SCRIPT="/home/kuhy/src/testsAndMisc/linux_configuration/periodic_background/hosts/install.sh"
/usr/local/bin/wsg-grabber /usr/local/bin/wsg-grabber
4:export PYTHONPATH="/home/kuhy/testsAndMisc${PYTHONPATH:+:$PYTHONPATH}" 4:export PYTHONPATH="/home/kuhy/testsAndMisc${PYTHONPATH:+:$PYTHONPATH}"
## symlinks into the repo ## symlinks into the repo
/home/kuhy/.config/i3blocks/claude_usage.sh -> /home/kuhy/testsAndMisc/linux_configuration/i3blocks/claude_usage.sh /home/kuhy/.config/i3blocks/claude_usage.sh -> /home/kuhy/src/testsAndMisc/linux_configuration/i3blocks/claude_usage.sh
/home/kuhy/.config/i3blocks/config -> /home/kuhy/testsAndMisc/linux_configuration/i3blocks/config /home/kuhy/.config/i3blocks/config -> /home/kuhy/src/testsAndMisc/linux_configuration/i3blocks/config
/home/kuhy/.config/i3blocks/cpu_monitor.sh -> /home/kuhy/testsAndMisc/linux_configuration/i3blocks/cpu_monitor.sh /home/kuhy/.config/i3blocks/cpu_monitor.sh -> /home/kuhy/src/testsAndMisc/linux_configuration/i3blocks/cpu_monitor.sh
/home/kuhy/.config/i3blocks/disk.sh -> /home/kuhy/testsAndMisc/linux_configuration/i3blocks/disk.sh /home/kuhy/.config/i3blocks/disk.sh -> /home/kuhy/src/testsAndMisc/linux_configuration/i3blocks/disk.sh
/home/kuhy/.config/i3blocks/ethernet.sh -> /home/kuhy/testsAndMisc/linux_configuration/i3blocks/ethernet.sh /home/kuhy/.config/i3blocks/ethernet.sh -> /home/kuhy/src/testsAndMisc/linux_configuration/i3blocks/ethernet.sh
/home/kuhy/.config/i3blocks/gpu_monitor.sh -> /home/kuhy/testsAndMisc/linux_configuration/i3blocks/gpu_monitor.sh /home/kuhy/.config/i3blocks/gpu_monitor.sh -> /home/kuhy/src/testsAndMisc/linux_configuration/i3blocks/gpu_monitor.sh
/home/kuhy/.config/i3blocks/memory.sh -> /home/kuhy/testsAndMisc/linux_configuration/i3blocks/memory.sh /home/kuhy/.config/i3blocks/memory.sh -> /home/kuhy/src/testsAndMisc/linux_configuration/i3blocks/memory.sh
/home/kuhy/.config/i3blocks/motherboard_temp.sh -> /home/kuhy/testsAndMisc/linux_configuration/i3blocks/motherboard_temp.sh /home/kuhy/.config/i3blocks/motherboard_temp.sh -> /home/kuhy/src/testsAndMisc/linux_configuration/i3blocks/motherboard_temp.sh
/home/kuhy/.config/i3blocks/pc_startup_status.sh -> /home/kuhy/testsAndMisc/linux_configuration/i3blocks/pc_startup_status.sh /home/kuhy/.config/i3blocks/pc_startup_status.sh -> /home/kuhy/src/testsAndMisc/linux_configuration/i3blocks/pc_startup_status.sh
/home/kuhy/.config/i3blocks/persist_common.sh -> /home/kuhy/testsAndMisc/linux_configuration/i3blocks/persist_common.sh /home/kuhy/.config/i3blocks/persist_common.sh -> /home/kuhy/src/testsAndMisc/linux_configuration/i3blocks/persist_common.sh
/home/kuhy/.config/i3blocks/shutdown_countdown.sh -> /home/kuhy/testsAndMisc/linux_configuration/i3blocks/shutdown_countdown.sh /home/kuhy/.config/i3blocks/shutdown_countdown.sh -> /home/kuhy/src/testsAndMisc/linux_configuration/i3blocks/shutdown_countdown.sh
/home/kuhy/.config/i3blocks/time.sh -> /home/kuhy/testsAndMisc/linux_configuration/i3blocks/time.sh /home/kuhy/.config/i3blocks/time.sh -> /home/kuhy/src/testsAndMisc/linux_configuration/i3blocks/time.sh
/home/kuhy/.config/i3blocks/volume.sh -> /home/kuhy/testsAndMisc/linux_configuration/i3blocks/volume.sh /home/kuhy/.config/i3blocks/volume.sh -> /home/kuhy/src/testsAndMisc/linux_configuration/i3blocks/volume.sh
/home/kuhy/.local/bin/keepass-open -> /home/kuhy/testsAndMisc/linux_configuration/features/keepass-open.sh /home/kuhy/.local/bin/keepass-open -> /home/kuhy/src/testsAndMisc/linux_configuration/features/keepass-open.sh
/home/kuhy/.local/bin/line-speed-probe.sh -> /home/kuhy/testsAndMisc/linux_configuration/diagnostics/line-speed-probe.sh /home/kuhy/.local/bin/line-speed-probe.sh -> /home/kuhy/src/testsAndMisc/linux_configuration/diagnostics/line-speed-probe.sh
/home/kuhy/.local/bin/steam-download-duty.sh -> /home/kuhy/testsAndMisc/linux_configuration/diagnostics/steam-download-duty.sh /home/kuhy/.local/bin/steam-download-duty.sh -> /home/kuhy/src/testsAndMisc/linux_configuration/diagnostics/steam-download-duty.sh
/home/kuhy/.oh-my-zsh/custom/calc-live.zsh -> /home/kuhy/testsAndMisc/linux_configuration/zsh/calc-live.zsh /home/kuhy/.oh-my-zsh/custom/calc-live.zsh -> /home/kuhy/src/testsAndMisc/linux_configuration/zsh/calc-live.zsh
/home/kuhy/.oh-my-zsh/custom/claude-tty-fix.zsh -> /home/kuhy/testsAndMisc/linux_configuration/zsh/claude-tty-fix.zsh /home/kuhy/.oh-my-zsh/custom/claude-tty-fix.zsh -> /home/kuhy/src/testsAndMisc/linux_configuration/zsh/claude-tty-fix.zsh
/home/kuhy/.oh-my-zsh/custom/token-savers.zsh -> /home/kuhy/testsAndMisc/linux_configuration/zsh/token-savers.zsh /home/kuhy/.oh-my-zsh/custom/token-savers.zsh -> /home/kuhy/src/testsAndMisc/linux_configuration/zsh/token-savers.zsh
``` ```
## Depth-cap progress ## Depth-cap progress

View File

@ -1,4 +1,4 @@
# Working safely in a shared `~/testsAndMisc` checkout # Working safely in a shared `~/src/testsAndMisc` checkout
Split out of `refactor_claude_todo_resume.md` (which hit the 250-line cap it Split out of `refactor_claude_todo_resume.md` (which hit the 250-line cap it
exists to enforce). Nothing here is specific to the file-length refactor -- it exists to enforce). Nothing here is specific to the file-length refactor -- it

View File

@ -25,7 +25,7 @@ Upstream, open and unfixed: [#1634] and [#1638].
## Fixed on this phone (2026-08-28) ## Fixed on this phone (2026-08-28)
The phone no longer runs the upstream APK. It runs The phone no longer runs the upstream APK. It runs
[kuhyx/TachiyomiSY](https://github.com/kuhyx/TachiyomiSY) (`~/tachiyomisy`, [kuhyx/TachiyomiSY](https://github.com/kuhyx/TachiyomiSY) (`~/vendor/tachiyomisy`,
commit "Serialize backup restore and refuse to sync a collapsed library"), commit "Serialize backup restore and refuse to sync a collapsed library"),
which carries three changes upstream does not have: which carries three changes upstream does not have:
@ -52,8 +52,8 @@ the official APK rather than sitting beside it, and is signed with
```bash ```bash
JAVA_HOME=/usr/lib/jvm/java-21-openjdk \ JAVA_HOME=/usr/lib/jvm/java-21-openjdk \
~/tachiyomisy/gradlew -p ~/tachiyomisy assembleFoss -PsyReplaceUpstream ~/vendor/tachiyomisy/gradlew -p ~/vendor/tachiyomisy assembleFoss -PsyReplaceUpstream
adb install -r ~/tachiyomisy/app/build/outputs/apk/foss/app-arm64-v8a-foss.apk adb install -r ~/vendor/tachiyomisy/app/build/outputs/apk/foss/app-arm64-v8a-foss.apk
``` ```
Never install an official TachiyomiSY APK over it: the signatures differ, so Never install an official TachiyomiSY APK over it: the signatures differ, so
@ -137,13 +137,13 @@ systemctl --user list-timers syncyomi-guard.timer
journalctl --user -u syncyomi-guard.service -n 20 journalctl --user -u syncyomi-guard.service -n 20
``` ```
Snapshots live in `~/syncyomi/snapshots/` and are ordinary `.tachibk` files — Snapshots live in `~/services/syncyomi/snapshots/` and are ordinary `.tachibk` files —
`adb push` one to the phone and restore it directly. `adb push` one to the phone and restore it directly.
**Retention: 14 snapshots at 30-minute intervals is about 7 hours of history.** **Retention: 14 snapshots at 30-minute intervals is about 7 hours of history.**
That covers a fast incident — the 2026-08-09 window was 51 seconds — but not a That covers a fast incident — the 2026-08-09 window was 51 seconds — but not a
slow drift noticed days later. For that, the reference copy is slow drift noticed days later. For that, the reference copy is
`~/syncyomi/recovery/`, which is never pruned. `~/services/syncyomi/recovery/`, which is never pruned.
After a **deliberate** library purge the guard will report a collapse; accept After a **deliberate** library purge the guard will report a collapse; accept
the new state with `--accept` to re-baseline. the new state with `--accept` to re-baseline.
@ -167,7 +167,7 @@ and 1 won. The phone was not losing a merge — it was honestly reporting what i
had. The restore writes manga and chapters but silently drops the category had. The restore writes manga and chapters but silently drops the category
tables, which is the narrow form of the same 1.13.2 bug. tables, which is the narrow form of the same 1.13.2 bug.
The categories still exist in `~/syncyomi/recovery/syncyomi_recovered_2026-08-09.tachibk` The categories still exist in `~/services/syncyomi/recovery/syncyomi_recovered_2026-08-09.tachibk`
(18 definitions, 1724 memberships). Getting them into the app needs the (18 definitions, 1724 memberships). Getting them into the app needs the
downgrade path — 1.12.0 → restore → verify → upgrade — which was not attempted. downgrade path — 1.12.0 → restore → verify → upgrade — which was not attempted.
If a future SY release fixes #1634, retry the restore before the downgrade. If a future SY release fixes #1634, retry the restore before the downgrade.
@ -181,7 +181,7 @@ payload may still exist as stale pages in `syncyomi.db-wal`.
the WAL and the only remaining copy with it. Snapshot all three files first: the WAL and the only remaining copy with it. Snapshot all three files first:
```bash ```bash
sudo cp -a ~/syncyomi/config/syncyomi.db{,-shm,-wal} /some/safe/dir/ sudo cp -a ~/services/syncyomi/config/syncyomi.db{,-shm,-wal} /some/safe/dir/
``` ```
Then rebuild by applying every WAL frame over the base database, last writer Then rebuild by applying every WAL frame over the base database, last writer
@ -189,7 +189,7 @@ wins, and extract `sync_data.data`. The 2026-08-09 reconstruction and its
verification are recorded in verification are recorded in
`docs/superpowers/evidence/syncyomi-library-recovery-2026-08-09.json`. `docs/superpowers/evidence/syncyomi-library-recovery-2026-08-09.json`.
**`~/syncyomi/recovery/recovered.db` is a forensic artifact, not a database.** **`~/services/syncyomi/recovery/recovered.db` is a forensic artifact, not a database.**
It was assembled by splicing pages from several checkpoint generations, so it is It was assembled by splicing pages from several checkpoint generations, so it is
not any single committed SQLite state — `quick_check` passes and the payload not any single committed SQLite state — `quick_check` passes and the payload
blob decodes, but do **not** copy it over `config/syncyomi.db`. The deliverable blob decodes, but do **not** copy it over `config/syncyomi.db`. The deliverable

View File

@ -139,7 +139,7 @@
<h3>The two things this change adds a home for</h3> <h3>The two things this change adds a home for</h3>
<ul> <ul>
<li><b>A file store</b> reachable from a phone on mobile data — the “Google Drive alternative”.</li> <li><b>A file store</b> reachable from a phone on mobile data — the “Google Drive alternative”.</li>
<li><b>The KeePassXC vault</b>: a single encrypted file, <code class="inl">~/Keepass/Passwords.kdbx</code>. KeePass has no built-in cloud sync by design — you choose where the file lives.</li> <li><b>The KeePassXC vault</b>: a single encrypted file, <code class="inl">~/data/Keepass/Passwords.kdbx</code>. KeePass has no built-in cloud sync by design — you choose where the file lives.</li>
</ul> </ul>
<div class="note"><b>dufs</b> is the chosen tool: a single ~2.5 MB Rust binary that serves a <i>real filesystem folder</i> over HTTP with a web UI <b>and</b> built-in WebDAV. Because it serves the folder directly, an <code class="inl">rsync</code> (or any local write) into that folder shows up instantly — no database, no re-scan step. Contrast Seafile, which stores files in an internal block database that <code class="inl">rsync</code> cannot write into.</p></div> <div class="note"><b>dufs</b> is the chosen tool: a single ~2.5 MB Rust binary that serves a <i>real filesystem folder</i> over HTTP with a web UI <b>and</b> built-in WebDAV. Because it serves the folder directly, an <code class="inl">rsync</code> (or any local write) into that folder shows up instantly — no database, no re-scan step. Contrast Seafile, which stores files in an internal block database that <code class="inl">rsync</code> cannot write into.</p></div>
@ -171,7 +171,7 @@
<div class="arrow">→</div> <div class="arrow">→</div>
<div class="node"><b>rsync</b><span>only the .kdbx</span></div> <div class="node"><b>rsync</b><span>only the .kdbx</span></div>
<div class="arrow">→</div> <div class="arrow">→</div>
<div class="node"><b>~/cloud/Keepass</b><span>phone pulls via WebDAV</span></div> <div class="node"><b>~/data/cloud/Keepass</b><span>phone pulls via WebDAV</span></div>
</div> </div>
</div> </div>
<p>On the phone, <b>KeePassDX</b> opens the database straight from the WebDAV URL — so the “mobile app” requirement is met with zero custom code.</p> <p>On the phone, <b>KeePassDX</b> opens the database straight from the WebDAV URL — so the “mobile app” requirement is met with zero custom code.</p>
@ -252,14 +252,14 @@ bad="$(printf '%s\n' "$staged" | grep -E '(^|/)\.dufs_cloud\.conf$' || true)"
<h2 id="playground">4 · Playground — the serve-dir guard</h2> <h2 id="playground">4 · Playground — the serve-dir guard</h2>
<p>Type a candidate <code class="inl">CLOUD_ROOT</code> and see the exact verdict <code class="inl">assert_cloud_root_safe</code> would give. (Assumes <code class="inl">$HOME=/home/kuhy</code>, repo <code class="inl">/home/kuhy/testsAndMisc</code>. Paths are resolved like <code class="inl">readlink -m</code>, so <code class="inl">..</code> collapses.)</p> <p>Type a candidate <code class="inl">CLOUD_ROOT</code> and see the exact verdict <code class="inl">assert_cloud_root_safe</code> would give. (Assumes <code class="inl">$HOME=/home/kuhy</code>, repo <code class="inl">/home/kuhy/testsAndMisc</code>. Paths are resolved like <code class="inl">readlink -m</code>, so <code class="inl">..</code> collapses.)</p>
<div class="mw"> <div class="mw">
<input id="mwIn" value="/home/kuhy/cloud" spellcheck="false" oninput="mwEval()"> <input id="mwIn" value="/home/kuhy/data/cloud" spellcheck="false" oninput="mwEval()">
<div id="mwOut" class="verdict"></div> <div id="mwOut" class="verdict"></div>
<div class="chips"> <div class="chips">
<span class="chip" onclick="mwSet('/home/kuhy/cloud')">~/cloud</span> <span class="chip" onclick="mwSet('/home/kuhy/data/cloud')">~/cloud</span>
<span class="chip" onclick="mwSet('/home/kuhy')">~ (home)</span> <span class="chip" onclick="mwSet('/home/kuhy')">~ (home)</span>
<span class="chip" onclick="mwSet('/home/kuhy/.ssh')">~/.ssh</span> <span class="chip" onclick="mwSet('/home/kuhy/.ssh')">~/.ssh</span>
<span class="chip" onclick="mwSet('/home/kuhy/.config/dufs')">~/.config/dufs</span> <span class="chip" onclick="mwSet('/home/kuhy/.config/dufs')">~/.config/dufs</span>
<span class="chip" onclick="mwSet('/home/kuhy/testsAndMisc/x')">inside the repo</span> <span class="chip" onclick="mwSet('/home/kuhy/src/testsAndMisc/x')">inside the repo</span>
<span class="chip" onclick="mwSet('/srv/cloud/../../home/kuhy/.gnupg')">traversal → .gnupg</span> <span class="chip" onclick="mwSet('/srv/cloud/../../home/kuhy/.gnupg')">traversal → .gnupg</span>
<span class="chip" onclick="mwSet('/srv/cloud')">/srv/cloud</span> <span class="chip" onclick="mwSet('/srv/cloud')">/srv/cloud</span>
</div> </div>
@ -288,9 +288,9 @@ bad="$(printf '%s\n' "$staged" | grep -E '(^|/)\.dufs_cloud\.conf$' || true)"
} }
function guard(cloudRoot){ function guard(cloudRoot){
// Mirrors assert_cloud_root_safe: HOME and / are blocked ONLY as an exact // Mirrors assert_cloud_root_safe: HOME and / are blocked ONLY as an exact
// match (subdirs of HOME like ~/cloud are fine); .ssh/.config/.gnupg and // match (subdirs of HOME like ~/data/cloud are fine); .ssh/.config/.gnupg and
// the repo are blocked as exact-or-prefix. // the repo are blocked as exact-or-prefix.
var HOME='/home/kuhy', REPO='/home/kuhy/testsAndMisc'; var HOME='/home/kuhy', REPO='/home/kuhy/src/testsAndMisc';
var r = resolve(cloudRoot); var r = resolve(cloudRoot);
if (r===HOME) return {ok:false, why:'Resolves to '+r+' — that is $HOME itself; serving it would expose everything.'}; if (r===HOME) return {ok:false, why:'Resolves to '+r+' — that is $HOME itself; serving it would expose everything.'};
if (r==='/') return {ok:false, why:'Resolves to / — the whole filesystem. Refused.'}; if (r==='/') return {ok:false, why:'Resolves to / — the whole filesystem. Refused.'};
@ -363,7 +363,7 @@ bad="$(printf '%s\n' "$staged" | grep -E '(^|/)\.dufs_cloud\.conf$' || true)"
rat: "assert_config_gitignored refuses to persist the secret unless git confirms it's ignored — a runtime, fail-closed check." rat: "assert_config_gitignored refuses to persist the secret unless git confirms it's ignored — a runtime, fail-closed check."
}, },
{ {
q: "Why does the sync copy only Passwords.kdbx instead of the whole ~/Keepass directory?", q: "Why does the sync copy only Passwords.kdbx instead of the whole ~/data/Keepass directory?",
opts: [ opts: [
"Because rsync cannot recurse into directories that contain hidden dot-folders.", "Because rsync cannot recurse into directories that contain hidden dot-folders.",
"Because copying the directory would exceed dufs's per-file upload size limit.", "Because copying the directory would exceed dufs's per-file upload size limit.",

View File

@ -186,7 +186,7 @@ kuhy-sync.duckdns.org { reverse_proxy 127.0.0.1:8282 } # SyncYomi</pre></div>
<div class="code"><pre>preflight # deps, docker up, gitea-caddy present <div class="code"><pre>preflight # deps, docker up, gitea-caddy present
migrate_caddy_snippets # split TODAY's Caddyfile → sites/*.caddy (keep dufs, sync) migrate_caddy_snippets # split TODAY's Caddyfile → sites/*.caddy (keep dufs, sync)
relocate_gitea # re-run setup_gitea.sh (now writes import + gitea.caddy) relocate_gitea # re-run setup_gitea.sh (now writes import + gitea.caddy)
build_website # pnpm build → ~/personal-website/dist build_website # pnpm build → ~/src/personal-website/dist
write_website_stack # loopback caddy compose + inner Caddyfile write_website_stack # loopback caddy compose + inner Caddyfile
write_website_snippet# sites/website.caddy → kuhy.duckdns.org write_website_snippet# sites/website.caddy → kuhy.duckdns.org
start_website # container up on 127.0.0.1:8088 start_website # container up on 127.0.0.1:8088

View File

@ -184,7 +184,7 @@
</div> </div>
<h3>Try it: how the desk routes a request</h3> <h3>Try it: how the desk routes a request</h3>
<p>Pick a hostname and watch Caddy decide where it goes. This mirrors the real site blocks in <code>~/gitea/Caddyfile</code>.</p> <p>Pick a hostname and watch Caddy decide where it goes. This mirrors the real site blocks in <code>~/services/gitea/Caddyfile</code>.</p>
<div class="router"> <div class="router">
<label for="host">Incoming request hostname</label> <label for="host">Incoming request hostname</label>

View File

@ -503,7 +503,7 @@ worth explaining why:</p>
# The live app: ~/dufs_client is a stale, remote-less checkout that # The live app: ~/dufs_client is a stale, remote-less checkout that
# was superseded when dufs-cloud absorbed it (see testsAndMisc # was superseded when dufs-cloud absorbed it (see testsAndMisc
# CLAUDE.md). Same package name, so it is easy to icon the wrong one. # CLAUDE.md). Same package name, so it is easy to icon the wrong one.
repo=_HOME / "dufs-cloud" / "app",</code></pre> repo=_HOME / "src/dufs-cloud" / "app",</code></pre>
<p>There were two checkouts with the same <code>name:</code>, the same <p>There were two checkouts with the same <code>name:</code>, the same
<code>applicationId</code>, and the same directory layout. Only one had a git remote and <code>applicationId</code>, and the same directory layout. Only one had a git remote and

View File

@ -1,13 +1,13 @@
{ {
"title": "Endurain viewer fed by RunnerUp WebDAV exports", "title": "Endurain viewer fed by RunnerUp WebDAV exports",
"objective": "Stand up self-hosted Endurain behind the existing Caddy edge and import RunnerUp activities into it, primarily over WebDAV from the phone with an adb pull as fallback. Endurain is a VIEWER only: screen-locker's workout gate keeps reading the phone over adb and is not modified, because a WebDAV drop folder is spoofable by anything that can write to ~/cloud and must never become a screen-unlock authority. Success means real activities from the phone are visible in Endurain, importing twice never creates a duplicate, and screen-locker still passes its full suite.", "objective": "Stand up self-hosted Endurain behind the existing Caddy edge and import RunnerUp activities into it, primarily over WebDAV from the phone with an adb pull as fallback. Endurain is a VIEWER only: screen-locker's workout gate keeps reading the phone over adb and is not modified, because a WebDAV drop folder is spoofable by anything that can write to ~/data/cloud and must never become a screen-unlock authority. Success means real activities from the phone are visible in Endurain, importing twice never creates a duplicate, and screen-locker still passes its full suite.",
"acceptance_criteria": [ "acceptance_criteria": [
"Endurain reachable at https://endurain.kuhy.duckdns.org with a valid Let's Encrypt certificate", "Endurain reachable at https://endurain.kuhy.duckdns.org with a valid Let's Encrypt certificate",
"At least 4 real RunnerUp activities imported and visible, with correct distance and duration", "At least 4 real RunnerUp activities imported and visible, with correct distance and duration",
"Re-running the importer over already-imported files creates ZERO new activities", "Re-running the importer over already-imported files creates ZERO new activities",
"The .gpx and .tcx of a single run import as ONE activity, not two", "The .gpx and .tcx of a single run import as ONE activity, not two",
"The seeded admin/admin login is rejected before the service is ever published", "The seeded admin/admin login is rejected before the service is ever published",
"The phone-facing WebDAV credential cannot read ~/cloud/Keepass or gdrive_backups", "The phone-facing WebDAV credential cannot read ~/data/cloud/Keepass or gdrive_backups",
"screen-locker's full test suite still passes at 100% coverage" "screen-locker's full test suite still passes at 100% coverage"
], ],
"out_of_scope": [ "out_of_scope": [

View File

@ -12,7 +12,7 @@
"pre-commit passes without --no-verify." "pre-commit passes without --no-verify."
], ],
"out_of_scope": [ "out_of_scope": [
"Moving heavy_job_lock.sh to ~/utils: the measurement does not support it, and every consumer but the installer already reads the installed copy.", "Moving heavy_job_lock.sh to ~/src/utils: the measurement does not support it, and every consumer but the installer already reads the installed copy.",
"The adb push half of update_android_hosts.sh, which needs a device in a writable state.", "The adb push half of update_android_hosts.sh, which needs a device in a writable state.",
"digital_wellbeing itself, which is the next and last extraction." "digital_wellbeing itself, which is the next and last extraction."
], ],

View File

@ -5,7 +5,7 @@
"check_directory_depth.sh --all exits 0 with --warn removed from the hook entry.", "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.", "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 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.", "All six moved suites plus every lib/tests/run_all.sh pass from ~/src/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.", "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.", "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.", "The pacman-wrapper drift manifest verifies with sha256sum -c after regeneration.",
@ -18,5 +18,5 @@
"The Nextcloud dedup, measured and recommended against in the handoff.", "The Nextcloud dedup, measured and recommended against in the handoff.",
"Reformatting vendor/common.sh, which is a verbatim copy." "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." "verifier": "./meta/scripts/check_directory_depth.sh --all exits 0; ~/src/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."
} }

View File

@ -4,8 +4,8 @@
"acceptance_criteria": [ "acceptance_criteria": [
"The extracted repo's history spans all four paths the unit has lived at, not just the current one — 52 commits, reaching the original 'block websites uising etc hosts'.", "The extracted repo's history spans all four paths the unit has lived at, not just the current one — 52 commits, reaching the original 'block websites uising etc hosts'.",
"Every extracted source file is byte-identical to its monorepo copy (diff -r reports no differences).", "Every extracted source file is byte-identical to its monorepo copy (diff -r reports no differences).",
"lib/tests/run_all.sh passes from ~/hosts-blocker, the real clone location, with 25 assertions.", "lib/tests/run_all.sh passes from ~/src/hosts-blocker, the real clone location, with 25 assertions.",
"All six monorepo callers resolve to /home/kuhy/hosts-blocker, including under sudo, where $HOME would be /root.", "All six monorepo callers resolve to /home/kuhy/src/hosts-blocker, including under sudo, where $HOME would be /root.",
"A missing checkout fails loudly with a clone instruction and a non-zero return — never a silent skip.", "A missing checkout fails loudly with a clone instruction and a non-zero return — never a silent skip.",
"check_directory_depth.sh --all reports 41 violations, all of them digital_wellbeing.", "check_directory_depth.sh --all reports 41 violations, all of them digital_wellbeing.",
"report_live_state.sh --check output is byte-identical before and after: no new stale references.", "report_live_state.sh --check output is byte-identical before and after: no new stale references.",
@ -20,5 +20,5 @@
"Fixing the 8 pre-existing stale live-state references — a separate commit after both extractions.", "Fixing the 8 pre-existing stale live-state references — a separate commit after both extractions.",
"Deleting .hippo/ or the untracked extraction residue — separate commits." "Deleting .hippo/ or the untracked extraction residue — separate commits."
], ],
"verifier": "diff -r --exclude=.git between the monorepo unit and the extracted repo reports IDENTICAL; ~/hosts-blocker/lib/tests/run_all.sh exits 0 with 25 passed; pre-commit run --all-files --hook-stage pre-push in the extracted repo passes with no file modified; shellcheck is clean on the helper and all six callers; sudo -n resolution prints /home/kuhy/hosts-blocker; HOSTS_BLOCKER_DIR=/nonexistent require_extracted_repo returns 1 with a clone instruction; check_directory_depth.sh --all reports 41; diff of report_live_state.sh --check before/after is empty." "verifier": "diff -r --exclude=.git between the monorepo unit and the extracted repo reports IDENTICAL; ~/src/hosts-blocker/lib/tests/run_all.sh exits 0 with 25 passed; pre-commit run --all-files --hook-stage pre-push in the extracted repo passes with no file modified; shellcheck is clean on the helper and all six callers; sudo -n resolution prints /home/kuhy/src/hosts-blocker; HOSTS_BLOCKER_DIR=/nonexistent require_extracted_repo returns 1 with a clone instruction; check_directory_depth.sh --all reports 41; diff of report_live_state.sh --check before/after is empty."
} }

View File

@ -13,5 +13,5 @@
"Notifications: a failed unit is the gate; notify-send from a root system unit is not.", "Notifications: a failed unit is the gate; notify-send from a root system unit is not.",
"Section 1 of the handoff (signal-bot boot resilience), which shipped separately in kuhyx/signal-bot." "Section 1 of the handoff (signal-bot boot resilience), which shipped separately in kuhyx/signal-bot."
], ],
"verifier": "linux_configuration/features/lib/tests/test_wg_verify_units.sh under meta/scripts/shell_coverage_jail.sh; plus two real reboots in ~/utils/vmbox (drifted -> failed unit, clean -> active unit) and a live run on the host." "verifier": "linux_configuration/features/lib/tests/test_wg_verify_units.sh under meta/scripts/shell_coverage_jail.sh; plus two real reboots in ~/src/utils/vmbox (drifted -> failed unit, clean -> active unit) and a live run on the host."
} }

View File

@ -1,5 +1,5 @@
{ {
"intent": "Give the new ~/restaurant-rater app a launcher icon from the shared family, so it stops shipping the stock Flutter beach icon.", "intent": "Give the new ~/src/restaurant-rater app a launcher icon from the shared family, so it stops shipping the stock Flutter beach icon.",
"scope": [ "scope": [
"python_pkg/app_icons/_glyph_art_more.py - the new SVG fragment", "python_pkg/app_icons/_glyph_art_more.py - the new SVG fragment",
"python_pkg/app_icons/glyphs.py - register the glyph in GLYPHS", "python_pkg/app_icons/glyphs.py - register the glyph in GLYPHS",
@ -14,14 +14,14 @@
], ],
"verification": [ "verification": [
{ {
"command": "PYTHONPATH=~/testsAndMisc python3 -m python_pkg.app_icons list", "command": "PYTHONPATH=~/src/testsAndMisc python3 -m python_pkg.app_icons list",
"result": "pass", "result": "pass",
"evidence": "bowl-star appears under Glyphs; 'restaurant_rater #B8862E bowl-star /home/kuhy/restaurant-rater' appears under Apps." "evidence": "bowl-star appears under Glyphs; 'restaurant_rater #B8862E bowl-star /home/kuhy/src/restaurant-rater' appears under Apps."
}, },
{ {
"command": "PYTHONPATH=~/testsAndMisc python3 -m python_pkg.app_icons generate --app restaurant_rater", "command": "PYTHONPATH=~/src/testsAndMisc python3 -m python_pkg.app_icons generate --app restaurant_rater",
"result": "pass", "result": "pass",
"evidence": "wrote 6 asset files to /home/kuhy/restaurant-rater/assets/icon (icon/foreground/monochrome, svg+png each). The generator's safe-box and negative-space checks did not fire." "evidence": "wrote 6 asset files to /home/kuhy/src/restaurant-rater/assets/icon (icon/foreground/monochrome, svg+png each). The generator's safe-box and negative-space checks did not fire."
}, },
{ {
"command": "visual review of the rendered 640px icon.png", "command": "visual review of the rendered 640px icon.png",

View File

@ -8,7 +8,7 @@
"changes": [ "changes": [
"Exact-pinned 95 previously range-specified dependencies to their newest stable release, via ~/.claude/scripts/apply_dep_pins.py driven by the gate's own --json findings.", "Exact-pinned 95 previously range-specified dependencies to their newest stable release, via ~/.claude/scripts/apply_dep_pins.py driven by the gate's own --json findings.",
"Moved the gatelock git-tag dependency from gatelock-v0.1.0 to gatelock-v0.7.2 -- six minor versions of drift that a PEP 508 direct reference hid from every previous check.", "Moved the gatelock git-tag dependency from gatelock-v0.1.0 to gatelock-v0.7.2 -- six minor versions of drift that a PEP 508 direct reference hid from every previous check.",
"Installed the shared gate's three pieces (delegate, pre-commit hook, CI workflow) from ~/utils/dep_freshness/templates." "Installed the shared gate's three pieces (delegate, pre-commit hook, CI workflow) from ~/src/utils/dep_freshness/templates."
], ],
"verification": [ "verification": [
{ {
@ -17,7 +17,7 @@
"evidence": "exit 0; clean venv rebuilt from the repinned meta/requirements.txt, pre-commit --all-files and the full suite green inside it" "evidence": "exit 0; clean venv rebuilt from the repinned meta/requirements.txt, pre-commit --all-files and the full suite green inside it"
}, },
{ {
"command": "~/utils/scripts/check_dependency_freshness.sh --all", "command": "~/src/utils/scripts/check_dependency_freshness.sh --all",
"result": "pass", "result": "pass",
"evidence": "exit 0 -- 97 findings before, 0 after" "evidence": "exit 0 -- 97 findings before, 0 after"
} }

View File

@ -1,5 +1,5 @@
{ {
"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.", "intent": "Extract the last over-cap unit into github.com/kuhyx/digital-wellbeing (cloned at ~/src/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": [ "scope": [
"REMOVED: linux_configuration/periodic_background/digital_wellbeing/ (80 tracked files)", "REMOVED: linux_configuration/periodic_background/digital_wellbeing/ (80 tracked files)",
"REMOVED: 6 test suites + 2 test libs from linux_configuration/tests/", "REMOVED: 6 test suites + 2 test libs from linux_configuration/tests/",
@ -33,7 +33,7 @@
"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." "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", "command": "~/src/digital-wellbeing/tests/run_all.sh",
"result": "pass", "result": "pass",
"evidence": "All six regression suites plus the discovered lib/tests/run_all.sh pass: 'All digital-wellbeing suites passed.'" "evidence": "All six regression suites plus the discovered lib/tests/run_all.sh pass: 'All digital-wellbeing suites passed.'"
}, },
@ -55,7 +55,7 @@
{ {
"command": "sudo sha256sum -c --status /var/lib/pacman-wrapper/source.sha256", "command": "sudo sha256sum -c --status /var/lib/pacman-wrapper/source.sha256",
"result": "pass", "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." "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 ~/src/digital-wellbeing and verifies -- with unchanged hashes, confirming the extracted bytes are identical."
}, },
{ {
"command": "report_live_state.sh --check | grep -c linux_configuration/scripts/", "command": "report_live_state.sh --check | grep -c linux_configuration/scripts/",

View File

@ -1,5 +1,5 @@
{ {
"intent": "Extract linux_configuration/periodic_background/hosts/ into its own public repo (github.com/kuhyx/hosts-blocker, cloned at ~/hosts-blocker), clearing 5 of the 46 remaining directory-depth violations without disturbing the live hosts blocker or its guards.", "intent": "Extract linux_configuration/periodic_background/hosts/ into its own public repo (github.com/kuhyx/hosts-blocker, cloned at ~/src/hosts-blocker), clearing 5 of the 46 remaining directory-depth violations without disturbing the live hosts blocker or its guards.",
"scope": [ "scope": [
"REMOVED: linux_configuration/periodic_background/hosts/ (15 files)", "REMOVED: linux_configuration/periodic_background/hosts/ (15 files)",
"ADDED: linux_configuration/lib/extracted_repos.sh (shared resolver)", "ADDED: linux_configuration/lib/extracted_repos.sh (shared resolver)",
@ -14,7 +14,7 @@
"No source file was modified during extraction: all 15 files are byte-identical to the monorepo copies (verified with diff -r). install.sh already resolved its lib/ via BASH_SOURCE and needed no rewrite.", "No source file was modified during extraction: all 15 files are byte-identical to the monorepo copies (verified with diff -r). install.sh already resolved its lib/ via BASH_SOURCE and needed no rewrite.",
"Added linux_configuration/lib/extracted_repos.sh: resolves an extracted repo's checkout via SUDO_USER's passwd entry (NOT $HOME, which is /root under sudo), supports a <NAME>_DIR override, and fails loudly with a clone instruction when absent.", "Added linux_configuration/lib/extracted_repos.sh: resolves an extracted repo's checkout via SUDO_USER's passwd entry (NOT $HOME, which is /root under sudo), supports a <NAME>_DIR override, and fails loudly with a clone instruction when absent.",
"Reused the existing REAL_HOME resolution already present in check_and_enable_services.sh for screen-locker rather than sourcing a second resolver into it.", "Reused the existing REAL_HOME resolution already present in check_and_enable_services.sh for screen-locker rather than sourcing a second resolver into it.",
"fresh-install/main.sh uses \"$HOME/hosts-blocker/install.sh\" because it runs from ~/linux-configuration (a different repo) and is not sudo-re-exec'd at that point.", "fresh-install/main.sh uses \"$HOME/src/hosts-blocker/install.sh\" because it runs from ~/src/testsAndMisc/linux_configuration (a different repo) and is not sudo-re-exec'd at that point.",
"The new repo ships NO shfmt hook, deliberately: guard/plugins/*.sh are space-indented and unformatted in the monorepo too (its git hook shfmt's only STAGED files), and their installed copies under /usr/local/share/guard-lib-plugins/ are byte-identical by md5. An shfmt hook would rewrite both on first commit and break that identity." "The new repo ships NO shfmt hook, deliberately: guard/plugins/*.sh are space-indented and unformatted in the monorepo too (its git hook shfmt's only STAGED files), and their installed copies under /usr/local/share/guard-lib-plugins/ are byte-identical by md5. An shfmt hook would rewrite both on first commit and break that identity."
], ],
"verification": [ "verification": [
@ -29,7 +29,7 @@
"evidence": "IDENTICAL — no source file altered by the extraction." "evidence": "IDENTICAL — no source file altered by the extraction."
}, },
{ {
"command": "./lib/tests/run_all.sh (in ~/hosts-blocker, the real clone location)", "command": "./lib/tests/run_all.sh (in ~/src/hosts-blocker, the real clone location)",
"result": "pass", "result": "pass",
"evidence": "test_hosts_protect.sh: 25 passed, 0 failed. All hosts install test files passed." "evidence": "test_hosts_protect.sh: 25 passed, 0 failed. All hosts install test files passed."
}, },
@ -41,7 +41,7 @@
{ {
"command": "sudo -n bash -c 'source lib/extracted_repos.sh; extracted_repo_dir hosts-blocker'", "command": "sudo -n bash -c 'source lib/extracted_repos.sh; extracted_repo_dir hosts-blocker'",
"result": "pass", "result": "pass",
"evidence": "HOME=/home/kuhy resolved=/home/kuhy/hosts-blocker — resolves the invoking user, not /root/hosts-blocker." "evidence": "HOME=/home/kuhy resolved=/home/kuhy/src/hosts-blocker — resolves the invoking user, not /root/hosts-blocker."
}, },
{ {
"command": "HOSTS_BLOCKER_DIR=/nonexistent require_extracted_repo hosts-blocker", "command": "HOSTS_BLOCKER_DIR=/nonexistent require_extracted_repo hosts-blocker",
@ -75,7 +75,7 @@
} }
], ],
"risks": [ "risks": [
"A machine without ~/hosts-blocker cloned now fails the hosts install step. That is deliberate and loud (clone instruction printed); the previous silent-skip mode is what caused five months of dead self-repair after the 2026-05-15 reorg.", "A machine without ~/src/hosts-blocker cloned now fails the hosts install step. That is deliberate and loud (clone instruction printed); the previous silent-skip mode is what caused five months of dead self-repair after the 2026-05-15 reorg.",
"install_core_system.sh on a genuinely fresh machine will hard-stop at the hosts module until the repo is cloned. INSTALLER_FIX_TASK.md already tracks fresh-machine installer defects and has been updated to name the new location.", "install_core_system.sh on a genuinely fresh machine will hard-stop at the hosts module until the repo is cloned. INSTALLER_FIX_TASK.md already tracks fresh-machine installer defects and has been updated to name the new location.",
"The guard plugins remain unformatted. If an shfmt hook is ever added to hosts-blocker it will rewrite them, and the installed copies must be re-synced in the same change.", "The guard plugins remain unformatted. If an shfmt hook is ever added to hosts-blocker it will rewrite them, and the installed copies must be re-synced in the same change.",
"The live hosts blocker was NOT re-installed as part of this change; /etc/hosts and its guards are untouched and still enforced from their installed copies." "The live hosts blocker was NOT re-installed as part of this change; /etc/hosts and its guards are untouched and still enforced from their installed copies."

View File

@ -6,13 +6,13 @@
"Non-goal: changing any gate's rules, or restyling the existing icon family" "Non-goal: changing any gate's rules, or restyling the existing icon family"
], ],
"changes": [ "changes": [
"pre-commit.yml now checks out kuhyx/utils into .utils/ and points UTILS_ROOT at it. The md-naming and file-length hooks are thin shims that exec the real gate out of that repo; a runner has no ~/utils, so both failed with 'shared gate not found at /home/runner/utils/...' and the workflow has been red since 2026-08-24. The dedicated md-naming.yml already did this checkout — this copies the same approach rather than inventing one.", "pre-commit.yml now checks out kuhyx/utils into .utils/ and points UTILS_ROOT at it. The md-naming and file-length hooks are thin shims that exec the real gate out of that repo; a runner has no ~/src/utils, so both failed with 'shared gate not found at /home/runner/utils/...' and the workflow has been red since 2026-08-24. The dedicated md-naming.yml already did this checkout — this copies the same approach rather than inventing one.",
"Registered app_icons entry 'punchme' (shared family accent #B8862E, glyph punch-card, linux=False since the app is Android-only).", "Registered app_icons entry 'punchme' (shared family accent #B8862E, glyph punch-card, linux=False since the app is Android-only).",
"Added the _PUNCH_CARD glyph: a time card with a clipped corner, a column of four punched holes and three ruled lines. The first draft put two holes above two wide slots and read unmistakably as a face (eyes over teeth) at every size, which is why the holes are now a vertical column." "Added the _PUNCH_CARD glyph: a time card with a clipped corner, a column of four punched holes and three ruled lines. The first draft put two holes above two wide slots and read unmistakably as a face (eyes over teeth) at every size, which is why the holes are now a vertical column."
], ],
"verification": [ "verification": [
{ {
"command": "UTILS_ROOT=/home/kuhy/utils bash scripts/check_md_naming.sh README.md", "command": "UTILS_ROOT=/home/kuhy/src/utils bash scripts/check_md_naming.sh README.md",
"result": "pass", "result": "pass",
"evidence": "Exit 0. With UTILS_ROOT=/nonexistent the same shim fails with 'shared gate not found at /nonexistent/scripts/check_md_naming.sh', so the override is what the fix relies on and it is proven in both directions." "evidence": "Exit 0. With UTILS_ROOT=/nonexistent the same shim fails with 'shared gate not found at /nonexistent/scripts/check_md_naming.sh', so the override is what the fix relies on and it is proven in both directions."
}, },
@ -32,7 +32,7 @@
"evidence": "All hooks passed, including pylint, bandit and 'pytest with coverage enforcement'." "evidence": "All hooks passed, including pylint, bandit and 'pytest with coverage enforcement'."
}, },
{ {
"command": "PYTHONPATH=~/testsAndMisc python3 -m python_pkg.app_icons generate --app punchme --android", "command": "PYTHONPATH=~/src/testsAndMisc python3 -m python_pkg.app_icons generate --app punchme --android",
"result": "pass", "result": "pass",
"evidence": "Wrote 6 asset files and regenerated the Android mipmaps with no safe-box warning. Rendered at full size, circle-masked and 48dp and reviewed each; legible at all three." "evidence": "Wrote 6 asset files and regenerated the Android mipmaps with no safe-box warning. Rendered at full size, circle-masked and 48dp and reviewed each; legible at all three."
} }

View File

@ -6,10 +6,10 @@
"python_pkg/app_icons/_glyph_art_more.py - the glyph's SVG fragment", "python_pkg/app_icons/_glyph_art_more.py - the glyph's SVG fragment",
"Non-goal: no new accent. The family accent #B8862E is shared; apps are distinguished by glyph shape only.", "Non-goal: no new accent. The family accent #B8862E is shared; apps are distinguished by glyph shape only.",
"Non-goal: no change to the 13 existing glyphs or to any other app's entry.", "Non-goal: no change to the 13 existing glyphs or to any other app's entry.",
"The consuming repo (~/phone-focus-mode) is committed separately; it blocks binaries, so the icon lands there in vector form." "The consuming repo (~/src/phone-focus-mode) is committed separately; it blocks binaries, so the icon lands there in vector form."
], ],
"changes": [ "changes": [
"Added AppIcon(key='focus_owner', repo=~/phone-focus-mode/focus-owner, glyph='padlock-closed', accent='#B8862E', linux=False). The repo path points into the monorepo because focus-owner was absorbed as a subtree on 2026-08-24 and both the standalone clone and its GitHub repo are gone.", "Added AppIcon(key='focus_owner', repo=~/src/phone-focus-mode/focus-owner, glyph='padlock-closed', accent='#B8862E', linux=False). The repo path points into the monorepo because focus-owner was absorbed as a subtree on 2026-08-24 and both the standalone clone and its GitHub repo are gone.",
"Added the _PADLOCK_CLOSED glyph fragment: shackle stroked (inheriting the wrapping group's round caps), body filled in the accent, keyhole punched back out in the field colour via fill-rule='evenodd'. Splitting stroke and fill is deliberate - a fully stroked padlock loses its keyhole at 48dp, a fully filled one loses the shackle's opening.", "Added the _PADLOCK_CLOSED glyph fragment: shackle stroked (inheriting the wrapping group's round caps), body filled in the accent, keyhole punched back out in the field colour via fill-rule='evenodd'. Splitting stroke and fill is deliberate - a fully stroked padlock loses its keyhole at 48dp, a fully filled one loses the shackle's opening.",
"Geometry computed against style.py rather than eyeballed, per the lesson recorded on _NOTE_ASCENDER: body 292..732 x 520..792; shackle centreline r=132 about (512,520) giving an outer top of 352, inside the 232 safe edge, with legs at x=380/644 inside the body; keyhole leaves 60px of body above and 76px below the stem, both clear of MIN_NEGATIVE_SPACE=36." "Geometry computed against style.py rather than eyeballed, per the lesson recorded on _NOTE_ASCENDER: body 292..732 x 520..792; shackle centreline r=132 about (512,520) giving an outer top of 352, inside the 232 safe edge, with legs at x=380/644 inside the body; keyhole leaves 60px of body above and 76px below the stem, both clear of MIN_NEGATIVE_SPACE=36."
], ],

View File

@ -14,7 +14,7 @@
"hosts-blocker: stop_hosts_guard/restart_hosts_guard now handle the guard-lib instances (guard-file@hosts.*, guard-bind-mount@hosts.service) as well as the legacy per-file units, and refresh the canonical copy with 'guardctl file-guard sync hosts' -- without this a SECOND install run fought a live read-only bind mount and the install was not idempotent.", "hosts-blocker: stop_hosts_guard/restart_hosts_guard now handle the guard-lib instances (guard-file@hosts.*, guard-bind-mount@hosts.service) as well as the legacy per-file units, and refresh the canonical copy with 'guardctl file-guard sync hosts' -- without this a SECOND install run fought a live read-only bind mount and the install was not idempotent.",
"screen-locker: install_systemd.sh installs its own runtime dependencies (tk for the tkinter shared library, plus the pinned gatelock and crdt-sync git dependencies). It previously installed none, so the post-install workout check crashed and the installer exited 1 on any fresh machine.", "screen-locker: install_systemd.sh installs its own runtime dependencies (tk for the tkinter shared library, plus the pinned gatelock and crdt-sync git dependencies). It previously installed none, so the post-install workout check crashed and the installer exited 1 on any fresh machine.",
"steam-backlog-enforcer: install.sh no longer aborts under `set -euo pipefail` when a bare `read` finds no stdin; it defaults to not installing the systemd service when stdin is not a TTY, with STEAM_ENFORCER_INSTALL_SERVICE=y to opt in.", "steam-backlog-enforcer: install.sh no longer aborts under `set -euo pipefail` when a bare `read` finds no stdin; it defaults to not installing the systemd service when stdin is not a TTY, with STEAM_ENFORCER_INSTALL_SERVICE=y to opt in.",
"tests/lib/security_apps.sh: the screen_locker section resolves the extracted repo via extracted_repo_dir instead of a hardcoded $HOME/testsAndMisc/python_pkg path, and its assertions were rewritten against the current implementation (SPORT_CHOICES, SPORT_TABLE_TENNIS, _validate_table_tennis, sets_won, MANUAL_WORKOUT_REFLECTION_MIN_CHARS) because the four symbols it used to grep for no longer exist anywhere in the codebase." "tests/lib/security_apps.sh: the screen_locker section resolves the extracted repo via extracted_repo_dir instead of a hardcoded $HOME/src/testsAndMisc/python_pkg path, and its assertions were rewritten against the current implementation (SPORT_CHOICES, SPORT_TABLE_TENNIS, _validate_table_tennis, sets_won, MANUAL_WORKOUT_REFLECTION_MIN_CHARS) because the four symbols it used to grep for no longer exist anywhere in the codebase."
], ],
"verification": [ "verification": [
{ {
@ -25,7 +25,7 @@
{ {
"command": "bash linux_configuration/tests/test_security_hardening.sh (host, after the path+assertion fix)", "command": "bash linux_configuration/tests/test_security_hardening.sh (host, after the path+assertion fix)",
"result": "23 passed / 0 skipped", "result": "23 passed / 0 skipped",
"evidence": "The five screen_locker checks now execute against ~/screen-locker and pass. Note this is a changed measuring instrument as well as a changed measurement: the suite is 24 checks today, so the task file's '18 passed / 6 skipped out of 17' target was stale and could not be matched literally." "evidence": "The five screen_locker checks now execute against ~/src/screen-locker and pass. Note this is a changed measuring instrument as well as a changed measurement: the suite is 24 checks today, so the task file's '18 passed / 6 skipped out of 17' target was stale and could not be matched literally."
}, },
{ {
"command": "vm run <guest> 'bash guest_verify.sh' -- pristine vmbox guest, installer clones its own siblings", "command": "vm run <guest> 'bash guest_verify.sh' -- pristine vmbox guest, installer clones its own siblings",

View File

@ -31,14 +31,14 @@
"evidence": "exit 0 -- the basis for deleting both refactor todos" "evidence": "exit 0 -- the basis for deleting both refactor todos"
}, },
{ {
"command": "python3 -m pytest md_naming/tests/ (in ~/utils)", "command": "python3 -m pytest md_naming/tests/ (in ~/src/utils)",
"result": "pass", "result": "pass",
"evidence": "71 passed; 100% branch coverage on the gate" "evidence": "71 passed; 100% branch coverage on the gate"
} }
], ],
"risks": [ "risks": [
"A rename breaks an inbound link not caught by the reference sweep. Mitigated: the sweep covered ~/.claude/CLAUDE.md, rules/, skills/, memory/ and every tracked .md; a bare-basename match was deliberately narrowed after a dry run showed it would corrupt vendored node_modules files.", "A rename breaks an inbound link not caught by the reference sweep. Mitigated: the sweep covered ~/.claude/CLAUDE.md, rules/, skills/, memory/ and every tracked .md; a bare-basename match was deliberately narrowed after a dry run showed it would corrupt vendored node_modules files.",
"A doc classified TASK is actually finished, or vice versa. Mitigated: verdicts came from content audits cross-checked against repo code, and are recorded in ~/utils/md_naming/migrate/classify.py.", "A doc classified TASK is actually finished, or vice versa. Mitigated: verdicts came from content audits cross-checked against repo code, and are recorded in ~/src/utils/md_naming/migrate/classify.py.",
"The gate depends on kuhyx/utils being present; CI checks it out, and the shim fails loudly with install instructions if it is missing." "The gate depends on kuhyx/utils being present; CI checks it out, and the shim fails loudly with install instructions if it is missing."
], ],
"rollback": [ "rollback": [

View File

@ -5,19 +5,19 @@
"Non-goal: no change to the generator, the glyph set, or any other app's icon" "Non-goal: no change to the generator, the glyph set, or any other app's icon"
], ],
"changes": [ "changes": [
"Registered betting_sim: repo ~/betting-sim/app, accent #B8862E, glyph track-bars (rising bars with a plotted trend point, which reads as odds movement).", "Registered betting_sim: repo ~/src/betting-sim/app, accent #B8862E, glyph track-bars (rising bars with a plotted trend point, which reads as odds movement).",
"Set linux=false: betting-sim's desktop target is a Chrome-wrapped web build rather than the GTK embedder, so the hicolor PNG set would go unread." "Set linux=false: betting-sim's desktop target is a Chrome-wrapped web build rather than the GTK embedder, so the hicolor PNG set would go unread."
], ],
"verification": [ "verification": [
{ {
"command": "PYTHONPATH=. python3 -m python_pkg.app_icons list", "command": "PYTHONPATH=. python3 -m python_pkg.app_icons list",
"result": "pass", "result": "pass",
"evidence": "betting_sim #B8862E track-bars /home/kuhy/betting-sim/app" "evidence": "betting_sim #B8862E track-bars /home/kuhy/src/betting-sim/app"
}, },
{ {
"command": "PYTHONPATH=. python3 -m python_pkg.app_icons generate --app betting_sim --android", "command": "PYTHONPATH=. python3 -m python_pkg.app_icons generate --app betting_sim --android",
"result": "pass", "result": "pass",
"evidence": "wrote 6 asset files to /home/kuhy/betting-sim/app/assets/icon; mipmaps generated after adding flutter_launcher_icons to the app pubspec" "evidence": "wrote 6 asset files to /home/kuhy/src/betting-sim/app/assets/icon; mipmaps generated after adding flutter_launcher_icons to the app pubspec"
}, },
{ {
"command": "visual check of assets/icon/icon.png", "command": "visual check of assets/icon/icon.png",

View File

@ -32,7 +32,7 @@
{ {
"command": "resolver across all four invocation modes", "command": "resolver across all four invocation modes",
"result": "pass", "result": "pass",
"evidence": "as user -> /home/kuhy/hosts-blocker; via sudo -> same; as root with SUDO_USER/USER unset -> same; HOSTS_BLOCKER_DIR=/opt/hb -> /opt/hb (override still wins)." "evidence": "as user -> /home/kuhy/src/hosts-blocker; via sudo -> same; as root with SUDO_USER/USER unset -> same; HOSTS_BLOCKER_DIR=/opt/hb -> /opt/hb (override still wins)."
}, },
{ {
"command": "sudo visudo -c", "command": "sudo visudo -c",

View File

@ -21,7 +21,7 @@
{ {
"command": "./meta/scripts/report_live_state.sh --check; echo $?", "command": "./meta/scripts/report_live_state.sh --check; echo $?",
"result": "pass", "result": "pass",
"evidence": "Exit code 1 with 'live references into /home/kuhy/testsAndMisc remain', which is correct at baseline since nothing has moved yet." "evidence": "Exit code 1 with 'live references into /home/kuhy/src/testsAndMisc remain', which is correct at baseline since nothing has moved yet."
}, },
{ {
"command": "14 hand-built path cases through check_directory_depth.sh", "command": "14 hand-built path cases through check_directory_depth.sh",

View File

@ -8,7 +8,7 @@
], ],
"changes": [ "changes": [
"SYNCYOMI_IMAGE defaults to the local build syncyomi-kuhy:local, overridable back to a registry tag; adds SYNCYOMI_SRC and the fork URL.", "SYNCYOMI_IMAGE defaults to the local build syncyomi-kuhy:local, overridable back to a registry tag; adds SYNCYOMI_SRC and the fork URL.",
"ensure_local_image() builds that image from ~/syncyomi-src (cloning the fork if absent) before the container starts, under DOCKER_BUILDKIT=1 because the Dockerfile reads $BUILDPLATFORM; SYNCYOMI_REBUILD=1 forces a rebuild.", "ensure_local_image() builds that image from ~/vendor/syncyomi-src (cloning the fork if absent) before the container starts, under DOCKER_BUILDKIT=1 because the Dockerfile reads $BUILDPLATFORM; SYNCYOMI_REBUILD=1 forces a rebuild.",
"configure_caddy() now owns gitea/sites/<subdomain>.caddy instead of appending to the shared Caddyfile, deletes a legacy inline block, and validates the config before reloading.", "configure_caddy() now owns gitea/sites/<subdomain>.caddy instead of appending to the shared Caddyfile, deletes a legacy inline block, and validates the config before reloading.",
"Preflight installs git and docker-buildx alongside the existing packages." "Preflight installs git and docker-buildx alongside the existing packages."
], ],
@ -36,12 +36,12 @@
], ],
"risks": [ "risks": [
"The deployed build is develop (v1.1.14 + dependency bumps), not the v1.1.11 image it replaced; schema stays at version 8 and internal/database is unchanged between those points, so no migration runs.", "The deployed build is develop (v1.1.14 + dependency bumps), not the v1.1.11 image it replaced; schema stays at version 8 and internal/database is unchanged between those points, so no migration runs.",
"A machine without ~/syncyomi-src now clones the fork on first setup, which needs network and a Docker build (~4 min) instead of an image pull.", "A machine without ~/vendor/syncyomi-src now clones the fork on first setup, which needs network and a Docker build (~4 min) instead of an image pull.",
"The Caddy snippet is only rewritten when its content differs, so hand edits to that file are overwritten by design." "The Caddy snippet is only rewritten when its content differs, so hand edits to that file are overwritten by design."
], ],
"rollback": [ "rollback": [
"SYNCYOMI_IMAGE=ghcr.io/syncyomi/syncyomi:v1.1.11 ./linux_configuration/features/setup_syncyomi.sh setup", "SYNCYOMI_IMAGE=ghcr.io/syncyomi/syncyomi:v1.1.11 ./linux_configuration/features/setup_syncyomi.sh setup",
"Cold database copy: ~/syncyomi_safe_2026-08-28/pre-local-build/cold/syncyomi.db (schema version 8).", "Cold database copy: ~/archive/2026-08-28-syncyomi_safe/pre-local-build/cold/syncyomi.db (schema version 8).",
"After rollback, re-run the guard and confirm 2163 manga, then a phone sync round-trip." "After rollback, re-run the guard and confirm 2163 manga, then a phone sync round-trip."
] ]
} }

View File

@ -30,7 +30,7 @@
"evidence": "No findings against the generated unit; the only output was pre-existing warnings about unrelated units on this host." "evidence": "No findings against the generated unit; the only output was pre-existing warnings about unrelated units on this host."
}, },
{ {
"command": "systemd-run --user --wait -p ProtectSystem=strict -p ProtectHome=read-only -p ReadWritePaths=/home/kuhy/personal-website ... running the three real build steps", "command": "systemd-run --user --wait -p ProtectSystem=strict -p ProtectHome=read-only -p ReadWritePaths=/home/kuhy/src/personal-website ... running the three real build steps",
"result": "pass", "result": "pass",
"evidence": "vite build ok / ssr build ok / prerender ok, and `touch $HOME/.pw-sandbox-should-fail` was refused with 'Read-only file system'. The sandbox permits exactly the checkout." "evidence": "vite build ok / ssr build ok / prerender ok, and `touch $HOME/.pw-sandbox-should-fail` was refused with 'Read-only file system'. The sandbox permits exactly the checkout."
} }

View File

@ -336,7 +336,7 @@ printf '\nResults: %d passed, %d failed\n' "${PASS}" "${FAIL}"
- [ ] **Step 1.3: Run the test to verify it passes** - [ ] **Step 1.3: Run the test to verify it passes**
```bash ```bash
cd /home/kuhy/testsAndMisc cd /home/kuhy/src/testsAndMisc
bash phone_focus_mode/lib/tests/test_adb_common.sh bash phone_focus_mode/lib/tests/test_adb_common.sh
``` ```
@ -1762,7 +1762,7 @@ After=network.target
[Service] [Service]
Type=oneshot Type=oneshot
ExecStart=%h/testsAndMisc/scripts/run_all/run_phone.sh auto ExecStart=%h/src/testsAndMisc/scripts/run_all/run_phone.sh auto
StandardOutput=journal StandardOutput=journal
StandardError=journal StandardError=journal
``` ```

View File

@ -5,7 +5,7 @@ fed by RunnerUp exports from the phone.
**Endurain is a viewer.** `screen-locker`'s workout gate still reads the phone **Endurain is a viewer.** `screen-locker`'s workout gate still reads the phone
over adb and is untouched by anything here — a WebDAV drop folder is trivially over adb and is untouched by anything here — a WebDAV drop folder is trivially
spoofable by anything that can write to `~/cloud`, so it must never become an spoofable by anything that can write to `~/data/cloud`, so it must never become an
unlock authority. unlock authority.
## Paths ## Paths
@ -14,7 +14,7 @@ unlock authority.
| ------------- | --------------------------------------------------------------------------- | | ------------- | --------------------------------------------------------------------------- |
| Public URL | `https://endurain.kuhy.duckdns.org` | | Public URL | `https://endurain.kuhy.duckdns.org` |
| Local URL | `http://127.0.0.1:8085` (loopback only) | | Local URL | `http://127.0.0.1:8085` (loopback only) |
| WebDAV inbox | `https://kuhy-cloud.duckdns.org/RunnerUp` → `~/cloud/RunnerUp/` | | WebDAV inbox | `https://kuhy-cloud.duckdns.org/RunnerUp` → `~/data/cloud/RunnerUp/` |
| Runtime data | `/var/opt/endurain/` | | Runtime data | `/var/opt/endurain/` |
| Ledger | `~/.local/state/endurain-import/ledger.json` | | Ledger | `~/.local/state/endurain-import/ledger.json` |
| Importer code | `../python_pkg/endurain_import/` (repo requires Python under `python_pkg/`) | | Importer code | `../python_pkg/endurain_import/` (repo requires Python under `python_pkg/`) |
@ -23,9 +23,9 @@ unlock authority.
## Flow ## Flow
``` ```
phone --(WebDAV, manual tap)--> dufs ~/cloud/RunnerUp/ --\ phone --(WebDAV, manual tap)--> dufs ~/data/cloud/RunnerUp/ --\
>-- importer --> Endurain >-- importer --> Endurain
phone --(adb pull, fallback)--> ~/cloud/RunnerUp/ -------/ phone --(adb pull, fallback)--> ~/data/cloud/RunnerUp/ -------/
``` ```
A systemd user timer (`endurain-import.timer`) runs every 15 min. A systemd user timer (`endurain-import.timer`) runs every 15 min.
@ -48,7 +48,7 @@ cd .. && .venv/bin/python -m pytest python_pkg/endurain_import/tests/ -q # 52 t
- **The WebDAV URL must not end in a slash.** RunnerUp builds the target as - **The WebDAV URL must not end in a slash.** RunnerUp builds the target as
`url + fileBase + fileExt` where `fileBase` already starts with `/`. `url + fileBase + fileExt` where `fileBase` already starts with `/`.
- **`MKCOL` is not supported by dufs.** RunnerUp `PROPFIND`s the directory and - **`MKCOL` is not supported by dufs.** RunnerUp `PROPFIND`s the directory and
only issues `MKCOL` on 404, so `~/cloud/RunnerUp/` must exist on disk or only issues `MKCOL` on 404, so `~/data/cloud/RunnerUp/` must exist on disk or
adding the account fails with an opaque generic error (upstream #1147/#1172). adding the account fails with an opaque generic error (upstream #1147/#1172).
- **HTTPS is mandatory.** RunnerUp targets `targetSdk 36` with no - **HTTPS is mandatory.** RunnerUp targets `targetSdk 36` with no
`usesCleartextTraffic`, so Android blocks plain HTTP. Self-signed certs are `usesCleartextTraffic`, so Android blocks plain HTTP. Self-signed certs are

View File

@ -20,7 +20,7 @@ SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_NAME readonly SCRIPT_NAME
readonly APP_URL="http://127.0.0.1:8085" readonly APP_URL="http://127.0.0.1:8085"
readonly PUBLIC_HOST="endurain.kuhy.duckdns.org" readonly PUBLIC_HOST="endurain.kuhy.duckdns.org"
readonly EDGE_DIR="${HOME}/gitea" readonly EDGE_DIR="${HOME}/services/gitea"
readonly SITE_FILE="${EDGE_DIR}/sites/endurain.caddy" readonly SITE_FILE="${EDGE_DIR}/sites/endurain.caddy"
readonly EDGE_CONTAINER="gitea-caddy" readonly EDGE_CONTAINER="gitea-caddy"

View File

@ -1,6 +1,6 @@
[Unit] [Unit]
Description=Import RunnerUp activities into Endurain Description=Import RunnerUp activities into Endurain
Documentation=file:///home/kuhy/testsAndMisc/endurain/README.md Documentation=file:///home/kuhy/src/testsAndMisc/endurain/README.md
# Endurain runs in Docker; without this the timer fires during boot before the # Endurain runs in Docker; without this the timer fires during boot before the
# stack is listening and logs a spurious "not reachable" failure. # stack is listening and logs a spurious "not reachable" failure.
After=network-online.target docker.service After=network-online.target docker.service
@ -8,9 +8,9 @@ Wants=network-online.target
[Service] [Service]
Type=oneshot Type=oneshot
WorkingDirectory=/home/kuhy/testsAndMisc WorkingDirectory=/home/kuhy/src/testsAndMisc
Environment=ENDURAIN_URL=http://127.0.0.1:8085 Environment=ENDURAIN_URL=http://127.0.0.1:8085
Environment=ENDURAIN_INBOX=/home/kuhy/cloud/RunnerUp Environment=ENDURAIN_INBOX=/home/kuhy/data/cloud/RunnerUp
Environment=ENDURAIN_STATE=/home/kuhy/.local/state/endurain-import Environment=ENDURAIN_STATE=/home/kuhy/.local/state/endurain-import
Environment=ENDURAIN_BULK_DIR=/var/opt/endurain/backend/data/activity_files/bulk_import Environment=ENDURAIN_BULK_DIR=/var/opt/endurain/backend/data/activity_files/bulk_import
# Two adb devices are usually attached (USB phone + a network target), and the # Two adb devices are usually attached (USB phone + a network target), and the
@ -18,7 +18,7 @@ Environment=ENDURAIN_BULK_DIR=/var/opt/endurain/backend/data/activity_files/bulk
Environment=ENDURAIN_ADB_SERIAL=23181JEGR08034 Environment=ENDURAIN_ADB_SERIAL=23181JEGR08034
# The API key is read from a mode-600 file rather than embedded here: systemd # The API key is read from a mode-600 file rather than embedded here: systemd
# unit files are world-readable. # unit files are world-readable.
ExecStart=/usr/bin/bash -c 'ENDURAIN_API_KEY="$(cat /home/kuhy/testsAndMisc/endurain/.api_key)" /home/kuhy/testsAndMisc/.venv/bin/python -m python_pkg.endurain_import' ExecStart=/usr/bin/bash -c 'ENDURAIN_API_KEY="$(cat /home/kuhy/src/testsAndMisc/endurain/.api_key)" /home/kuhy/src/testsAndMisc/.venv/bin/python -m python_pkg.endurain_import'
[Install] [Install]
WantedBy=default.target WantedBy=default.target

View File

@ -15,8 +15,9 @@ printf ' ✓ toolchain complete\n'
printf 'Running auto-fixers and shell_check before committing...\n' printf 'Running auto-fixers and shell_check before committing...\n'
# Get list of staged shell files # Get list of staged shell files. zsh is deliberately excluded: neither shfmt
mapfile -t staged_files < <(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(sh|bash|zsh)$' || true) # nor shellcheck parses zsh (SC1071), so .zsh files get `zsh -n` further down.
mapfile -t staged_files < <(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(sh|bash)$' || true)
if [[ ${#staged_files[@]} -gt 0 ]]; then if [[ ${#staged_files[@]} -gt 0 ]]; then
printf 'Auto-fixing %d shell file(s)...\n' "${#staged_files[@]}" printf 'Auto-fixing %d shell file(s)...\n' "${#staged_files[@]}"
@ -44,7 +45,7 @@ printf 'Running shell_check validation...\n'
# Run shell_check to validate all checks # Run shell_check to validate all checks
# Note: We only validate the staged files since we just auto-formatted them # Note: We only validate the staged files since we just auto-formatted them
# Validate only shellcheck, not shfmt (we already applied formatting) # Validate only shellcheck, not shfmt (we already applied formatting)
mapfile -d '' -t staged_shell_files < <(git diff --cached --name-only --diff-filter=ACM -z | grep -zE '\.(sh|bash|zsh)$' || true) mapfile -d '' -t staged_shell_files < <(git diff --cached --name-only --diff-filter=ACM -z | grep -zE '\.(sh|bash)$' || true)
if [[ ${#staged_shell_files[@]} -gt 0 ]]; then if [[ ${#staged_shell_files[@]} -gt 0 ]]; then
# Run shellcheck on staged files (guaranteed present by verify_toolchain) # Run shellcheck on staged files (guaranteed present by verify_toolchain)
@ -57,6 +58,15 @@ if [[ ${#staged_shell_files[@]} -gt 0 ]]; then
fi fi
fi fi
# zsh files: syntax check only, with the shell that will actually run them.
mapfile -d '' -t staged_zsh_files < <(git diff --cached --name-only --diff-filter=ACM -z | grep -zE '\.zsh$' || true)
for file in "${staged_zsh_files[@]}"; do
if ! zsh -n "$file"; then
printf '\nCommit aborted: zsh -n found a syntax error in %s.\n' "$file" >&2
exit 1
fi
done
printf 'shell_check passed.\n' printf 'shell_check passed.\n'
# Run duplicate code detection # Run duplicate code detection

View File

@ -24,7 +24,7 @@ This repo automates Linux desktop bootstrap, hardening, and i3 setup. It’s pri
## Core workflows (what to run) ## Core workflows (what to run)
- Fresh machine: run from repo root - Fresh machine: run from repo root
- `fresh-install/main.sh` (bootstraps configs, GPU, hosts, i3, pacman wrapper, services). It assumes the repo is at `~/linux-configuration` in some steps. - `fresh-install/main.sh` (bootstraps configs, GPU, hosts, i3, pacman wrapper, services). It assumes the repo is at `~/src/testsAndMisc/linux_configuration` in some steps.
- Periodic services: `sudo periodic_background/setup_periodic_system.sh` (installs timer, startup service, hosts monitor, and browser pre-exec wrapper; then performs an initial run). - Periodic services: `sudo periodic_background/setup_periodic_system.sh` (installs timer, startup service, hosts monitor, and browser pre-exec wrapper; then performs an initial run).
- Pacman wrapper only: `sudo periodic_background/digital_wellbeing/pacman/install_pacman_wrapper.sh` (backs up pacman and wires the wrapper). The wrapper auto-runs hosts-guard pre/post hooks and can self-setup periodic services when missing. - Pacman wrapper only: `sudo periodic_background/digital_wellbeing/pacman/install_pacman_wrapper.sh` (backs up pacman and wires the wrapper). The wrapper auto-runs hosts-guard pre/post hooks and can self-setup periodic services when missing.
- Hosts guard: - Hosts guard:
@ -66,4 +66,4 @@ For in-depth understanding of specific components, see these dedicated guides:
| Midnight Shutdown | Auto-shutdown at night | `periodic_background/digital_wellbeing/setup_midnight_shutdown.sh` | | Midnight Shutdown | Auto-shutdown at night | `periodic_background/digital_wellbeing/setup_midnight_shutdown.sh` |
| Compulsive Block | Limit app launches | `periodic_background/digital_wellbeing/block_compulsive_opening.sh` | | Compulsive Block | Limit app launches | `periodic_background/digital_wellbeing/block_compulsive_opening.sh` |
| Music Wrapper | Block music during focus | `periodic_background/digital_wellbeing/youtube-music-wrapper.sh` | | Music Wrapper | Block music during focus | `periodic_background/digital_wellbeing/youtube-music-wrapper.sh` |
| Screen Locker | Require workout to unlock | External: `~/testsAndMisc/python_pkg/screen_locker/` | | Screen Locker | Require workout to unlock | External: `~/src/testsAndMisc/python_pkg/screen_locker/` |

View File

@ -57,7 +57,7 @@
### 3. Screen Locker (Python - External Repo) ### 3. Screen Locker (Python - External Repo)
**File:** `/home/kuhy/testsAndMisc/python_pkg/screen_locker/screen_lock.py` **File:** `/home/kuhy/src/testsAndMisc/python_pkg/screen_locker/screen_lock.py`
**Current Workout Types:** **Current Workout Types:**

View File

@ -8,7 +8,7 @@
```` ````
I need to implement comprehensive security hardening for a Linux digital wellbeing system. I need to implement comprehensive security hardening for a Linux digital wellbeing system.
The codebase is at ~/linux-configuration/ with these components needing changes: The codebase is at ~/src/testsAndMisc/linux_configuration/ with these components needing changes:
## 1. HOSTS PROTECTION - nsswitch.conf Guard ## 1. HOSTS PROTECTION - nsswitch.conf Guard
@ -42,7 +42,7 @@ Changes needed:
## 3. SCREEN LOCKER - External Repo ## 3. SCREEN LOCKER - External Repo
Location: ~/testsAndMisc/python_pkg/screen_locker/screen_lock.py Location: ~/src/testsAndMisc/python_pkg/screen_locker/screen_lock.py
Changes needed: Changes needed:
- REMOVE the "Running" workout option entirely (too easy to fake) - REMOVE the "Running" workout option entirely (too easy to fake)
@ -173,7 +173,7 @@ Modified files:
External repo (separate changes): External repo (separate changes):
- ~/testsAndMisc/python_pkg/screen_locker/screen_lock.py (remove running, harden table tennis) - ~/src/testsAndMisc/python_pkg/screen_locker/screen_lock.py (remove running, harden table tennis)
``` ```

View File

@ -167,7 +167,7 @@ LIMITATION (needs fix):
You are an expert on the screen_lock.py workout locker. You understand: You are an expert on the screen_lock.py workout locker. You understand:
FILE LOCATION: ~/testsAndMisc/python_pkg/screen_locker/screen_lock.py (1261 lines) FILE LOCATION: ~/src/testsAndMisc/python_pkg/screen_locker/screen_lock.py (1261 lines)
PURPOSE: PURPOSE:

View File

@ -39,8 +39,8 @@ systemctl status hosts-guard.path hosts-bind-mount.service
sudo /usr/local/sbin/unlock-hosts sudo /usr/local/sbin/unlock-hosts
# Reinstall/repair # Reinstall/repair
sudo ~/linux-configuration/hosts/install.sh sudo ~/src/hosts-blocker/install.sh
sudo ~/linux-configuration/hosts/guard/setup_hosts_guard.sh sudo ~/src/hosts-blocker/guard/setup_hosts_guard.sh
```` ````
## DO NOT ## DO NOT

View File

@ -51,7 +51,7 @@ else
fi fi
# Test 5: google-chrome is blocked # Test 5: google-chrome is blocked
if grep -qi "google-chrome" ~/linux-configuration/scripts/periodic_background/digital_wellbeing/pacman/pacman_blocked_keywords.txt; then if grep -qi "google-chrome" ~/src/testsAndMisc/linux_configuration/scripts/periodic_background/digital_wellbeing/pacman/pacman_blocked_keywords.txt; then
test_result "google-chrome in blocked list" "pass" test_result "google-chrome in blocked list" "pass"
else else
test_result "google-chrome in blocked list" "fail" test_result "google-chrome in blocked list" "fail"

View File

@ -20,7 +20,7 @@ command -v simplenote >/dev/null 2>&1 && simplenote &
[ -x "$HOME/.fehbg" ] && "$HOME/.fehbg" & [ -x "$HOME/.fehbg" ] && "$HOME/.fehbg" &
# repo helpers (real current paths under single_use/utils); guarded in case moved # repo helpers (real current paths under single_use/utils); guarded in case moved
idle="$HOME/testsAndMisc/linux_configuration/utils/turn_off_auto_idle_screen_shutdown.sh" idle="$HOME/src/testsAndMisc/linux_configuration/utils/turn_off_auto_idle_screen_shutdown.sh"
[ -x "$idle" ] && bash "$idle" --watch-controller & [ -x "$idle" ] && bash "$idle" --watch-controller &
# lightweight status bar (root window name) # lightweight status bar (root window name)

View File

@ -201,13 +201,13 @@ static const Key keys[] = {
* backend as the media keys, with a notification and NO pacman side-effects. * backend as the media keys, with a notification and NO pacman side-effects.
* (toggle_mic.sh is deliberately avoided here: it runs `sudo pacman -S dunst` * (toggle_mic.sh is deliberately avoided here: it runs `sudo pacman -S dunst`
* if dunst is missing, which deadlocks pacman when spawned without a tty.) */ * if dunst is missing, which deadlocks pacman when spawned without a tty.) */
{ MODKEY, XK_m, spawn, SHCMD("$HOME/testsAndMisc/linux_configuration/utils/volume_control.sh micmute") }, { MODKEY, XK_m, spawn, SHCMD("$HOME/src/testsAndMisc/linux_configuration/utils/volume_control.sh micmute") },
/* media keys — routed through volume_control.sh, same as the (now-fixed) i3 config */ /* media keys — routed through volume_control.sh, same as the (now-fixed) i3 config */
{ 0, XF86XK_AudioRaiseVolume, spawn, SHCMD("$HOME/testsAndMisc/linux_configuration/utils/volume_control.sh up") }, { 0, XF86XK_AudioRaiseVolume, spawn, SHCMD("$HOME/src/testsAndMisc/linux_configuration/utils/volume_control.sh up") },
{ 0, XF86XK_AudioLowerVolume, spawn, SHCMD("$HOME/testsAndMisc/linux_configuration/utils/volume_control.sh down") }, { 0, XF86XK_AudioLowerVolume, spawn, SHCMD("$HOME/src/testsAndMisc/linux_configuration/utils/volume_control.sh down") },
{ 0, XF86XK_AudioMute, spawn, SHCMD("$HOME/testsAndMisc/linux_configuration/utils/volume_control.sh mute") }, { 0, XF86XK_AudioMute, spawn, SHCMD("$HOME/src/testsAndMisc/linux_configuration/utils/volume_control.sh mute") },
{ 0, XF86XK_AudioMicMute, spawn, SHCMD("$HOME/testsAndMisc/linux_configuration/utils/volume_control.sh micmute") }, { 0, XF86XK_AudioMicMute, spawn, SHCMD("$HOME/src/testsAndMisc/linux_configuration/utils/volume_control.sh micmute") },
/* tags 1-10 (i3 workspaces 1-10) */ /* tags 1-10 (i3 workspaces 1-10) */
TAGKEYS( XK_1, 0) TAGKEYS( XK_1, 0)

View File

@ -17,7 +17,7 @@
set -euo pipefail set -euo pipefail
CANONICAL="${KP_CANONICAL:-$HOME/cloud/Keepass/Passwords.kdbx}" CANONICAL="${KP_CANONICAL:-$HOME/data/cloud/Keepass/Passwords.kdbx}"
SEARCH_ROOT="${KP_SEARCH_ROOT:-$HOME}" SEARCH_ROOT="${KP_SEARCH_ROOT:-$HOME}"
DRY_RUN=0 DRY_RUN=0
STATE_DIR="${KP_STATE_DIR:-$HOME/.config/keepass-sync}" STATE_DIR="${KP_STATE_DIR:-$HOME/.config/keepass-sync}"
@ -25,16 +25,34 @@ STATE_DIR="${KP_STATE_DIR:-$HOME/.config/keepass-sync}"
EXCLUDE_ARGS=() EXCLUDE_ARGS=()
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
--canonical) CANONICAL="$2"; shift 2 ;; --canonical)
--root) SEARCH_ROOT="$2"; shift 2 ;; CANONICAL="$2"
--exclude) EXCLUDE_ARGS+=("$2"); shift 2 ;; shift 2
--dry-run|-n) DRY_RUN=1; shift ;; ;;
*) echo "unknown arg: $1" >&2; exit 2 ;; --root)
SEARCH_ROOT="$2"
shift 2
;;
--exclude)
EXCLUDE_ARGS+=("$2")
shift 2
;;
--dry-run | -n)
DRY_RUN=1
shift
;;
*)
echo "unknown arg: $1" >&2
exit 2
;;
esac esac
done done
log() { printf '[keepass-consolidate] %s\n' "$*" >&2; } log() { printf '[keepass-consolidate] %s\n' "$*" >&2; }
die() { printf '[keepass-consolidate] ERROR: %s\n' "$*" >&2; exit 1; } die() {
printf '[keepass-consolidate] ERROR: %s\n' "$*" >&2
exit 1
}
MASTER_PW="${KP_MASTER_PW:-}" MASTER_PW="${KP_MASTER_PW:-}"
[[ -n "$MASTER_PW" ]] || die "KP_MASTER_PW not set (needed to open/merge vaults)" [[ -n "$MASTER_PW" ]] || die "KP_MASTER_PW not set (needed to open/merge vaults)"
@ -47,18 +65,23 @@ FD="$(command -v fd || command -v fdfind || true)"
opens() { printf '%s\n' "$MASTER_PW" | keepassxc-cli ls "$1" >/dev/null 2>&1; } opens() { printf '%s\n' "$MASTER_PW" | keepassxc-cli ls "$1" >/dev/null 2>&1; }
opens "$CANONICAL" || die "master password does not open the canonical vault — aborting" opens "$CANONICAL" || die "master password does not open the canonical vault — aborting"
backup() { # backup <file> — copy into a timestamped sibling .backup_ dir backup() { # backup <file> — copy into a timestamped sibling .backup_ dir
local f="$1" bdir local f="$1" bdir
bdir="$(dirname "$f")/.backup_$(date +%Y%m%d_%H%M%S)_consolidate" bdir="$(dirname "$f")/.backup_$(date +%Y%m%d_%H%M%S)_consolidate"
mkdir -p "$bdir"; cp -f "$f" "$bdir/" mkdir -p "$bdir"
cp -f "$f" "$bdir/"
} }
# --- exclude prefixes: dirs whose vaults are NOT strays ----------------------- # --- exclude prefixes: dirs whose vaults are NOT strays -----------------------
# Critically the dufs serve-path: on the machine running dufs that directory IS # Critically the dufs serve-path: on the machine running dufs that directory IS
# the remote canonical store (e.g. ~/cloud/Keepass) — it must never be treated # the remote canonical store (e.g. ~/data/cloud/Keepass) — it must never be treated
# as a stray and removed. Plus any --exclude args and KP_EXCLUDE (colon-list). # as a stray and removed. Plus any --exclude args and KP_EXCLUDE (colon-list).
EXCLUDES=() EXCLUDES=()
_add_exclude() { local p; p="$(readlink -f "$1" 2>/dev/null || echo "$1")"; [[ -n "$p" ]] && EXCLUDES+=("$p"); } _add_exclude() {
local p
p="$(readlink -f "$1" 2>/dev/null || echo "$1")"
[[ -n "$p" ]] && EXCLUDES+=("$p")
}
_dufs_cfg="$HOME/.config/dufs/dufs.yaml" _dufs_cfg="$HOME/.config/dufs/dufs.yaml"
if [[ -f "$_dufs_cfg" ]]; then if [[ -f "$_dufs_cfg" ]]; then
_sp="$(sed -nE 's/^serve-path:[[:space:]]*//p' "$_dufs_cfg" | head -1)" _sp="$(sed -nE 's/^serve-path:[[:space:]]*//p' "$_dufs_cfg" | head -1)"
@ -67,7 +90,7 @@ fi
IFS=':' read -r -a _kp_excl <<<"${KP_EXCLUDE:-}" IFS=':' read -r -a _kp_excl <<<"${KP_EXCLUDE:-}"
for e in "${_kp_excl[@]}" "${EXCLUDE_ARGS[@]}"; do [[ -n "$e" ]] && _add_exclude "$e"; done for e in "${_kp_excl[@]}" "${EXCLUDE_ARGS[@]}"; do [[ -n "$e" ]] && _add_exclude "$e"; done
is_excluded() { # is_excluded <abs-path> — under any excluded prefix? is_excluded() { # is_excluded <abs-path> — under any excluded prefix?
local p="$1" ex local p="$1" ex
for ex in "${EXCLUDES[@]}"; do for ex in "${EXCLUDES[@]}"; do
[[ "$p" == "$ex" || "$p" == "$ex"/* ]] && return 0 [[ "$p" == "$ex" || "$p" == "$ex"/* ]] && return 0
@ -90,8 +113,8 @@ STRAYS=()
for f in "${CANDIDATES[@]}"; do for f in "${CANDIDATES[@]}"; do
[[ -f "$f" ]] || continue [[ -f "$f" ]] || continue
local_abs="$(readlink -f "$f")" local_abs="$(readlink -f "$f")"
[[ "$local_abs" == "$CANON_ABS" ]] && continue # the canonical itself [[ "$local_abs" == "$CANON_ABS" ]] && continue # the canonical itself
is_excluded "$local_abs" && continue # dufs store / excluded dirs is_excluded "$local_abs" && continue # dufs store / excluded dirs
# Skip our own timestamped backups defensively (in case fd glob missed). # Skip our own timestamped backups defensively (in case fd glob missed).
[[ "$f" == *"/.backup_"* ]] && continue [[ "$f" == *"/.backup_"* ]] && continue
STRAYS+=("$f") STRAYS+=("$f")

View File

@ -22,7 +22,7 @@ trap _t_teardown EXIT
# shellcheck source=../wg_verify_units.sh # shellcheck source=../wg_verify_units.sh
. "$SCRIPT_DIR/../wg_verify_units.sh" . "$SCRIPT_DIR/../wg_verify_units.sh"
readonly ENTRY="/home/kuhy/testsAndMisc/linux_configuration/features/setup_wireguard_ssh.sh" readonly ENTRY="/home/kuhy/src/testsAndMisc/linux_configuration/features/setup_wireguard_ssh.sh"
UNIT_DIR="$TEST_TMPDIR/units" UNIT_DIR="$TEST_TMPDIR/units"
mkdir -p "$UNIT_DIR" mkdir -p "$UNIT_DIR"

View File

@ -27,8 +27,8 @@ source "$SCRIPT_DIR/../lib/common.sh"
readonly GITEA_DOMAIN="kuhy.duckdns.org" readonly GITEA_DOMAIN="kuhy.duckdns.org"
readonly GITEA_OWNER="kuhyx" readonly GITEA_OWNER="kuhyx"
readonly GITHUB_OWNER="kuhyx" readonly GITHUB_OWNER="kuhyx"
readonly GITEA_TOKEN_FILE="${HOME}/gitea/.admin_token" readonly GITEA_TOKEN_FILE="${HOME}/services/gitea/.admin_token"
readonly GITHUB_MIRROR_TOKEN_FILE="${HOME}/gitea/.github_mirror_token" readonly GITHUB_MIRROR_TOKEN_FILE="${HOME}/services/gitea/.github_mirror_token"
die() { die() {
log_error "$1" log_error "$1"

View File

@ -34,7 +34,7 @@ source "$SCRIPT_DIR/../lib/common.sh"
readonly GITEA_DOMAIN="gitea.kuhy.duckdns.org" readonly GITEA_DOMAIN="gitea.kuhy.duckdns.org"
readonly GITEA_ADMIN_USER="kuhyx" readonly GITEA_ADMIN_USER="kuhyx"
readonly GITEA_ADMIN_EMAIL="krzysztofrudnicki0@gmail.com" readonly GITEA_ADMIN_EMAIL="krzysztofrudnicki0@gmail.com"
readonly GITEA_DATA_DIR="${HOME}/gitea" readonly GITEA_DATA_DIR="${HOME}/services/gitea"
readonly COMPOSE_FILE="${GITEA_DATA_DIR}/docker-compose.yml" readonly COMPOSE_FILE="${GITEA_DATA_DIR}/docker-compose.yml"
readonly CADDYFILE="${GITEA_DATA_DIR}/Caddyfile" readonly CADDYFILE="${GITEA_DATA_DIR}/Caddyfile"
# Per-service Caddy site blocks live here as individual *.caddy snippets so # Per-service Caddy site blocks live here as individual *.caddy snippets so

View File

@ -42,14 +42,14 @@ source "${SCRIPT_DIR}/../lib/common.sh"
readonly DICE_DOMAIN="dice.kuhy.duckdns.org" readonly DICE_DOMAIN="dice.kuhy.duckdns.org"
readonly DICE_PORT="8089" readonly DICE_PORT="8089"
# The solver was extracted to its own repo; this script only deploys it. # The solver was extracted to its own repo; this script only deploys it.
readonly DICE_SRC="${HOME}/kcd2-dice-solver" readonly DICE_SRC="${HOME}/src/kcd2-dice-solver"
readonly REPO_ROOT="${DICE_SRC}" readonly REPO_ROOT="${DICE_SRC}"
readonly DICE_BUILD="${DICE_SRC}/deploy_build.sh" readonly DICE_BUILD="${DICE_SRC}/deploy_build.sh"
readonly DICE_DATA_DIR="${HOME}/kcd2-dice-serve" readonly DICE_DATA_DIR="${HOME}/services/kcd2-dice-serve"
readonly DICE_COMPOSE="${DICE_DATA_DIR}/docker-compose.yml" readonly DICE_COMPOSE="${DICE_DATA_DIR}/docker-compose.yml"
readonly DICE_INNER_CADDY="${DICE_DATA_DIR}/Caddyfile" readonly DICE_INNER_CADDY="${DICE_DATA_DIR}/Caddyfile"
# Shared edge owned by setup_gitea.sh. # Shared edge owned by setup_gitea.sh.
readonly GITEA_DATA_DIR="${HOME}/gitea" readonly GITEA_DATA_DIR="${HOME}/services/gitea"
readonly CADDYFILE="${GITEA_DATA_DIR}/Caddyfile" readonly CADDYFILE="${GITEA_DATA_DIR}/Caddyfile"
readonly SITES_DIR="${GITEA_DATA_DIR}/sites" readonly SITES_DIR="${GITEA_DATA_DIR}/sites"
readonly DICE_SNIPPET="${SITES_DIR}/dice.caddy" readonly DICE_SNIPPET="${SITES_DIR}/dice.caddy"

View File

@ -2,7 +2,7 @@
# setup_keepass_sync.sh — set up "one KeePass vault, synced everywhere". # setup_keepass_sync.sh — set up "one KeePass vault, synced everywhere".
# #
# IDENTICAL on every Linux device (PC and laptop alike): each keeps one local # IDENTICAL on every Linux device (PC and laptop alike): each keeps one local
# working vault at ~/Keepass/Passwords.kdbx and syncs it to the canonical copy # working vault at ~/data/Keepass/Passwords.kdbx and syncs it to the canonical copy
# on the dufs server over WebDAV, at the moment you open KeePass. You open the # on the dufs server over WebDAV, at the moment you open KeePass. You open the
# vault with a single command — `keepass-open` — on every machine. The phone # vault with a single command — `keepass-open` — on every machine. The phone
# uses KeePassDX (same idea: enter password → Synchronize over WebDAV → use). # uses KeePassDX (same idea: enter password → Synchronize over WebDAV → use).
@ -22,13 +22,19 @@ readonly CONFIG_DIR="$HOME/.config/keepass-sync"
readonly CONFIG="$CONFIG_DIR/config.env" readonly CONFIG="$CONFIG_DIR/config.env"
readonly BIN_DIR="$HOME/.local/bin" readonly BIN_DIR="$HOME/.local/bin"
readonly KEYRING_SERVICE="${KP_KEYRING_SERVICE:-keepass-sync}" readonly KEYRING_SERVICE="${KP_KEYRING_SERVICE:-keepass-sync}"
readonly LOCAL_DB="$HOME/Keepass/Passwords.kdbx" readonly LOCAL_DB="$HOME/data/Keepass/Passwords.kdbx"
C() { printf '\033[1;34m[keepass-setup]\033[0m %s\n' "$*"; } C() { printf '\033[1;34m[keepass-setup]\033[0m %s\n' "$*"; }
OK() { printf '\033[1;32m ✓\033[0m %s\n' "$*"; } OK() { printf '\033[1;32m ✓\033[0m %s\n' "$*"; }
WARN() { printf '\033[1;33m !\033[0m %s\n' "$*"; } WARN() { printf '\033[1;33m !\033[0m %s\n' "$*"; }
die() { printf '\033[1;31m[keepass-setup] ERROR:\033[0m %s\n' "$*" >&2; exit 1; } die() {
usage() { grep -E '^#( |$)' "$0" | sed -E 's/^# ?//'; exit 0; } printf '\033[1;31m[keepass-setup] ERROR:\033[0m %s\n' "$*" >&2
exit 1
}
usage() {
grep -E '^#( |$)' "$0" | sed -E 's/^# ?//'
exit 0
}
install_deps() { install_deps() {
C "Installing dependencies" C "Installing dependencies"
@ -55,7 +61,8 @@ main() {
read -r -p "dufs vault URL [https://kuhy-cloud.duckdns.org/Keepass/Passwords.kdbx]: " url read -r -p "dufs vault URL [https://kuhy-cloud.duckdns.org/Keepass/Passwords.kdbx]: " url
url="${url:-https://kuhy-cloud.duckdns.org/Keepass/Passwords.kdbx}" url="${url:-https://kuhy-cloud.duckdns.org/Keepass/Passwords.kdbx}"
read -r -p "dufs web username: " user read -r -p "dufs web username: " user
read -r -s -p "dufs web password (server credential — cached in keyring): " dpass; echo read -r -s -p "dufs web password (server credential — cached in keyring): " dpass
echo
[[ -n "$user" && -n "$dpass" ]] || die "dufs username/password required" [[ -n "$user" && -n "$dpass" ]] || die "dufs username/password required"
printf '%s' "$dpass" | secret-tool store --label='dufs server password (keepass-sync)' \ printf '%s' "$dpass" | secret-tool store --label='dufs server password (keepass-sync)' \
service "$KEYRING_SERVICE" key dufs service "$KEYRING_SERVICE" key dufs
@ -86,8 +93,8 @@ main() {
# --- retire the old one-way mirror unit (dufs host only) ---------------- # --- retire the old one-way mirror unit (dufs host only) ----------------
# is-enabled/is-active are pipe-free and work as non-root (unlike # is-enabled/is-active are pipe-free and work as non-root (unlike
# `list-unit-files | grep -q`, which misfires under set -o pipefail). # `list-unit-files | grep -q`, which misfires under set -o pipefail).
if systemctl is-enabled keepass-cloud-sync.path >/dev/null 2>&1 \ if systemctl is-enabled keepass-cloud-sync.path >/dev/null 2>&1 ||
|| systemctl is-active keepass-cloud-sync.path >/dev/null 2>&1; then systemctl is-active keepass-cloud-sync.path >/dev/null 2>&1; then
C "Retiring the old one-way keepass-cloud-sync units" C "Retiring the old one-way keepass-cloud-sync units"
sudo systemctl disable --now keepass-cloud-sync.path keepass-cloud-sync.service 2>/dev/null || true sudo systemctl disable --now keepass-cloud-sync.path keepass-cloud-sync.service 2>/dev/null || true
OK "old one-way sync retired" OK "old one-way sync retired"

View File

@ -43,14 +43,14 @@ readonly WEBSITE_DOMAIN="kuhy.duckdns.org"
readonly GITEA_DOMAIN="gitea.kuhy.duckdns.org" readonly GITEA_DOMAIN="gitea.kuhy.duckdns.org"
readonly WEBSITE_PORT="8088" readonly WEBSITE_PORT="8088"
# React/TS source lives in its own standalone repo (github.com/kuhyx/personal-website), # React/TS source lives in its own standalone repo (github.com/kuhyx/personal-website),
# cloned at ~/personal-website; the runtime data dir holds the generated compose + # cloned at ~/src/personal-website; the runtime data dir holds the generated compose +
# inner Caddyfile. # inner Caddyfile.
readonly WEBSITE_SRC="${HOME}/personal-website" readonly WEBSITE_SRC="${HOME}/src/personal-website"
readonly WEBSITE_DATA_DIR="${HOME}/personal-website-serve" readonly WEBSITE_DATA_DIR="${HOME}/services/personal-website-serve"
readonly WEBSITE_COMPOSE="${WEBSITE_DATA_DIR}/docker-compose.yml" readonly WEBSITE_COMPOSE="${WEBSITE_DATA_DIR}/docker-compose.yml"
readonly WEBSITE_INNER_CADDY="${WEBSITE_DATA_DIR}/Caddyfile" readonly WEBSITE_INNER_CADDY="${WEBSITE_DATA_DIR}/Caddyfile"
# Shared edge owned by setup_gitea.sh. # Shared edge owned by setup_gitea.sh.
readonly GITEA_DATA_DIR="${HOME}/gitea" readonly GITEA_DATA_DIR="${HOME}/services/gitea"
readonly CADDYFILE="${GITEA_DATA_DIR}/Caddyfile" readonly CADDYFILE="${GITEA_DATA_DIR}/Caddyfile"
readonly SITES_DIR="${GITEA_DATA_DIR}/sites" readonly SITES_DIR="${GITEA_DATA_DIR}/sites"
readonly WEBSITE_SNIPPET="${SITES_DIR}/website.caddy" readonly WEBSITE_SNIPPET="${SITES_DIR}/website.caddy"
@ -60,8 +60,8 @@ readonly GITEA_SCRIPT="${SCRIPT_DIR}/setup_gitea.sh"
readonly WG_SCRIPT="${SCRIPT_DIR}/setup_wireguard_ssh.sh" readonly WG_SCRIPT="${SCRIPT_DIR}/setup_wireguard_ssh.sh"
# Canonical CV data (rendered by the CV repo's build_cv.py); copied into the # Canonical CV data (rendered by the CV repo's build_cv.py); copied into the
# website build. A committed copy in the repo keeps the build standalone. # website build. A committed copy in the repo keeps the build standalone.
readonly CV_JSON_SRC="${HOME}/CV/generic/cv.json" readonly CV_JSON_SRC="${HOME}/src/CV/generic/cv.json"
readonly CV_PDF_SRC="${HOME}/CV/generic/cv-en.pdf" readonly CV_PDF_SRC="${HOME}/src/CV/generic/cv-en.pdf"
die() { die() {
log_error "$1" log_error "$1"

View File

@ -11,7 +11,7 @@
# #
# What it does (idempotent — safe to re-run): # What it does (idempotent — safe to re-run):
# - Installs runtime deps. # - Installs runtime deps.
# - Writes ~/searxng/{docker-compose.yml,core-config/settings.yml}. # - Writes ~/services/searxng/{docker-compose.yml,core-config/settings.yml}.
# - Runs valkey (limiter backend) on 127.0.0.1:6379. # - Runs valkey (limiter backend) on 127.0.0.1:6379.
# - Runs searxng on 127.0.0.1:8090, both with network_mode: host. # - Runs searxng on 127.0.0.1:8090, both with network_mode: host.
# - Adds a reverse-proxy + security-header snippet to the shared edge. # - Adds a reverse-proxy + security-header snippet to the shared edge.
@ -53,13 +53,13 @@ readonly VALKEY_PORT="6379"
# searxng/searxng:latest as of 2026-08-09 == release 2026.8.4. See the comment # searxng/searxng:latest as of 2026-08-09 == release 2026.8.4. See the comment
# on the image: line in the compose heredoc for why this is pinned. # on the image: line in the compose heredoc for why this is pinned.
readonly SEARX_IMAGE="searxng/searxng@sha256:f4c8e59de166ed71f6380c0847c312ca51f0d41996e31d0559163b6b09ecde52" readonly SEARX_IMAGE="searxng/searxng@sha256:f4c8e59de166ed71f6380c0847c312ca51f0d41996e31d0559163b6b09ecde52"
readonly SEARX_DATA_DIR="${HOME}/searxng" readonly SEARX_DATA_DIR="${HOME}/services/searxng"
readonly SEARX_COMPOSE="${SEARX_DATA_DIR}/docker-compose.yml" readonly SEARX_COMPOSE="${SEARX_DATA_DIR}/docker-compose.yml"
readonly SEARX_CONFIG_DIR="${SEARX_DATA_DIR}/core-config" readonly SEARX_CONFIG_DIR="${SEARX_DATA_DIR}/core-config"
readonly SEARX_SETTINGS="${SEARX_CONFIG_DIR}/settings.yml" readonly SEARX_SETTINGS="${SEARX_CONFIG_DIR}/settings.yml"
readonly SEARX_LIMITER="${SEARX_CONFIG_DIR}/limiter.toml" readonly SEARX_LIMITER="${SEARX_CONFIG_DIR}/limiter.toml"
# Shared edge owned by setup_gitea.sh. # Shared edge owned by setup_gitea.sh.
readonly GITEA_DATA_DIR="${HOME}/gitea" readonly GITEA_DATA_DIR="${HOME}/services/gitea"
readonly SITES_DIR="${GITEA_DATA_DIR}/sites" readonly SITES_DIR="${GITEA_DATA_DIR}/sites"
readonly SEARX_SNIPPET="${SITES_DIR}/searx.caddy" readonly SEARX_SNIPPET="${SITES_DIR}/searx.caddy"
readonly CADDY_CONTAINER="gitea-caddy" readonly CADDY_CONTAINER="gitea-caddy"
@ -161,7 +161,6 @@ source "$SCRIPT_DIR/lib/searxng_expose.sh"
# shellcheck source=lib/searxng_status.sh # shellcheck source=lib/searxng_status.sh
source "$SCRIPT_DIR/lib/searxng_status.sh" source "$SCRIPT_DIR/lib/searxng_status.sh"
main() { main() {
local cmd="${1:-setup}" local cmd="${1:-setup}"
case "$cmd" in case "$cmd" in

View File

@ -29,13 +29,13 @@ set -euo pipefail
# SYNCYOMI_IMAGE=ghcr.io/syncyomi/syncyomi:v1.1.11 ./setup_syncyomi.sh # SYNCYOMI_IMAGE=ghcr.io/syncyomi/syncyomi:v1.1.11 ./setup_syncyomi.sh
# Any value containing a "/" is treated as a registry image and pulled as-is. # Any value containing a "/" is treated as a registry image and pulled as-is.
readonly SYNCYOMI_IMAGE="${SYNCYOMI_IMAGE:-syncyomi-kuhy:local}" readonly SYNCYOMI_IMAGE="${SYNCYOMI_IMAGE:-syncyomi-kuhy:local}"
readonly SYNCYOMI_SRC="${SYNCYOMI_SRC:-${HOME}/syncyomi-src}" readonly SYNCYOMI_SRC="${SYNCYOMI_SRC:-${HOME}/vendor/syncyomi-src}"
readonly SYNCYOMI_FORK_URL="https://github.com/kuhyx/syncyomi.git" readonly SYNCYOMI_FORK_URL="https://github.com/kuhyx/syncyomi.git"
readonly SYNCYOMI_PORT=8282 readonly SYNCYOMI_PORT=8282
readonly SYNCYOMI_LOCAL="http://127.0.0.1:${SYNCYOMI_PORT}" readonly SYNCYOMI_LOCAL="http://127.0.0.1:${SYNCYOMI_PORT}"
# --- Data dir + secrets (outside the git repo, like ~/gitea) ----------------- # --- Data dir + secrets (outside the git repo, like ~/services/gitea) -----------------
readonly DATA_DIR="${HOME}/syncyomi" readonly DATA_DIR="${HOME}/services/syncyomi"
readonly COMPOSE_FILE="${DATA_DIR}/docker-compose.yml" readonly COMPOSE_FILE="${DATA_DIR}/docker-compose.yml"
readonly CONFIG_TOML="${DATA_DIR}/config/config.toml" readonly CONFIG_TOML="${DATA_DIR}/config/config.toml"
readonly CONF_FILE="${DATA_DIR}/.syncyomi.conf" readonly CONF_FILE="${DATA_DIR}/.syncyomi.conf"
@ -43,8 +43,8 @@ readonly TOKEN_FILE="${DATA_DIR}/.api_token"
# --- Existing host infrastructure (from setup_gitea.sh / install_joplin.sh) -- # --- Existing host infrastructure (from setup_gitea.sh / install_joplin.sh) --
readonly CADDY_CONTAINER="gitea-caddy" readonly CADDY_CONTAINER="gitea-caddy"
readonly CADDYFILE="${HOME}/gitea/Caddyfile" readonly CADDYFILE="${HOME}/services/gitea/Caddyfile"
readonly CADDY_SITES_DIR="${HOME}/gitea/sites" readonly CADDY_SITES_DIR="${HOME}/services/gitea/sites"
readonly CADDYFILE_IN_CONTAINER="/etc/caddy/Caddyfile" readonly CADDYFILE_IN_CONTAINER="/etc/caddy/Caddyfile"
readonly DUCKDNS_UPDATER="${HOME}/.joplin-server/duckdns-update.sh" readonly DUCKDNS_UPDATER="${HOME}/.joplin-server/duckdns-update.sh"
@ -121,7 +121,7 @@ load_config() {
save_config() { save_config() {
# 0600 — holds the plaintext SyncYomi password so re-runs stay # 0600 — holds the plaintext SyncYomi password so re-runs stay
# non-interactive. Lives under ~/syncyomi (outside the git repo). # non-interactive. Lives under ~/services/syncyomi (outside the git repo).
umask 077 umask 077
{ {
printf 'SYNCYOMI_USER=%q\n' "${SYNCYOMI_USER}" printf 'SYNCYOMI_USER=%q\n' "${SYNCYOMI_USER}"

View File

@ -21,9 +21,9 @@ readonly REPO_ROOT
readonly UNIT_DIR="${HOME}/.config/systemd/user" readonly UNIT_DIR="${HOME}/.config/systemd/user"
readonly SERVICE_NAME="syncyomi-guard.service" readonly SERVICE_NAME="syncyomi-guard.service"
readonly TIMER_NAME="syncyomi-guard.timer" readonly TIMER_NAME="syncyomi-guard.timer"
readonly SNAPSHOT_DIR="${HOME}/syncyomi/snapshots" readonly SNAPSHOT_DIR="${HOME}/services/syncyomi/snapshots"
readonly STATE_DIR="${HOME}/.local/share/syncyomi_guard" readonly STATE_DIR="${HOME}/.local/share/syncyomi_guard"
readonly DB_PATH="${HOME}/syncyomi/config/syncyomi.db" readonly DB_PATH="${HOME}/services/syncyomi/config/syncyomi.db"
log_info() { echo "[*] $*"; } log_info() { echo "[*] $*"; }
log_ok() { echo "[+] $*"; } log_ok() { echo "[+] $*"; }

View File

@ -8,6 +8,6 @@ Description=Mirror KeePass vault into dufs cloud folder
[Service] [Service]
Type=oneshot Type=oneshot
User=__USER__ User=__USER__
# Only the vault file — not the whole ~/Keepass dir (keeps local .backup_* vaults # Only the vault file — not the whole ~/data/Keepass dir (keeps local .backup_* vaults
# off the public server). No --delete: the cloud folder may hold other files. # off the public server). No --delete: the cloud folder may hold other files.
ExecStart=/usr/bin/rsync -a __HOME__/Keepass/Passwords.kdbx __CLOUD_ROOT__/Keepass/ ExecStart=/usr/bin/rsync -a __HOME__/Keepass/Passwords.kdbx __CLOUD_ROOT__/Keepass/

View File

@ -11,8 +11,8 @@ set -euo pipefail
SERVICE_NAME="media-organizer" SERVICE_NAME="media-organizer"
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
DEFAULT_ORGANIZE_SCRIPT="/home/kuhy/testsAndMisc/linux_configuration/utils/organize_downloads.sh" DEFAULT_ORGANIZE_SCRIPT="/home/kuhy/src/testsAndMisc/linux_configuration/utils/organize_downloads.sh"
LEGACY_ORGANIZE_SCRIPT="/home/kuhy/linux-configuration/utils/organize_downloads.sh" LEGACY_ORGANIZE_SCRIPT="/home/kuhy/src/testsAndMisc/linux_configuration/utils/organize_downloads.sh"
if [[ -f $DEFAULT_ORGANIZE_SCRIPT ]]; then if [[ -f $DEFAULT_ORGANIZE_SCRIPT ]]; then
ORGANIZE_SCRIPT="$DEFAULT_ORGANIZE_SCRIPT" ORGANIZE_SCRIPT="$DEFAULT_ORGANIZE_SCRIPT"
elif [[ -f $LEGACY_ORGANIZE_SCRIPT ]]; then elif [[ -f $LEGACY_ORGANIZE_SCRIPT ]]; then

View File

@ -65,8 +65,8 @@ fix_media_organizer() {
# Find the organize_downloads.sh script # Find the organize_downloads.sh script
local script_path="" local script_path=""
local candidates=( local candidates=(
"${ORGANIZE_SCRIPT_CANDIDATES:-/home/kuhy/testsAndMisc/linux_configuration/utils/organize_downloads.sh}" "${ORGANIZE_SCRIPT_CANDIDATES:-/home/kuhy/src/testsAndMisc/linux_configuration/utils/organize_downloads.sh}"
"/home/kuhy/linux-configuration/utils/organize_downloads.sh" "/home/kuhy/src/testsAndMisc/linux_configuration/utils/organize_downloads.sh"
) )
for candidate in "${candidates[@]}"; do for candidate in "${candidates[@]}"; do
if [[ -f $candidate ]]; then if [[ -f $candidate ]]; then

View File

@ -71,7 +71,7 @@ _t_lacks "$(_t_calls)" "systemctl disable" \
# candidate paths are absolute; the first is overridden into the tmpdir and # candidate paths are absolute; the first is overridden into the tmpdir and
# the second is a real home path that does not exist on this machine. # the second is a real home path that does not exist on this machine.
arch_reset arch_reset
if [[ -f /home/kuhy/linux-configuration/utils/organize_downloads.sh ]]; then if [[ -f /home/kuhy/src/testsAndMisc/linux_configuration/utils/organize_downloads.sh ]]; then
_t_pass "fix_media_organizer: SKIP not-found case (the second candidate exists here)" _t_pass "fix_media_organizer: SKIP not-found case (the second candidate exists here)"
else else
out="$(fix_media_organizer 2>&1)" out="$(fix_media_organizer 2>&1)"

View File

@ -13,8 +13,8 @@
set -e set -e
[ "${GPU_VENDOR}" = "amd" ] || { [ "${GPU_VENDOR}" = "amd" ] || {
echo "AMD installer invoked but GPU_VENDOR=${GPU_VENDOR}" echo "AMD installer invoked but GPU_VENDOR=${GPU_VENDOR}"
exit 0 exit 0
} }
AMD_INSTALL_XF86=${AMD_INSTALL_XF86:-0} AMD_INSTALL_XF86=${AMD_INSTALL_XF86:-0}
@ -32,9 +32,9 @@ warn() { echo "[amd][warn] $*" >&2; }
# Detect multilib enabled # Detect multilib enabled
if grep -q '^\[multilib\]' /etc/pacman.conf; then if grep -q '^\[multilib\]' /etc/pacman.conf; then
MULTILIB_ENABLED=1 MULTILIB_ENABLED=1
else else
MULTILIB_ENABLED=0 MULTILIB_ENABLED=0
fi fi
# Basic packages # Basic packages
@ -58,49 +58,49 @@ LIB32_AMDVLK_PKG="lib32-amdvlk"
# Simple AUR builder (reused from NVIDIA script style) # Simple AUR builder (reused from NVIDIA script style)
_build_aur_pkg() { _build_aur_pkg() {
local pkg="$1" local pkg="$1"
local url="https://aur.archlinux.org/${pkg}.git" local url="https://aur.archlinux.org/${pkg}.git"
mkdir -p "$HOME/aur" mkdir -p "$HOME/sdk/aur"
cd "$HOME/aur" cd "$HOME/sdk/aur"
if [ ! -d "$pkg" ]; then git clone "$url"; else (cd "$pkg" && git fetch -q --all && git reset -q --hard origin/HEAD || git pull --ff-only || true); fi if [ ! -d "$pkg" ]; then git clone "$url"; else (cd "$pkg" && git fetch -q --all && git reset -q --hard origin/HEAD || git pull --ff-only || true); fi
cd "$pkg" cd "$pkg"
rm -f -- *.pkg.tar.* 2> /dev/null || true rm -f -- *.pkg.tar.* 2>/dev/null || true
yes | makepkg -s -c -C --noconfirm --needed yes | makepkg -s -c -C --noconfirm --needed
local built=(*.pkg.tar.zst) local built=(*.pkg.tar.zst)
yes | sudo pacman -U --noconfirm "${built[@]}" yes | sudo pacman -U --noconfirm "${built[@]}"
} }
_install_repo_or_aur() { _install_repo_or_aur() {
local pkg="$1" local pkg="$1"
if pacman -Si "$pkg" > /dev/null 2>&1; then if pacman -Si "$pkg" >/dev/null 2>&1; then
if pacman -Qi "$pkg" > /dev/null 2>&1; then if pacman -Qi "$pkg" >/dev/null 2>&1; then
vlog "$pkg already installed" vlog "$pkg already installed"
else else
yes | sudo pacman -Sy --noconfirm "$pkg" yes | sudo pacman -Sy --noconfirm "$pkg"
fi fi
else else
info "Building AUR package: $pkg" info "Building AUR package: $pkg"
_build_aur_pkg "$pkg" _build_aur_pkg "$pkg"
fi fi
} }
info "Installing AMD GPU stack" info "Installing AMD GPU stack"
for p in "${BASE_PKGS[@]}" "$VULKAN_PKG"; do _install_repo_or_aur "$p"; done for p in "${BASE_PKGS[@]}" "$VULKAN_PKG"; do _install_repo_or_aur "$p"; done
if [ "$AMD_INSTALL_XF86" = 1 ]; then if [ "$AMD_INSTALL_XF86" = 1 ]; then
_install_repo_or_aur "$XF86_PKG" _install_repo_or_aur "$XF86_PKG"
fi fi
# AMDVLK optional (install after vulkan-radeon if requested) # AMDVLK optional (install after vulkan-radeon if requested)
if [ "$AMD_INSTALL_AMDVLK" = 1 ]; then if [ "$AMD_INSTALL_AMDVLK" = 1 ]; then
_install_repo_or_aur "$AMDVLK_PKG" _install_repo_or_aur "$AMDVLK_PKG"
fi fi
if [ $MULTILIB_ENABLED = 1 ] || [ "$AMD_INSTALL_LIB32" = 1 ]; then if [ $MULTILIB_ENABLED = 1 ] || [ "$AMD_INSTALL_LIB32" = 1 ]; then
for p in "${LIB32_BASE[@]}" "$LIB32_VULKAN_PKG"; do _install_repo_or_aur "$p"; done for p in "${LIB32_BASE[@]}" "$LIB32_VULKAN_PKG"; do _install_repo_or_aur "$p"; done
if [ "$AMD_INSTALL_AMDVLK" = 1 ]; then _install_repo_or_aur "$LIB32_AMDVLK_PKG"; fi if [ "$AMD_INSTALL_AMDVLK" = 1 ]; then _install_repo_or_aur "$LIB32_AMDVLK_PKG"; fi
else else
vlog "Skipping 32-bit packages (multilib disabled)" vlog "Skipping 32-bit packages (multilib disabled)"
fi fi
# Detect SI / CIK codename presence for optional amdgpu enablement # Detect SI / CIK codename presence for optional amdgpu enablement
@ -113,41 +113,41 @@ for n in "${SI_NAMES[@]}"; do echo "$GPU_LINES" | grep -q "$n" && IS_SI=1 && bre
for n in "${CIK_NAMES[@]}"; do echo "$GPU_LINES" | grep -q "$n" && IS_CIK=1 && break; done for n in "${CIK_NAMES[@]}"; do echo "$GPU_LINES" | grep -q "$n" && IS_CIK=1 && break; done
if [ "$AMD_ENABLE_SI_CIK" = "1" ] || { [ "$AMD_ENABLE_SI_CIK" = "auto" ] && { [ $IS_SI = 1 ] || [ $IS_CIK = 1 ]; }; }; then if [ "$AMD_ENABLE_SI_CIK" = "1" ] || { [ "$AMD_ENABLE_SI_CIK" = "auto" ] && { [ $IS_SI = 1 ] || [ $IS_CIK = 1 ]; }; }; then
info "Configuring amdgpu for SI/CIK (IS_SI=$IS_SI IS_CIK=$IS_CIK)" info "Configuring amdgpu for SI/CIK (IS_SI=$IS_SI IS_CIK=$IS_CIK)"
TMP_CONF=$(mktemp) TMP_CONF=$(mktemp)
printf 'options amdgpu si_support=1\noptions amdgpu cik_support=1\n' > "$TMP_CONF" printf 'options amdgpu si_support=1\noptions amdgpu cik_support=1\n' >"$TMP_CONF"
printf 'options radeon si_support=0\noptions radeon cik_support=0\n' >> "$TMP_CONF" printf 'options radeon si_support=0\noptions radeon cik_support=0\n' >>"$TMP_CONF"
sudo mkdir -p /etc/modprobe.d sudo mkdir -p /etc/modprobe.d
sudo cp "$TMP_CONF" /etc/modprobe.d/10-amdgpu-si-cik.conf sudo cp "$TMP_CONF" /etc/modprobe.d/10-amdgpu-si-cik.conf
rm -f "$TMP_CONF" rm -f "$TMP_CONF"
# Ensure amdgpu early in MODULES # Ensure amdgpu early in MODULES
if [ -f /etc/mkinitcpio.conf ]; then if [ -f /etc/mkinitcpio.conf ]; then
if ! grep -q '^MODULES=.*amdgpu' /etc/mkinitcpio.conf; then if ! grep -q '^MODULES=.*amdgpu' /etc/mkinitcpio.conf; then
sudo sed -i 's/^MODULES=\(.*\)/MODULES=(amdgpu radeon)/' /etc/mkinitcpio.conf || true sudo sed -i 's/^MODULES=\(.*\)/MODULES=(amdgpu radeon)/' /etc/mkinitcpio.conf || true
fi fi
if ! grep -q 'modconf' /etc/mkinitcpio.conf; then if ! grep -q 'modconf' /etc/mkinitcpio.conf; then
warn "modconf hook not found in mkinitcpio.conf (needed for module options)" warn "modconf hook not found in mkinitcpio.conf (needed for module options)"
fi fi
if [ "$AMD_SKIP_INITRAMFS" != 1 ]; then if [ "$AMD_SKIP_INITRAMFS" != 1 ]; then
info "Regenerating initramfs (mkinitcpio -P)" info "Regenerating initramfs (mkinitcpio -P)"
sudo mkinitcpio -P || warn "mkinitcpio failed; review manually" sudo mkinitcpio -P || warn "mkinitcpio failed; review manually"
else else
info "Skipping initramfs regeneration per AMD_SKIP_INITRAMFS=1" info "Skipping initramfs regeneration per AMD_SKIP_INITRAMFS=1"
fi fi
else else
warn "/etc/mkinitcpio.conf not found; skipping MODULES update" warn "/etc/mkinitcpio.conf not found; skipping MODULES update"
fi fi
else else
vlog "SI/CIK enablement not required (AMD_ENABLE_SI_CIK=$AMD_ENABLE_SI_CIK IS_SI=$IS_SI IS_CIK=$IS_CIK)" vlog "SI/CIK enablement not required (AMD_ENABLE_SI_CIK=$AMD_ENABLE_SI_CIK IS_SI=$IS_SI IS_CIK=$IS_CIK)"
fi fi
# Check active kernel driver # Check active kernel driver
KDRV=$(lspci -k -d ::0300 2> /dev/null | awk '/Kernel driver in use:/ {print $5; exit}') KDRV=$(lspci -k -d ::0300 2>/dev/null | awk '/Kernel driver in use:/ {print $5; exit}')
[ -z "$KDRV" ] && KDRV=$(lsmod | grep -E 'amdgpu|radeon' | head -n1 | awk '{print $1}') [ -z "$KDRV" ] && KDRV=$(lsmod | grep -E 'amdgpu|radeon' | head -n1 | awk '{print $1}')
info "Kernel driver in use: ${KDRV:-unknown}" info "Kernel driver in use: ${KDRV:-unknown}"
if [ "$KDRV" = "radeon" ] && { [ $IS_SI = 1 ] || [ $IS_CIK = 1 ]; }; then if [ "$KDRV" = "radeon" ] && { [ $IS_SI = 1 ] || [ $IS_CIK = 1 ]; }; then
warn "radeon driver still active for SI/CIK; reboot may be required to switch to amdgpu" warn "radeon driver still active for SI/CIK; reboot may be required to switch to amdgpu"
fi fi
export AMD_STACK_DONE=1 export AMD_STACK_DONE=1

View File

@ -5,96 +5,96 @@
set -e set -e
[ "$GPU_VENDOR" = "nvidia" ] || { [ "$GPU_VENDOR" = "nvidia" ] || {
echo "NVIDIA installer invoked but GPU_VENDOR=$GPU_VENDOR" echo "NVIDIA installer invoked but GPU_VENDOR=$GPU_VENDOR"
exit 0 exit 0
} }
_build_aur_pkg() { _build_aur_pkg() {
local pkg="$1" local pkg="$1"
local repo_url="https://aur.archlinux.org/${pkg}.git" local repo_url="https://aur.archlinux.org/${pkg}.git"
mkdir -p "$HOME/aur" mkdir -p "$HOME/sdk/aur"
cd "$HOME/aur" cd "$HOME/sdk/aur"
if [ ! -d "$pkg" ]; then git clone "$repo_url"; else (cd "$pkg" && git fetch -q --all && git reset -q --hard origin/HEAD || git pull --ff-only || true); fi if [ ! -d "$pkg" ]; then git clone "$repo_url"; else (cd "$pkg" && git fetch -q --all && git reset -q --hard origin/HEAD || git pull --ff-only || true); fi
cd "$pkg" cd "$pkg"
rm -f -- *.pkg.tar.* 2> /dev/null || true rm -f -- *.pkg.tar.* 2>/dev/null || true
yes | makepkg -s -c -C --noconfirm --needed || return 1 yes | makepkg -s -c -C --noconfirm --needed || return 1
local built=(*.pkg.tar.zst) local built=(*.pkg.tar.zst)
yes | sudo pacman -U --noconfirm "${built[@]}" yes | sudo pacman -U --noconfirm "${built[@]}"
} }
_choose_nvidia_pkg() { _choose_nvidia_pkg() {
local have_linux have_linux_lts multiple_kernels driver_pkg prefer_open detect_out legacy_detected=0 local have_linux have_linux_lts multiple_kernels driver_pkg prefer_open detect_out legacy_detected=0
prefer_open=${NVIDIA_PREFER_OPEN:-1} prefer_open=${NVIDIA_PREFER_OPEN:-1}
pacman -Qq | grep -qx linux && have_linux=1 || have_linux=0 pacman -Qq | grep -qx linux && have_linux=1 || have_linux=0
pacman -Qq | grep -qx linux-lts && have_linux_lts=1 || have_linux_lts=0 pacman -Qq | grep -qx linux-lts && have_linux_lts=1 || have_linux_lts=0
if [ $((have_linux + have_linux_lts)) -gt 1 ]; then multiple_kernels=1; else multiple_kernels=0; fi if [ $((have_linux + have_linux_lts)) -gt 1 ]; then multiple_kernels=1; else multiple_kernels=0; fi
# Optionally skip attempting to install nvidia-detect (some minimal repo setups don't have it yet) # Optionally skip attempting to install nvidia-detect (some minimal repo setups don't have it yet)
if [ -z "${NVIDIA_SKIP_DETECT:-}" ] && ! command -v nvidia-detect > /dev/null 2>&1; then if [ -z "${NVIDIA_SKIP_DETECT:-}" ] && ! command -v nvidia-detect >/dev/null 2>&1; then
if pacman -Si nvidia-detect > /dev/null 2>&1; then if pacman -Si nvidia-detect >/dev/null 2>&1; then
echo "Attempting to install helper utility: nvidia-detect" >&2 echo "Attempting to install helper utility: nvidia-detect" >&2
# Use --needed to avoid forcing refresh (& avoid partial upgrade semantics with -Sy) # Use --needed to avoid forcing refresh (& avoid partial upgrade semantics with -Sy)
yes | sudo pacman -S --needed --noconfirm nvidia-detect || echo "nvidia-detect install failed (continuing with heuristic)" >&2 yes | sudo pacman -S --needed --noconfirm nvidia-detect || echo "nvidia-detect install failed (continuing with heuristic)" >&2
else else
echo "nvidia-detect not present in enabled repos; using heuristic selection." >&2 echo "nvidia-detect not present in enabled repos; using heuristic selection." >&2
fi fi
fi fi
if command -v nvidia-detect > /dev/null 2>&1; then if command -v nvidia-detect >/dev/null 2>&1; then
detect_out="$(nvidia-detect 2> /dev/null || true)" detect_out="$(nvidia-detect 2>/dev/null || true)"
fi fi
if [ -n "$detect_out" ]; then if [ -n "$detect_out" ]; then
if echo "$detect_out" | grep -q '470'; then if echo "$detect_out" | grep -q '470'; then
driver_pkg='nvidia-470xx-dkms' driver_pkg='nvidia-470xx-dkms'
legacy_detected=1 legacy_detected=1
fi fi
if echo "$detect_out" | grep -q '390'; then if echo "$detect_out" | grep -q '390'; then
driver_pkg='nvidia-390xx-dkms' driver_pkg='nvidia-390xx-dkms'
legacy_detected=1 legacy_detected=1
fi fi
if echo "$detect_out" | grep -q '340'; then if echo "$detect_out" | grep -q '340'; then
driver_pkg='nvidia-340xx-dkms' driver_pkg='nvidia-340xx-dkms'
legacy_detected=1 legacy_detected=1
fi fi
fi fi
if [ "$legacy_detected" = 0 ]; then if [ "$legacy_detected" = 0 ]; then
# Heuristic modern driver selection # Heuristic modern driver selection
if [ "$multiple_kernels" = 1 ]; then if [ "$multiple_kernels" = 1 ]; then
if [ "$prefer_open" = 1 ] && pacman -Si nvidia-open-dkms > /dev/null 2>&1; then driver_pkg='nvidia-open-dkms'; else driver_pkg='nvidia-dkms'; fi if [ "$prefer_open" = 1 ] && pacman -Si nvidia-open-dkms >/dev/null 2>&1; then driver_pkg='nvidia-open-dkms'; else driver_pkg='nvidia-dkms'; fi
else else
if [ "$have_linux_lts" = 1 ] && [ "$have_linux" = 0 ]; then if [ "$have_linux_lts" = 1 ] && [ "$have_linux" = 0 ]; then
if [ "$prefer_open" = 1 ] && pacman -Si nvidia-open-lts > /dev/null 2>&1; then driver_pkg='nvidia-open-lts'; else driver_pkg='nvidia-lts'; fi if [ "$prefer_open" = 1 ] && pacman -Si nvidia-open-lts >/dev/null 2>&1; then driver_pkg='nvidia-open-lts'; else driver_pkg='nvidia-lts'; fi
else else
if [ "$prefer_open" = 1 ] && pacman -Si nvidia-open > /dev/null 2>&1; then driver_pkg='nvidia-open'; else driver_pkg='nvidia'; fi if [ "$prefer_open" = 1 ] && pacman -Si nvidia-open >/dev/null 2>&1; then driver_pkg='nvidia-open'; else driver_pkg='nvidia'; fi
fi fi
fi fi
else else
echo "Legacy NVIDIA generation detected via nvidia-detect output; choosing $driver_pkg" >&2 echo "Legacy NVIDIA generation detected via nvidia-detect output; choosing $driver_pkg" >&2
fi fi
echo "$driver_pkg" echo "$driver_pkg"
} }
_remove_conflicting_nvidia_pkgs() { _remove_conflicting_nvidia_pkgs() {
local keep="$1" local keep="$1"
local candidates=(nvidia nvidia-lts nvidia-dkms nvidia-open nvidia-open-lts nvidia-open-dkms nvidia-470xx-dkms nvidia-390xx-dkms nvidia-340xx-dkms) local candidates=(nvidia nvidia-lts nvidia-dkms nvidia-open nvidia-open-lts nvidia-open-dkms nvidia-470xx-dkms nvidia-390xx-dkms nvidia-340xx-dkms)
local to_remove=() local to_remove=()
for p in "${candidates[@]}"; do for p in "${candidates[@]}"; do
if pacman -Qi "$p" > /dev/null 2>&1 && [ "$p" != "$keep" ]; then to_remove+=("$p"); fi if pacman -Qi "$p" >/dev/null 2>&1 && [ "$p" != "$keep" ]; then to_remove+=("$p"); fi
done done
if [ ${#to_remove[@]} -gt 0 ]; then yes | sudo pacman -Rns --noconfirm "${to_remove[@]}" || true; fi if [ ${#to_remove[@]} -gt 0 ]; then yes | sudo pacman -Rns --noconfirm "${to_remove[@]}" || true; fi
} }
_install_nvidia_stack() { _install_nvidia_stack() {
local driver_pkg="$1" local driver_pkg="$1"
if [[ $driver_pkg == nvidia-*xx-dkms ]]; then _build_aur_pkg "$driver_pkg"; else yes | sudo pacman -Sy --noconfirm "$driver_pkg"; fi if [[ $driver_pkg == nvidia-*xx-dkms ]]; then _build_aur_pkg "$driver_pkg"; else yes | sudo pacman -Sy --noconfirm "$driver_pkg"; fi
local utils_pkg="nvidia-utils" utils32_pkg="lib32-nvidia-utils" local utils_pkg="nvidia-utils" utils32_pkg="lib32-nvidia-utils"
if ! pacman -Qi "$utils_pkg" > /dev/null 2>&1; then yes | sudo pacman -Sy --noconfirm "$utils_pkg"; fi if ! pacman -Qi "$utils_pkg" >/dev/null 2>&1; then yes | sudo pacman -Sy --noconfirm "$utils_pkg"; fi
if grep -q '^\[multilib\]' /etc/pacman.conf; then if grep -q '^\[multilib\]' /etc/pacman.conf; then
if ! pacman -Qi "$utils32_pkg" > /dev/null 2>&1; then yes | sudo pacman -Sy --noconfirm "$utils32_pkg" || true; fi if ! pacman -Qi "$utils32_pkg" >/dev/null 2>&1; then yes | sudo pacman -Sy --noconfirm "$utils32_pkg" || true; fi
fi fi
} }
echo "Detected NVIDIA GPU. Selecting driver..." echo "Detected NVIDIA GPU. Selecting driver..."

View File

@ -6,116 +6,116 @@
# Function to play a sound on error # Function to play a sound on error
play_error_sound() { play_error_sound() {
#pactl set-sink-volume @DEFAULT_SINK@ +50% #pactl set-sink-volume @DEFAULT_SINK@ +50%
for _ in 1 2 3; do for _ in 1 2 3; do
paplay /usr/share/sounds/freedesktop/stereo/dialog-error.oga paplay /usr/share/sounds/freedesktop/stereo/dialog-error.oga
done done
#pactl set-sink-volume @DEFAULT_SINK@ -50% #pactl set-sink-volume @DEFAULT_SINK@ -50%
} }
install_from_aur() { install_from_aur() {
local repo_url pkg_name repo_dir local repo_url pkg_name repo_dir
repo_url="$1" repo_url="$1"
pkg_name="$2" pkg_name="$2"
mkdir -p "$HOME/aur" mkdir -p "$HOME/sdk/aur"
cd "$HOME/aur" || return 1 cd "$HOME/sdk/aur" || return 1
repo_dir="$(basename "$repo_url" .git)" repo_dir="$(basename "$repo_url" .git)"
if [ ! -d "$repo_dir" ]; then if [ ! -d "$repo_dir" ]; then
git clone "$repo_url" git clone "$repo_url"
else else
echo "Repository $repo_dir already cloned; updating" echo "Repository $repo_dir already cloned; updating"
(cd "$repo_dir" && git fetch --all -q && git reset --hard origin/HEAD -q || git pull --ff-only || true) (cd "$repo_dir" && git fetch --all -q && git reset --hard origin/HEAD -q || git pull --ff-only || true)
fi fi
cd "$repo_dir" || return 1 cd "$repo_dir" || return 1
if pacman -Qi "$pkg_name" > /dev/null 2>&1; then if pacman -Qi "$pkg_name" >/dev/null 2>&1; then
echo "$pkg_name is already installed" echo "$pkg_name is already installed"
return 0 return 0
fi fi
echo "Cleaning old package artifacts to avoid duplicate -U targets" echo "Cleaning old package artifacts to avoid duplicate -U targets"
find . -maxdepth 1 -type f -name '*.pkg.tar.*' -delete 2> /dev/null || true find . -maxdepth 1 -type f -name '*.pkg.tar.*' -delete 2>/dev/null || true
echo "Building $pkg_name (clean build)" echo "Building $pkg_name (clean build)"
# -c (clean up work dirs after) -C (clean build - remove src/ and pkg/ first) # -c (clean up work dirs after) -C (clean build - remove src/ and pkg/ first)
if ! yes | makepkg -s -c -C --noconfirm --nocheck --skipchecksums --skipinteg --skippgpcheck --needed; then if ! yes | makepkg -s -c -C --noconfirm --nocheck --skipchecksums --skipinteg --skippgpcheck --needed; then
echo "Build failed for $pkg_name" >&2 echo "Build failed for $pkg_name" >&2
return 1 return 1
fi fi
# Collect only the freshly built packages (should now be only current version) # Collect only the freshly built packages (should now be only current version)
mapfile -t built_pkgs < <(find . -maxdepth 1 -type f -name '*.pkg.tar.zst' -printf './%f\n') mapfile -t built_pkgs < <(find . -maxdepth 1 -type f -name '*.pkg.tar.zst' -printf './%f\n')
if [ ${#built_pkgs[@]} -eq 0 ]; then if [ ${#built_pkgs[@]} -eq 0 ]; then
echo "No package files produced for $pkg_name" >&2 echo "No package files produced for $pkg_name" >&2
return 1 return 1
fi fi
echo "Installing built package(s): ${built_pkgs[*]}" echo "Installing built package(s): ${built_pkgs[*]}"
if ! yes | sudo pacman -U --noconfirm "${built_pkgs[@]}"; then if ! yes | sudo pacman -U --noconfirm "${built_pkgs[@]}"; then
echo "Installation failed for $pkg_name" >&2 echo "Installation failed for $pkg_name" >&2
return 1 return 1
fi fi
} }
# Helper: try to install from AUR and log result to done.txt/failed.txt # Helper: try to install from AUR and log result to done.txt/failed.txt
try_aur_install() { try_aur_install() {
local repo_url="$1" local repo_url="$1"
local pkg_name="$2" local pkg_name="$2"
if install_from_aur "$repo_url" "$pkg_name"; then if install_from_aur "$repo_url" "$pkg_name"; then
echo "$pkg_name" >> done.txt echo "$pkg_name" >>done.txt
else else
echo "$pkg_name" >> failed.txt echo "$pkg_name" >>failed.txt
fi fi
} }
process_packages() { process_packages() {
local file_path local file_path
file_path="$1" file_path="$1"
: > failed.txt : >failed.txt
: > done.txt : >done.txt
while IFS= read -r pkg_name; do while IFS= read -r pkg_name; do
if [ -z "$pkg_name" ]; then if [ -z "$pkg_name" ]; then
continue continue
fi fi
local repo_url repo_dir local repo_url repo_dir
repo_url="https://aur.archlinux.org/${pkg_name}-git.git" repo_url="https://aur.archlinux.org/${pkg_name}-git.git"
repo_dir="${pkg_name}-git" repo_dir="${pkg_name}-git"
git clone "$repo_url" git clone "$repo_url"
if [ -d "$repo_dir" ] && [ -z "$(ls -A "$repo_dir")" ]; then if [ -d "$repo_dir" ] && [ -z "$(ls -A "$repo_dir")" ]; then
echo "Repository $repo_dir is empty, trying without -git suffix" echo "Repository $repo_dir is empty, trying without -git suffix"
repo_url="https://aur.archlinux.org/${pkg_name}.git" repo_url="https://aur.archlinux.org/${pkg_name}.git"
repo_dir="${pkg_name}" repo_dir="${pkg_name}"
git clone "$repo_url" git clone "$repo_url"
if [ -d "$repo_dir" ] && [ -z "$(ls -A "$repo_dir")" ]; then if [ -d "$repo_dir" ] && [ -z "$(ls -A "$repo_dir")" ]; then
echo "Repository $repo_dir is empty, trying to install with pacman" echo "Repository $repo_dir is empty, trying to install with pacman"
if sudo pacman -Sy --noconfirm "$pkg_name"; then if sudo pacman -Sy --noconfirm "$pkg_name"; then
echo "$pkg_name" >> done.txt echo "$pkg_name" >>done.txt
else else
echo "$pkg_name" >> failed.txt echo "$pkg_name" >>failed.txt
fi fi
else else
try_aur_install "$repo_url" "$pkg_name" try_aur_install "$repo_url" "$pkg_name"
fi fi
else else
try_aur_install "$repo_url" "$pkg_name" try_aur_install "$repo_url" "$pkg_name"
fi fi
done < "$file_path" done <"$file_path"
} }
# Helper: Check if all subpackages are installed # Helper: Check if all subpackages are installed
# Returns 0 if ALL subpackages are installed, 1 otherwise # Returns 0 if ALL subpackages are installed, 1 otherwise
all_subpackages_installed() { all_subpackages_installed() {
local -n sub_pkgs_ref=$1 local -n sub_pkgs_ref=$1
for subpkg in "${sub_pkgs_ref[@]}"; do for subpkg in "${sub_pkgs_ref[@]}"; do
if ! pacman -Qi "$subpkg" &> /dev/null; then if ! pacman -Qi "$subpkg" &>/dev/null; then
return 1 return 1
fi fi
done done
return 0 return 0
} }

View File

@ -164,7 +164,7 @@ for entry in "${aur_packages[@]}"; do
install_from_aur "$repo_url" "$pkg_name" install_from_aur "$repo_url" "$pkg_name"
done done
cd ~/linux-configuration/fresh-install cd ~/src/testsAndMisc/linux_configuration/fresh-install
if [ ! -d "$HOME/.config/mpv" ]; then if [ ! -d "$HOME/.config/mpv" ]; then
mkdir -p "$HOME/.config/mpv" mkdir -p "$HOME/.config/mpv"
fi fi
@ -176,10 +176,10 @@ else
echo "Oh My Zsh is already installed" echo "Oh My Zsh is already installed"
fi fi
cd ~/linux-configuration cd ~/src/testsAndMisc/linux_configuration
sudo "$HOME/hosts-blocker/install.sh" sudo "$HOME/src/hosts-blocker/install.sh"
i3/install.sh i3/install.sh
"$HOME/digital-wellbeing/pacman/install_pacman_wrapper.sh" "$HOME/src/digital-wellbeing/pacman/install_pacman_wrapper.sh"
fixes/nvidia_troubleshoot.sh fixes/nvidia_troubleshoot.sh
sudo features/setup_activitywatch.sh sudo features/setup_activitywatch.sh
sudo utils/setup_media_organizer.sh sudo utils/setup_media_organizer.sh

View File

@ -28,7 +28,7 @@ sudo chmod 440 /etc/sudoers.d/player2-steam
**4. Symlink the script into PATH:** **4. Symlink the script into PATH:**
```bash ```bash
sudo ln -sf ~/testsAndMisc/linux_configuration/gaming/start-player2.sh /usr/local/bin/start-player2 sudo ln -sf ~/src/testsAndMisc/linux_configuration/gaming/start-player2.sh /usr/local/bin/start-player2
``` ```
**5. Start the getty on tty2** (needed if LightDM autologin is configured, otherwise tty2 has no login prompt): **5. Start the getty on tty2** (needed if LightDM autologin is configured, otherwise tty2 has no login prompt):

View File

@ -35,7 +35,7 @@ exec --no-startup-id xss-lock --transfer-sleep-lock -- i3lock --nofork
exec --no-startup-id nm-applet exec --no-startup-id nm-applet
# Keep screen awake and unlocked; also treat controller input as activity # Keep screen awake and unlocked; also treat controller input as activity
exec --no-startup-id bash /home/kuhy/testsAndMisc/linux_configuration/utils/turn_off_auto_idle_screen_shutdown.sh --watch-controller exec --no-startup-id bash /home/kuhy/src/testsAndMisc/linux_configuration/utils/turn_off_auto_idle_screen_shutdown.sh --watch-controller
# Use pactl to adjust volume in PulseAudio. # Use pactl to adjust volume in PulseAudio.
set $refresh_i3status killall -SIGUSR1 i3status set $refresh_i3status killall -SIGUSR1 i3status
@ -59,7 +59,7 @@ bindsym $mod+Return exec terminator
# scratchpad calculator: floating popup running the live-calc widget (type # scratchpad calculator: floating popup running the live-calc widget (type
# math, results appear as you type; Ctrl-D to close) # math, results appear as you type; Ctrl-D to close)
bindsym $mod+c exec --no-startup-id /home/kuhy/testsAndMisc/linux_configuration/zsh/calc-popup.sh bindsym $mod+c exec --no-startup-id /home/kuhy/src/testsAndMisc/linux_configuration/zsh/calc-popup.sh
for_window [window_role="calc"] floating enable, border pixel 3, resize set 820 px 220 px, move position center for_window [window_role="calc"] floating enable, border pixel 3, resize set 820 px 220 px, move position center
# kill focused window # kill focused window

View File

@ -6,14 +6,14 @@
# a clone that fails is a loud module failure, never a silent skip. # a clone that fails is a loud module failure, never a silent skip.
# #
# CORE modules (always installed): # CORE modules (always installed):
# 0. Guard library (guardctl) – github.com/kuhyx/utils (~/utils/guard-lib) # 0. Guard library (guardctl) – github.com/kuhyx/utils (~/src/utils/guard-lib)
# 1. Workout screen locker – github.com/kuhyx/screen-locker (~/screen-locker) # 1. Workout screen locker – github.com/kuhyx/screen-locker (~/src/screen-locker)
# 2. Hosts blocking setup – github.com/kuhyx/hosts-blocker (~/hosts-blocker) # 2. Hosts blocking setup – github.com/kuhyx/hosts-blocker (~/src/hosts-blocker)
# 3. Midnight shutdown timer – github.com/kuhyx/digital-wellbeing # 3. Midnight shutdown timer – github.com/kuhyx/digital-wellbeing
# #
# SECONDARY modules (prompted unless --all / --none given): # SECONDARY modules (prompted unless --all / --none given):
# 4. Steam backlog enforcer – github.com/kuhyx/steam-backlog-enforcer # 4. Steam backlog enforcer – github.com/kuhyx/steam-backlog-enforcer
# 5. Pacman wrapper – github.com/kuhyx/digital-wellbeing (~/digital-wellbeing) # 5. Pacman wrapper – github.com/kuhyx/digital-wellbeing (~/src/digital-wellbeing)
# 6. i3 configuration – i3/ # 6. i3 configuration – i3/
# 7. Compulsive opening block – block_compulsive_opening.sh # 7. Compulsive opening block – block_compulsive_opening.sh
# #

View File

@ -59,7 +59,7 @@ extracted_repo_dir() {
# yourself" is not actionable -- there is nothing to clone it with yet. # yourself" is not actionable -- there is nothing to clone it with yet.
# #
# The clone runs as the HUMAN user, never as root. Installers re-exec themselves # The clone runs as the HUMAN user, never as root. Installers re-exec themselves
# via sudo, and a root-owned ~/screen-locker silently breaks every later git # via sudo, and a root-owned ~/src/screen-locker silently breaks every later git
# operation the user makes in it. A sandbox never catches this (the guest runs # operation the user makes in it. A sandbox never catches this (the guest runs
# as root throughout), so the ownership is forced here rather than assumed. # as root throughout), so the ownership is forced here rather than assumed.
# #

View File

@ -84,7 +84,7 @@ LEECHBLOCK_SCRIPT="$DW_REPO/install_leechblock.sh"
REMOVE_GUEST_MODE_SCRIPT="$DW_REPO/remove_guest_mode.sh" REMOVE_GUEST_MODE_SCRIPT="$DW_REPO/remove_guest_mode.sh"
VBOX_HOSTS_SCRIPT="$DW_REPO/virtualbox/enforce_vbox_hosts.sh" VBOX_HOSTS_SCRIPT="$DW_REPO/virtualbox/enforce_vbox_hosts.sh"
# screen-locker was EXTRACTED out of this monorepo into its own repo # screen-locker was EXTRACTED out of this monorepo into its own repo
# (github.com/kuhyx/screen-locker, checked out at ~/screen-locker), so these # (github.com/kuhyx/screen-locker, checked out at ~/src/screen-locker), so these
# paths deliberately live outside testsAndMisc. They used to point at # paths deliberately live outside testsAndMisc. They used to point at
# python_pkg/screen_locker/, which stopped existing at extraction time — the # 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 # result was check_workout_locker reporting a red "error" for a service that was

View File

@ -2,7 +2,7 @@
Security Hardening Test Suite Security Hardening Test Suite
========================================== ==========================================
Testing components in: /home/kuhy/linux-configuration Testing components in: /home/kuhy/src/testsAndMisc/linux_configuration
--- HOSTS GUARD --- --- HOSTS GUARD ---
✅ PASS: /etc/hosts is immutable ✅ PASS: /etc/hosts is immutable

View File

@ -24,6 +24,7 @@ _REPO_ROOT = Path(__file__).resolve().parents[2]
_SCRIPT_DIRS = ( _SCRIPT_DIRS = (
_REPO_ROOT / "meta" / "scripts", # validate_evidence, validate_contract _REPO_ROOT / "meta" / "scripts", # validate_evidence, validate_contract
_REPO_ROOT / "linux_configuration" / "utils", # fast_count _REPO_ROOT / "linux_configuration" / "utils", # fast_count
_REPO_ROOT / "linux_configuration" / "fixes", # g29_shifter_capture
_REPO_ROOT _REPO_ROOT
/ "linux_configuration" / "linux_configuration"
/ "misc" / "misc"

View File

@ -73,7 +73,7 @@ sec_tests_screen_locker() {
echo "--- SCREEN LOCKER ---" echo "--- SCREEN LOCKER ---"
# screen_locker was EXTRACTED to github.com/kuhyx/screen-locker. This used # screen_locker was EXTRACTED to github.com/kuhyx/screen-locker. This used
# to hardcode $HOME/testsAndMisc/python_pkg/screen_locker/, which stopped # to hardcode $HOME/src/testsAndMisc/python_pkg/screen_locker/, which stopped
# existing -- so all five checks below silently SKIPPED, reporting a clean # existing -- so all five checks below silently SKIPPED, reporting a clean
# run for tests that never executed. $HOME is also wrong under sudo; use the # run for tests that never executed. $HOME is also wrong under sudo; use the
# shared resolver, which handles the sudo/systemd cases. # shared resolver, which handles the sudo/systemd cases.

View File

@ -153,7 +153,7 @@ class TestSessionEndHookLaunchesIngest:
assert "ingest" in argv assert "ingest" in argv
assert str(transcript) in argv assert str(transcript) in argv
assert "--quiet" in argv assert "--quiet" in argv
assert f"PYTHONPATH={home}/mcp-servers/session-autopsy" in argv assert f"PYTHONPATH={home}/src/mcp-servers/session-autopsy" in argv
assert "timeout 120" in argv assert "timeout 120" in argv
def test_creates_the_autopsy_home(self, tmp_path: Path) -> None: def test_creates_the_autopsy_home(self, tmp_path: Path) -> None:

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Check Brother laser printer consumable/maintenance status. # Check Brother laser printer consumable/maintenance status.
# Thin wrapper that ensures dependencies are present, then runs the # Thin wrapper that ensures dependencies are present, then runs the
# Python implementation in ~/testsAndMisc/python_pkg/brother_printer. # Python implementation in ~/src/testsAndMisc/python_pkg/brother_printer.
# #
# Usage: # Usage:
# ./check_brother_printer.sh # auto-detect USB or network # ./check_brother_printer.sh # auto-detect USB or network
@ -9,7 +9,7 @@
set -euo pipefail set -euo pipefail
PYTHON_PKG_DIR="${HOME}/testsAndMisc/python_pkg" PYTHON_PKG_DIR="${HOME}/src/testsAndMisc/python_pkg"
BROTHER_MODULE="brother_printer" BROTHER_MODULE="brother_printer"
# ── Ensure dependencies ───────────────────────────────────────────── # ── Ensure dependencies ─────────────────────────────────────────────

View File

@ -3,28 +3,28 @@
# Uses 'fd' for fast searching - install with: sudo pacman -S fd # Uses 'fd' for fast searching - install with: sudo pacman -S fd
# #
# Usage: ./find_keepassxc.sh [destination_directory] # Usage: ./find_keepassxc.sh [destination_directory]
# Default destination: ~/Keepass # Default destination: ~/data/Keepass
set -euo pipefail set -euo pipefail
# Source common library if available # Source common library if available
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
if [[ -f "$SCRIPT_DIR/../lib/common.sh" ]]; then if [[ -f "$SCRIPT_DIR/../lib/common.sh" ]]; then
# shellcheck source=../lib/common.sh # shellcheck source=../lib/common.sh
source "$SCRIPT_DIR/../lib/common.sh" source "$SCRIPT_DIR/../lib/common.sh"
else else
log() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; } log() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; }
fi fi
# Configuration # Configuration
DEST_DIR="${1:-$HOME/Keepass}" DEST_DIR="${1:-$HOME/data/Keepass}"
SEARCH_ROOT="/" SEARCH_ROOT="/"
TIMEOUT_SECONDS=30 TIMEOUT_SECONDS=30
# Ensure fd is installed # Ensure fd is installed
if ! command -v fd &> /dev/null; then if ! command -v fd &>/dev/null; then
log "ERROR: 'fd' is not installed. Install with: sudo pacman -S fd" log "ERROR: 'fd' is not installed. Install with: sudo pacman -S fd"
exit 1 exit 1
fi fi
# Create destination directory if it doesn't exist # Create destination directory if it doesn't exist
@ -41,31 +41,31 @@ log "Searching for .kdbx files across the system (timeout: ${TIMEOUT_SECONDS}s).
# Use timeout to ensure the search doesn't take too long # Use timeout to ensure the search doesn't take too long
# Exclude /proc, /sys, /dev, /run, /tmp, /var/cache, /var/tmp for speed # Exclude /proc, /sys, /dev, /run, /tmp, /var/cache, /var/tmp for speed
FOUND_FILES=$(timeout "$TIMEOUT_SECONDS" fd \ FOUND_FILES=$(timeout "$TIMEOUT_SECONDS" fd \
-e kdbx \ -e kdbx \
-u \ -u \
-a \ -a \
--exclude '/proc' \ --exclude '/proc' \
--exclude '/sys' \ --exclude '/sys' \
--exclude '/dev' \ --exclude '/dev' \
--exclude '/run' \ --exclude '/run' \
--exclude '/tmp' \ --exclude '/tmp' \
--exclude '/var/cache' \ --exclude '/var/cache' \
--exclude '/var/tmp' \ --exclude '/var/tmp' \
--exclude '/snap' \ --exclude '/snap' \
--exclude '/.snapshots' \ --exclude '/.snapshots' \
--exclude '/lost+found' \ --exclude '/lost+found' \
. "$SEARCH_ROOT" 2> /dev/null || true) . "$SEARCH_ROOT" 2>/dev/null || true)
if [[ -z $FOUND_FILES ]]; then if [[ -z $FOUND_FILES ]]; then
log "No .kdbx files found." log "No .kdbx files found."
exit 0 exit 0
fi fi
# Count and display found files # Count and display found files
FILE_COUNT=$(echo "$FOUND_FILES" | wc -l) FILE_COUNT=$(echo "$FOUND_FILES" | wc -l)
log "Found $FILE_COUNT .kdbx file(s):" log "Found $FILE_COUNT .kdbx file(s):"
echo "$FOUND_FILES" | while read -r file; do echo "$FOUND_FILES" | while read -r file; do
echo " - $file" echo " - $file"
done done
# Move files to destination # Move files to destination
@ -74,51 +74,51 @@ MOVED_COUNT=0
SKIPPED_COUNT=0 SKIPPED_COUNT=0
while IFS= read -r src_file; do while IFS= read -r src_file; do
[[ -z $src_file ]] && continue [[ -z $src_file ]] && continue
# Skip if file is already in destination # Skip if file is already in destination
if [[ "$(dirname "$src_file")" == "$DEST_DIR" ]]; then if [[ "$(dirname "$src_file")" == "$DEST_DIR" ]]; then
log "Skipping (already in destination): $src_file" log "Skipping (already in destination): $src_file"
((SKIPPED_COUNT++)) || true ((SKIPPED_COUNT++)) || true
continue continue
fi fi
# Get the base filename # Get the base filename
base_name=$(basename "$src_file") base_name=$(basename "$src_file")
dest_file="$DEST_DIR/$base_name" dest_file="$DEST_DIR/$base_name"
# Handle filename conflicts by adding a number suffix # Handle filename conflicts by adding a number suffix
if [[ -f $dest_file ]]; then if [[ -f $dest_file ]]; then
# Check if it's the exact same file (by content) # Check if it's the exact same file (by content)
if cmp -s "$src_file" "$dest_file"; then if cmp -s "$src_file" "$dest_file"; then
log "Skipping (identical file exists): $src_file" log "Skipping (identical file exists): $src_file"
# Remove the duplicate source file # Remove the duplicate source file
rm -v "$src_file" rm -v "$src_file"
((SKIPPED_COUNT++)) || true ((SKIPPED_COUNT++)) || true
continue continue
fi fi
# Different file with same name - add suffix # Different file with same name - add suffix
counter=1 counter=1
name_without_ext="${base_name%.kdbx}" name_without_ext="${base_name%.kdbx}"
while [[ -f $dest_file ]]; do while [[ -f $dest_file ]]; do
dest_file="$DEST_DIR/${name_without_ext} ($counter).kdbx" dest_file="$DEST_DIR/${name_without_ext} ($counter).kdbx"
((counter++)) ((counter++))
done done
log "Renaming to avoid conflict: $base_name -> $(basename "$dest_file")" log "Renaming to avoid conflict: $base_name -> $(basename "$dest_file")"
fi fi
# Move the file # Move the file
if mv -v "$src_file" "$dest_file"; then if mv -v "$src_file" "$dest_file"; then
((MOVED_COUNT++)) || true ((MOVED_COUNT++)) || true
else else
log "ERROR: Failed to move $src_file" log "ERROR: Failed to move $src_file"
fi fi
done <<< "$FOUND_FILES" done <<<"$FOUND_FILES"
log "Done! Moved $MOVED_COUNT file(s), skipped $SKIPPED_COUNT file(s)." log "Done! Moved $MOVED_COUNT file(s), skipped $SKIPPED_COUNT file(s)."
log "All KeePassXC databases are now in: $DEST_DIR" log "All KeePassXC databases are now in: $DEST_DIR"
# List final contents # List final contents
log "Contents of $DEST_DIR:" log "Contents of $DEST_DIR:"
ls -la "$DEST_DIR"/*.kdbx 2> /dev/null || log "No .kdbx files in destination" ls -la "$DEST_DIR"/*.kdbx 2>/dev/null || log "No .kdbx files in destination"

View File

@ -7,52 +7,52 @@
# Make sure all databases use the same master password, or know each password. # Make sure all databases use the same master password, or know each password.
# #
# Usage: ./sync_keepassxc.sh [directory] # Usage: ./sync_keepassxc.sh [directory]
# Default directory: ~/Keepass # Default directory: ~/data/Keepass
set -euo pipefail set -euo pipefail
# Source common library if available # Source common library if available
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
if [[ -f "$SCRIPT_DIR/../lib/common.sh" ]]; then if [[ -f "$SCRIPT_DIR/../lib/common.sh" ]]; then
# shellcheck source=../lib/common.sh # shellcheck source=../lib/common.sh
source "$SCRIPT_DIR/../lib/common.sh" source "$SCRIPT_DIR/../lib/common.sh"
else else
log() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; } log() { printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"; }
fi fi
# Configuration # Configuration
KEEPASS_DIR="${1:-$HOME/Keepass}" KEEPASS_DIR="${1:-$HOME/data/Keepass}"
BACKUP_DIR="$KEEPASS_DIR/.backup_$(date +%Y%m%d_%H%M%S)" BACKUP_DIR="$KEEPASS_DIR/.backup_$(date +%Y%m%d_%H%M%S)"
# Ensure keepassxc-cli is installed # Ensure keepassxc-cli is installed
if ! command -v keepassxc-cli &> /dev/null; then if ! command -v keepassxc-cli &>/dev/null; then
log "ERROR: 'keepassxc-cli' is not installed. Install with: sudo pacman -S keepassxc" log "ERROR: 'keepassxc-cli' is not installed. Install with: sudo pacman -S keepassxc"
exit 1 exit 1
fi fi
# Check if directory exists # Check if directory exists
if [[ ! -d $KEEPASS_DIR ]]; then if [[ ! -d $KEEPASS_DIR ]]; then
log "ERROR: Directory does not exist: $KEEPASS_DIR" log "ERROR: Directory does not exist: $KEEPASS_DIR"
exit 1 exit 1
fi fi
# Find all .kdbx files # Find all .kdbx files
mapfile -t KDBX_FILES < <(find "$KEEPASS_DIR" -maxdepth 1 -name "*.kdbx" -type f | sort) mapfile -t KDBX_FILES < <(find "$KEEPASS_DIR" -maxdepth 1 -name "*.kdbx" -type f | sort)
if [[ ${#KDBX_FILES[@]} -eq 0 ]]; then if [[ ${#KDBX_FILES[@]} -eq 0 ]]; then
log "No .kdbx files found in $KEEPASS_DIR" log "No .kdbx files found in $KEEPASS_DIR"
exit 0 exit 0
fi fi
if [[ ${#KDBX_FILES[@]} -eq 1 ]]; then if [[ ${#KDBX_FILES[@]} -eq 1 ]]; then
log "Only one .kdbx file found. Nothing to merge." log "Only one .kdbx file found. Nothing to merge."
log "File: ${KDBX_FILES[0]}" log "File: ${KDBX_FILES[0]}"
exit 0 exit 0
fi fi
log "Found ${#KDBX_FILES[@]} .kdbx files in $KEEPASS_DIR:" log "Found ${#KDBX_FILES[@]} .kdbx files in $KEEPASS_DIR:"
for f in "${KDBX_FILES[@]}"; do for f in "${KDBX_FILES[@]}"; do
echo " - $(basename "$f")" echo " - $(basename "$f")"
done done
# Create backup directory # Create backup directory
@ -61,7 +61,7 @@ log "Creating backups in: $BACKUP_DIR"
# Backup all files before any operation # Backup all files before any operation
for f in "${KDBX_FILES[@]}"; do for f in "${KDBX_FILES[@]}"; do
cp -v "$f" "$BACKUP_DIR/" cp -v "$f" "$BACKUP_DIR/"
done done
log "All files backed up successfully." log "All files backed up successfully."
@ -75,8 +75,8 @@ echo "=============================================="
echo "" echo ""
read -rp "Do you want to continue? (yes/no): " CONFIRM read -rp "Do you want to continue? (yes/no): " CONFIRM
if [[ $CONFIRM != "yes" ]]; then if [[ $CONFIRM != "yes" ]]; then
log "Aborted by user." log "Aborted by user."
exit 1 exit 1
fi fi
# Select the target database (the one to merge INTO) # Select the target database (the one to merge INTO)
@ -93,9 +93,9 @@ read -rsp "Enter master password for TARGET database ($(basename "$TARGET_DB")):
echo "" echo ""
# Verify target password works # Verify target password works
if ! echo "$TARGET_PASSWORD" | keepassxc-cli ls "$TARGET_DB" &> /dev/null; then if ! echo "$TARGET_PASSWORD" | keepassxc-cli ls "$TARGET_DB" &>/dev/null; then
log "ERROR: Failed to open target database. Wrong password?" log "ERROR: Failed to open target database. Wrong password?"
exit 1 exit 1
fi fi
log "Target database password verified." log "Target database password verified."
@ -107,41 +107,41 @@ SAME_PASSWORD="${SAME_PASSWORD,,}" # lowercase
# Merge each source database into the target # Merge each source database into the target
MERGE_COUNT=0 MERGE_COUNT=0
for ((i = 1; i < ${#KDBX_FILES[@]}; i++)); do for ((i = 1; i < ${#KDBX_FILES[@]}; i++)); do
SOURCE_DB="${KDBX_FILES[$i]}" SOURCE_DB="${KDBX_FILES[$i]}"
log "" log ""
log "Merging $(basename "$SOURCE_DB") into $(basename "$TARGET_DB")..." log "Merging $(basename "$SOURCE_DB") into $(basename "$TARGET_DB")..."
# Reuse target password if user confirmed all are the same # Reuse target password if user confirmed all are the same
if [[ $SAME_PASSWORD == "y" || $SAME_PASSWORD == "yes" ]]; then if [[ $SAME_PASSWORD == "y" || $SAME_PASSWORD == "yes" ]]; then
SOURCE_PASSWORD="$TARGET_PASSWORD" SOURCE_PASSWORD="$TARGET_PASSWORD"
else else
# Ask for source password (might be different) # Ask for source password (might be different)
echo "" echo ""
read -rsp "Enter master password for SOURCE database ($(basename "$SOURCE_DB")): " SOURCE_PASSWORD read -rsp "Enter master password for SOURCE database ($(basename "$SOURCE_DB")): " SOURCE_PASSWORD
echo "" echo ""
fi fi
# Verify source password # Verify source password
if ! echo "$SOURCE_PASSWORD" | keepassxc-cli ls "$SOURCE_DB" &> /dev/null; then if ! echo "$SOURCE_PASSWORD" | keepassxc-cli ls "$SOURCE_DB" &>/dev/null; then
log "ERROR: Failed to open source database $(basename "$SOURCE_DB"). Wrong password?" log "ERROR: Failed to open source database $(basename "$SOURCE_DB"). Wrong password?"
log "Skipping this database. You can try again later." log "Skipping this database. You can try again later."
continue continue
fi fi
# Perform the merge # Perform the merge
# keepassxc-cli merge requires: target_db source_db # keepassxc-cli merge requires: target_db source_db
# It will prompt for passwords # It will prompt for passwords
if echo -e "${TARGET_PASSWORD}\n${SOURCE_PASSWORD}" | keepassxc-cli merge "$TARGET_DB" "$SOURCE_DB"; then if echo -e "${TARGET_PASSWORD}\n${SOURCE_PASSWORD}" | keepassxc-cli merge "$TARGET_DB" "$SOURCE_DB"; then
log "Successfully merged $(basename "$SOURCE_DB")" log "Successfully merged $(basename "$SOURCE_DB")"
# Delete the source database after successful merge # Delete the source database after successful merge
log "Deleting source database: $(basename "$SOURCE_DB")" log "Deleting source database: $(basename "$SOURCE_DB")"
rm -v "$SOURCE_DB" rm -v "$SOURCE_DB"
((MERGE_COUNT++)) || true ((MERGE_COUNT++)) || true
else else
log "ERROR: Failed to merge $(basename "$SOURCE_DB")" log "ERROR: Failed to merge $(basename "$SOURCE_DB")"
log "Source database NOT deleted. Check the backup and try manually." log "Source database NOT deleted. Check the backup and try manually."
fi fi
done done
echo "" echo ""
@ -159,15 +159,15 @@ find "$KEEPASS_DIR" -maxdepth 1 -name "*.kdbx" -type f -exec basename {} \;
# Rename to clean name if desired # Rename to clean name if desired
FINAL_COUNT=$(find "$KEEPASS_DIR" -maxdepth 1 -name "*.kdbx" -type f | wc -l) FINAL_COUNT=$(find "$KEEPASS_DIR" -maxdepth 1 -name "*.kdbx" -type f | wc -l)
if [[ $FINAL_COUNT -eq 1 ]]; then if [[ $FINAL_COUNT -eq 1 ]]; then
log "" log ""
FINAL_NAME="$KEEPASS_DIR/Passwords.kdbx" FINAL_NAME="$KEEPASS_DIR/Passwords.kdbx"
if [[ $TARGET_DB != "$FINAL_NAME" ]]; then if [[ $TARGET_DB != "$FINAL_NAME" ]]; then
read -rp "Rename final database to 'Passwords.kdbx'? (y/n): " RENAME_CONFIRM read -rp "Rename final database to 'Passwords.kdbx'? (y/n): " RENAME_CONFIRM
if [[ $RENAME_CONFIRM == "y" ]]; then if [[ $RENAME_CONFIRM == "y" ]]; then
mv -v "$TARGET_DB" "$FINAL_NAME" mv -v "$TARGET_DB" "$FINAL_NAME"
log "Final database: $FINAL_NAME" log "Final database: $FINAL_NAME"
fi fi
fi fi
log "" log ""
log "SUCCESS: You now have exactly ONE KeePassXC database!" log "SUCCESS: You now have exactly ONE KeePassXC database!"
fi fi

View File

@ -15,7 +15,7 @@ phone-deploy() { bash "$HOME/.claude/scripts/phone_deploy.sh" "${1:-.}" "${@:2}"
# autopsy [subcommand ...] — the transcript analyzer; defaults to `report`. # autopsy [subcommand ...] — the transcript analyzer; defaults to `report`.
# Try: autopsy candidates | autopsy measure | autopsy report --mark-reviewed # Try: autopsy candidates | autopsy measure | autopsy report --mark-reviewed
autopsy() { PYTHONPATH="$HOME/testsAndMisc" python3 -m python_pkg.session_autopsy "${1:-report}" "${@:2}"; } autopsy() { PYTHONPATH="$HOME/src/testsAndMisc" python3 -m python_pkg.session_autopsy "${1:-report}" "${@:2}"; }
# yay-clean [--dry-run] <pkg>...|--all — remove yay cache dirs plain rm chokes # yay-clean [--dry-run] <pkg>...|--all — remove yay cache dirs plain rm chokes
# on (read-only fakeroot/Bazel trees). The cache was 314GB on 2026-07-24. # on (read-only fakeroot/Bazel trees). The cache was 314GB on 2026-07-24.

View File

@ -1,3 +1,3 @@
{ {
"flutter": "3.47.2" "flutter": "3.47.3"
} }

View File

@ -173,7 +173,7 @@ disallow_untyped_decorators = false
# bare name (the same dirs linux_configuration/tests/conftest.py adds to # bare name (the same dirs linux_configuration/tests/conftest.py adds to
# sys.path at runtime) resolve under static analysis instead of raising E0401. # sys.path at runtime) resolve under static analysis instead of raising E0401.
# Paths are relative to the repo root, which is pre-commit's working directory. # Paths are relative to the repo root, which is pre-commit's working directory.
init-hook = "import sys; sys.path[:0] = ['meta/scripts', 'python_pkg', 'linux_configuration/utils', 'linux_configuration/misc/tools']" init-hook = "import sys; sys.path[:0] = ['meta/scripts', 'python_pkg', 'linux_configuration/utils', 'linux_configuration/fixes', 'linux_configuration/misc/tools']"
# Analyse import fallback blocks # Analyse import fallback blocks
analyse-fallback-blocks = true analyse-fallback-blocks = true
# Pickle collected data for later comparisons # Pickle collected data for later comparisons

View File

@ -21,16 +21,16 @@
# suite itself fails loudly here on a missing import. # suite itself fails loudly here on a missing import.
# ============================================================================== # ==============================================================================
anthropic==1.2.0 anthropic==1.5.0
beautifulsoup4==4.15.0 beautifulsoup4==4.15.0
coverage==7.16.0 coverage==7.16.0
defusedxml==0.7.1 defusedxml==0.7.1
markdownify==1.2.3 markdownify==1.2.3
numpy==2.5.2 numpy==2.5.3
pillow==12.3.0 pillow==12.3.0
pytest==9.1.1 pytest==9.1.1
pytest-cov==7.1.0 pytest-cov==7.1.0
pytest-randomly==4.1.0 pytest-randomly==5.0.0
pytest-sugar==1.1.1 pytest-sugar==1.1.1
pytest-timeout==2.4.0 pytest-timeout==2.4.0
pytest-xdist==3.8.0 pytest-xdist==3.8.0

View File

@ -6,7 +6,7 @@
add-trailing-comma==4.0.0 add-trailing-comma==4.0.0
aiohttp==3.14.3 aiohttp==3.14.3
anthropic==1.2.0 anthropic==1.5.0
autoflake==2.4.0 autoflake==2.4.0
autopep8==2.3.2 autopep8==2.3.2
bandit==1.9.4 bandit==1.9.4
@ -24,7 +24,7 @@ fixit==2.2.1
flake8==7.3.0 flake8==7.3.0
flake8-annotations==3.2.0 flake8-annotations==3.2.0
flake8-bandit==4.1.1 flake8-bandit==4.1.1
flake8-bugbear==25.11.29 flake8-bugbear==26.9.9
flake8-comprehensions==3.17.0 flake8-comprehensions==3.17.0
flake8-docstrings==1.7.0 flake8-docstrings==1.7.0
flake8-eradicate==1.5.0 flake8-eradicate==1.5.0
@ -38,23 +38,23 @@ gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.7.3#subdirectory=gatel
genanki==0.13.1 genanki==0.13.1
geopandas==1.1.4 geopandas==1.1.4
howlongtobeatpy==1.0.23 howlongtobeatpy==1.0.23
hypothesis==6.167.0 hypothesis==6.168.0
importlib-metadata==9.0.1 importlib-metadata==9.0.1
interrogate==1.7.0 interrogate==1.7.0
# isort is deliberately not pinned: nothing here imports it (ruff's isort # isort is deliberately not pinned: nothing here imports it (ruff's isort
# rules replaced it) and pylint constrains it (`isort!=5.13,<10,>=5` as of # rules replaced it) and pylint constrains it (`isort!=5.13,<10,>=5` as of
# 4.0.8), so pinning it independently risks an unsolvable environment for a # 4.0.8), so pinning it independently risks an unsolvable environment for a
# package no code here imports. pylint brings a compatible one. # package no code here imports. pylint brings a compatible one.
lxml==6.1.2 lxml==6.1.3
markdownify==1.2.3 markdownify==1.2.3
matplotlib==3.11.1 matplotlib==3.11.1
mccabe==0.7.0 mccabe==0.7.0
mypy==2.3.1 mypy==2.3.1
numpy==2.5.2 numpy==2.5.3
opencv-python==5.0.0.93 opencv-python==5.0.0.93
pillow==12.3.0 pillow==12.3.0
pip-audit==2.10.1 pip-audit==2.10.1
pipdeptree==4.2.2 pipdeptree==4.2.5
pre-commit==4.6.2 pre-commit==4.6.2
prospector==1.19.1 prospector==1.19.1
pycodestyle==2.14.0 pycodestyle==2.14.0
@ -62,10 +62,10 @@ pydocstyle==6.3.0
pyflakes==3.4.0 pyflakes==3.4.0
pygame==2.6.1 pygame==2.6.1
pylint==4.0.8 pylint==4.0.8
pyright==1.1.411 pyright==1.1.414
pytest==9.1.1 pytest==9.1.1
pytest-cov==7.1.0 pytest-cov==7.1.0
pytest-randomly==4.1.0 pytest-randomly==5.0.0
pytest-sugar==1.1.1 pytest-sugar==1.1.1
pytest-timeout==2.4.0 pytest-timeout==2.4.0
pytest-xdist==3.8.0 pytest-xdist==3.8.0
@ -76,15 +76,15 @@ radon==6.0.1
reorder-python-imports==3.17.0 reorder-python-imports==3.17.0
requests==2.34.2 requests==2.34.2
rich==15.0.0 rich==15.0.0
ruff==0.16.5 ruff==0.16.7
# safety is deliberately not pinned: its only reference in this repo is a # safety is deliberately not pinned: its only reference in this repo is a
# commented-out pre-commit hook, and safety 3.8.1 declares `typer<0.26`, # commented-out pre-commit hook, and safety 3.8.1 declares `typer<0.26`,
# which would hold the typer this repo actually imports below latest. # which would hold the typer this repo actually imports below latest.
selenium==4.48.0 selenium==4.49.0
typer==0.27.2 typer==0.27.2
types-python-dateutil==2.9.0.20260807 types-python-dateutil==2.9.0.20260807
types-PyYAML==6.0.12.20260815 types-PyYAML==6.0.12.20260906
types-requests==2.33.0.20260712 types-requests==2.33.0.20260906
types-setuptools==84.0.0.20260812 types-setuptools==84.0.0.20260812
typing_extensions==4.16.0 # Self annotation on py310 target (session_autopsy store lock) typing_extensions==4.16.0 # Self annotation on py310 target (session_autopsy store lock)
websockets==17.1 websockets==17.1

View File

@ -206,7 +206,7 @@ if ((journal_kb > 204800)); then
fi fi
# 19. AUR source dirs — old archives (>30 days) # 19. AUR source dirs — old archives (>30 days)
aur_dir="$HOME/aur" aur_dir="$HOME/sdk/aur"
if [[ -d "$aur_dir" ]]; then if [[ -d "$aur_dir" ]]; then
old_archives_kb=$(find "$aur_dir" \ old_archives_kb=$(find "$aur_dir" \
\( -name "*.pkg.tar.zst" -o -name "*.tar.gz" -o -name "*.tar.xz" \ \( -name "*.pkg.tar.zst" -o -name "*.tar.gz" -o -name "*.tar.xz" \
@ -215,7 +215,7 @@ if [[ -d "$aur_dir" ]]; then
awk '{t+=$1} END{print t+0}') awk '{t+=$1} END{print t+0}')
if ((old_archives_kb > 0)); then if ((old_archives_kb > 0)); then
report "AUR old archives (>30d)" "$old_archives_kb" \ report "AUR old archives (>30d)" "$old_archives_kb" \
"find ~/aur '*.pkg.tar.zst' -mtime +30 -delete" "find ~/sdk/aur '*.pkg.tar.zst' -mtime +30 -delete"
if confirm "Delete old AUR archives (>30 days)?"; then if confirm "Delete old AUR archives (>30 days)?"; then
find "$aur_dir" \ find "$aur_dir" \
\( -name "*.pkg.tar.zst" -o -name "*.tar.gz" -o -name "*.tar.xz" \ \( -name "*.pkg.tar.zst" -o -name "*.tar.gz" -o -name "*.tar.xz" \

View File

@ -83,7 +83,7 @@ print_report_only() {
for manual_entry in \ for manual_entry in \
"$HOME/Downloads/too_big:~/Downloads/too_big" \ "$HOME/Downloads/too_big:~/Downloads/too_big" \
"$HOME/Downloads:~/Downloads total" \ "$HOME/Downloads:~/Downloads total" \
"$HOME/inne:~/inne" \ "$HOME/media/inne:~/media/inne" \
"/Games:/Games — still playing?"; do "/Games:/Games — still playing?"; do
dir="${manual_entry%%:*}" dir="${manual_entry%%:*}"
label="${manual_entry#*:}" label="${manual_entry#*:}"

View File

@ -19,7 +19,7 @@ set -euo pipefail
SCRIPT_NAME="$(basename "$0")" SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_NAME readonly SCRIPT_NAME
readonly REPO="${REPO:-$HOME/testsAndMisc}" readonly REPO="${REPO:-$HOME/src/testsAndMisc}"
CHECK_ONLY=0 CHECK_ONLY=0
@ -40,8 +40,8 @@ report_systemd() {
[[ -n "$unit" ]] || continue [[ -n "$unit" ]] || continue
found=1 found=1
printf ' %s\n' "$unit" printf ' %s\n' "$unit"
grep -n "$REPO\|%h/testsAndMisc" "$unit" | sed 's/^/ /' grep -n "$REPO\|%h/src/testsAndMisc" "$unit" | sed 's/^/ /'
done < <(grep -rl "$REPO\|%h/testsAndMisc" \ done < <(grep -rl "$REPO\|%h/src/testsAndMisc" \
/etc/systemd/system "$HOME/.config/systemd/user" 2>/dev/null | sort) /etc/systemd/system "$HOME/.config/systemd/user" 2>/dev/null | sort)
((found)) || echo " (none)" ((found)) || echo " (none)"
return "$found" return "$found"

View File

@ -1,7 +1,7 @@
# android_ui — drive Android apps by element, never by coordinates # android_ui — drive Android apps by element, never by coordinates
Shared across every repo with an Android surface (`~/screen-locker`'s Shared across every repo with an Android surface (`~/src/screen-locker`'s
workout_app, `~/todo`, `~/dufs-cloud/app`). Works against a plain release APK workout_app, `~/src/todo`, `~/src/dufs-cloud/app`). Works against a plain release APK
on a physical device over `adb` — no root, no emulator, no Flutter debug on a physical device over `adb` — no root, no emulator, no Flutter debug
connection, no change to app source. connection, no change to app source.
@ -31,7 +31,7 @@ false results before they were understood:
## Use ## Use
```bash ```bash
export PYTHONPATH=~/testsAndMisc export PYTHONPATH=~/src/testsAndMisc
python3 -m python_pkg.android_ui dump python3 -m python_pkg.android_ui dump
python3 -m python_pkg.android_ui find "Connect Firebase" python3 -m python_pkg.android_ui find "Connect Firebase"
python3 -m python_pkg.android_ui --exact tap "Back" python3 -m python_pkg.android_ui --exact tap "Back"

View File

@ -1,7 +1,7 @@
"""Module entry point for the Android UI driver. """Module entry point for the Android UI driver.
Usage: Usage:
PYTHONPATH=~/testsAndMisc python3 -m python_pkg.android_ui --help PYTHONPATH=~/src/testsAndMisc python3 -m python_pkg.android_ui --help
""" """
from __future__ import annotations from __future__ import annotations

View File

@ -8,17 +8,17 @@ drawn at one stroke weight and optically centred so a row of them lines up.
```bash ```bash
# what is registered # what is registered
PYTHONPATH=~/testsAndMisc python3 -m python_pkg.app_icons list PYTHONPATH=~/src/testsAndMisc python3 -m python_pkg.app_icons list
# contact sheet for visual review: full size / circle-masked / 48dp # contact sheet for visual review: full size / circle-masked / 48dp
PYTHONPATH=~/testsAndMisc python3 -m python_pkg.app_icons preview -o /tmp/sheet.png PYTHONPATH=~/src/testsAndMisc python3 -m python_pkg.app_icons preview -o /tmp/sheet.png
# write assets into every app repo and rebuild the Android mipmaps # write assets into every app repo and rebuild the Android mipmaps
PYTHONPATH=~/testsAndMisc python3 -m python_pkg.app_icons generate --android PYTHONPATH=~/src/testsAndMisc python3 -m python_pkg.app_icons generate --android
# one app, plus the hicolor PNGs for a Linux desktop target # one app, plus the hicolor PNGs for a Linux desktop target
PYTHONPATH=~/testsAndMisc python3 -m python_pkg.app_icons \ PYTHONPATH=~/src/testsAndMisc python3 -m python_pkg.app_icons \
generate --app todo --android --linux-out ~/todo/linux/icons generate --app todo --android --linux-out ~/src/todo/linux/icons
``` ```
## What gets written ## What gets written
@ -58,7 +58,7 @@ review the contact sheet, then verify on the phone.
## Tests ## Tests
```bash ```bash
cd ~/testsAndMisc cd ~/src/testsAndMisc
python3 -m pytest python_pkg/app_icons --cov=python_pkg.app_icons \ python3 -m pytest python_pkg/app_icons --cov=python_pkg.app_icons \
--cov-report=term-missing --cov-report=term-missing
``` ```

View File

@ -1,7 +1,7 @@
"""Module entry point for the shared app-icon generator. """Module entry point for the shared app-icon generator.
Usage: Usage:
PYTHONPATH=~/testsAndMisc python3 -m python_pkg.app_icons --help PYTHONPATH=~/src/testsAndMisc python3 -m python_pkg.app_icons --help
""" """
from __future__ import annotations from __future__ import annotations

View File

@ -130,3 +130,47 @@ _BOWL_STAR = """\
L 430 493 L 459 397 L 379 337 L 479 335 Z" fill="{{ACCENT}}" stroke="none"/> L 430 493 L 459 397 L 379 337 L 479 335 Z" fill="{{ACCENT}}" stroke="none"/>
<path d="M 268 566 L 756 566"/> <path d="M 268 566 L 756 566"/>
<path d="M 348 566 C 348 700 420 752 512 752 C 604 752 676 700 676 566"/>""" <path d="M 348 566 C 348 700 420 752 512 752 C 604 752 676 700 676 566"/>"""
# A handset under a broadcast arc: the bot's control panel, reached from
# somewhere else. The arc is what makes it "remote" rather than "settings",
# and it is detached from the body on purpose -- an arc that *meets* the body
# is a padlock shackle, and padlock-closed is already in this family.
#
# Filled body with punched-out buttons, not a stroked outline, for the reason
# the nib and the anvil are filled: a 200-wide stroked body leaves a 56px
# interior at STROKE_WIDTH=72, which fills in solid at 48dp.
#
# Geometry, all inside SAFE_BOX (232..792) and clear of MIN_NEGATIVE_SPACE=36:
# body x=412..612, y=440..792, corner radius 56
# buttons r=40 at (512,540) and (512,680). Edge gap between them 60; to the
# body's top edge 60, to its bottom edge 72, to each side wall 60
# arc r=132 about the body's top centre (512,440), drawn between its
# 135 and 45 degree points (419,347)-(605,347)
#
# The arc's *lowest* ink is its endpoints at y=347 plus half the stroke = 383,
# which is 57 clear of the body at 440 -- not the arc's centre, which is
# inside the body. Total ink y=272..792 and x=383..641, both within the box.
_REMOTE_WAVE = """\
<path d="M 419 347 A 132 132 0 0 1 605 347"/>
<path fill="{{ACCENT}}" fill-rule="evenodd" stroke="none" d="\
M 468 440 L 556 440 A 56 56 0 0 1 612 496 L 612 736 A 56 56 0 0 1 556 792 \
L 468 792 A 56 56 0 0 1 412 736 L 412 496 A 56 56 0 0 1 468 440 Z \
M 512 500 A 40 40 0 0 0 512 580 A 40 40 0 0 0 512 500 Z \
M 512 640 A 40 40 0 0 0 512 720 A 40 40 0 0 0 512 640 Z"/>"""
# House with a tick inside: the home, cleared. A stroked house outline rather
# than a broom or sparkle -- a broom reads as a paintbrush at 48dp, and the
# family already leans on "check" for done-ness (see checklist).
#
# Geometry, all inside SAFE_BOX (232..792) and clear of MIN_NEGATIVE_SPACE=36:
# house apex (512,280), eaves y=480 at x=272/752, walls down to y=752.
# Apex ink with the round join reaches ~y=244, inside the box.
# tick (400,560)-(480,640)-(630,500). Bottom ink 676 vs floor ink 716
# -> 40 gap; right ink 666 vs wall ink 716 -> 50; left ink 364 vs
# wall ink 308 -> 56; top ink 464 vs the roof line at x=630
# (y=363, ink to 399) -> 65.
_HOUSE_TICK = """\
<path d="M 272 752 L 272 480 L 512 280 L 752 480 L 752 752 Z"/>
<path d="M 400 560 L 480 640 L 630 500"/>"""

View File

@ -51,7 +51,7 @@ APPS: Final[dict[str, AppIcon]] = {
# The live app: ~/dufs_client is a stale, remote-less checkout that # The live app: ~/dufs_client is a stale, remote-less checkout that
# was superseded when dufs-cloud absorbed it (see testsAndMisc # was superseded when dufs-cloud absorbed it (see testsAndMisc
# CLAUDE.md). Same package name, so it is easy to icon the wrong one. # CLAUDE.md). Same package name, so it is easy to icon the wrong one.
repo=_HOME / "dufs-cloud" / "app", repo=_HOME / "src/dufs-cloud" / "app",
accent="#B8862E", accent="#B8862E",
glyph="cloud-down", glyph="cloud-down",
icon_name="dufs-client", icon_name="dufs-client",
@ -61,7 +61,7 @@ APPS: Final[dict[str, AppIcon]] = {
key="focus_owner", key="focus_owner",
# Absorbed into the phone-focus-mode monorepo on 2026-08-24; the # Absorbed into the phone-focus-mode monorepo on 2026-08-24; the
# standalone ~/focus-owner clone is gone, as is its GitHub repo. # standalone ~/focus-owner clone is gone, as is its GitHub repo.
repo=_HOME / "phone-focus-mode" / "focus-owner", repo=_HOME / "src/phone-focus-mode" / "focus-owner",
accent="#B8862E", accent="#B8862E",
glyph="padlock-closed", glyph="padlock-closed",
icon_name="focus-owner", icon_name="focus-owner",
@ -69,7 +69,7 @@ APPS: Final[dict[str, AppIcon]] = {
), ),
AppIcon( AppIcon(
key="workout_app", key="workout_app",
repo=_HOME / "screen-locker" / "stronglift_replacement" / "workout_app", repo=_HOME / "src/screen-locker" / "stronglift_replacement" / "workout_app",
accent="#B8862E", accent="#B8862E",
glyph="barbell", glyph="barbell",
icon_name="workout-app", icon_name="workout-app",
@ -77,7 +77,7 @@ APPS: Final[dict[str, AppIcon]] = {
), ),
AppIcon( AppIcon(
key="wake_alarm_sync", key="wake_alarm_sync",
repo=_HOME / "wake-alarm" / "phone_app", repo=_HOME / "src/wake-alarm" / "phone_app",
accent="#B8862E", accent="#B8862E",
glyph="clock", glyph="clock",
icon_name="wake-alarm-sync", icon_name="wake-alarm-sync",
@ -85,7 +85,7 @@ APPS: Final[dict[str, AppIcon]] = {
), ),
AppIcon( AppIcon(
key="diet_guard_app", key="diet_guard_app",
repo=_HOME / "diet-guard" / "app", repo=_HOME / "src/diet-guard" / "app",
accent="#B8862E", accent="#B8862E",
glyph="shield-cutlery", glyph="shield-cutlery",
icon_name="diet-guard-app", icon_name="diet-guard-app",
@ -93,7 +93,7 @@ APPS: Final[dict[str, AppIcon]] = {
), ),
AppIcon( AppIcon(
key="todo", key="todo",
repo=_HOME / "todo", repo=_HOME / "src/todo",
accent="#B8862E", accent="#B8862E",
glyph="checklist", glyph="checklist",
icon_name="todo", icon_name="todo",
@ -101,7 +101,7 @@ APPS: Final[dict[str, AppIcon]] = {
), ),
AppIcon( AppIcon(
key="untools", key="untools",
repo=_HOME / "untools", repo=_HOME / "src/untools",
accent="#B8862E", accent="#B8862E",
glyph="decision-tree", glyph="decision-tree",
icon_name="untools", icon_name="untools",
@ -111,7 +111,7 @@ APPS: Final[dict[str, AppIcon]] = {
), ),
AppIcon( AppIcon(
key="punchme", key="punchme",
repo=_HOME / "punchme", repo=_HOME / "src/punchme",
accent="#B8862E", accent="#B8862E",
glyph="punch-card", glyph="punch-card",
icon_name="punchme", icon_name="punchme",
@ -120,7 +120,7 @@ APPS: Final[dict[str, AppIcon]] = {
), ),
AppIcon( AppIcon(
key="restaurant_rater", key="restaurant_rater",
repo=_HOME / "restaurant-rater", repo=_HOME / "src/restaurant-rater",
accent="#B8862E", accent="#B8862E",
glyph="bowl-star", glyph="bowl-star",
icon_name="restaurant-rater", icon_name="restaurant-rater",
@ -129,7 +129,7 @@ APPS: Final[dict[str, AppIcon]] = {
), ),
AppIcon( AppIcon(
key="habit_stack", key="habit_stack",
repo=_HOME / "habit_stack", repo=_HOME / "src/habit_stack",
accent="#B8862E", accent="#B8862E",
glyph="chain-link", glyph="chain-link",
icon_name="habit-stack", icon_name="habit-stack",
@ -137,7 +137,7 @@ APPS: Final[dict[str, AppIcon]] = {
), ),
AppIcon( AppIcon(
key="home_inventory", key="home_inventory",
repo=_HOME / "home_inventory", repo=_HOME / "src/home_inventory",
accent="#B8862E", accent="#B8862E",
glyph="storage-box", glyph="storage-box",
icon_name="home-inventory", icon_name="home-inventory",
@ -145,7 +145,7 @@ APPS: Final[dict[str, AppIcon]] = {
), ),
AppIcon( AppIcon(
key="epopeja_karta", key="epopeja_karta",
repo=_HOME / "epopeja_karta", repo=_HOME / "src/epopeja_karta",
accent="#B8862E", accent="#B8862E",
glyph="quill-nib", glyph="quill-nib",
icon_name="epopeja-karta", icon_name="epopeja-karta",
@ -158,7 +158,7 @@ APPS: Final[dict[str, AppIcon]] = {
key="octoforge", key="octoforge",
# The Flutter app is a subdirectory of the repo: the repo root # The Flutter app is a subdirectory of the repo: the repo root
# holds the pure-Dart core and the tooling alongside it. # holds the pure-Dart core and the tooling alongside it.
repo=_HOME / "octoforge" / "octoforge" / "app", repo=_HOME / "src/octoforge" / "app",
accent="#B8862E", accent="#B8862E",
glyph="anvil", glyph="anvil",
icon_name="octoforge", icon_name="octoforge",
@ -168,7 +168,7 @@ APPS: Final[dict[str, AppIcon]] = {
), ),
AppIcon( AppIcon(
key="kuhylog", key="kuhylog",
repo=_HOME / "kuhylog" / "kuhylog", repo=_HOME / "src/kuhylog",
accent="#B8862E", accent="#B8862E",
glyph="track-bars", glyph="track-bars",
icon_name="kuhylog", icon_name="kuhylog",
@ -179,7 +179,7 @@ APPS: Final[dict[str, AppIcon]] = {
), ),
AppIcon( AppIcon(
key="lyricanki", key="lyricanki",
repo=_HOME / "lyricanki", repo=_HOME / "src/lyricanki",
accent="#B8862E", accent="#B8862E",
glyph="note-ascender", glyph="note-ascender",
icon_name="lyricanki", icon_name="lyricanki",
@ -189,7 +189,7 @@ APPS: Final[dict[str, AppIcon]] = {
), ),
AppIcon( AppIcon(
key="betting_sim", key="betting_sim",
repo=_HOME / "betting-sim" / "app", repo=_HOME / "src/betting-sim" / "app",
accent="#B8862E", accent="#B8862E",
glyph="track-bars", glyph="track-bars",
icon_name="betting_sim", icon_name="betting_sim",
@ -198,6 +198,27 @@ APPS: Final[dict[str, AppIcon]] = {
# would go unread. # would go unread.
linux=False, linux=False,
), ),
AppIcon(
key="home_guard_app",
repo=_HOME / "src/home-guard" / "app",
accent="#B8862E",
glyph="house-tick",
icon_name="home-guard",
linux=False,
),
AppIcon(
key="signalbot_remote",
# Inside the bot's own repository, not beside it: the app is a
# client of that repo's management API, and kuhy's call was that a
# route change and the client change land in one commit.
repo=_HOME / "src/signal-bot" / "app",
accent="#B8862E",
glyph="remote-wave",
icon_name="signalbot_remote",
# Android only. The bot's desktop control is the TypeScript page
# it already has, served on loopback.
linux=False,
),
) )
} }

View File

@ -1,9 +1,9 @@
"""Command-line entry point for the shared app-icon generator. """Command-line entry point for the shared app-icon generator.
Usage: Usage:
PYTHONPATH=~/testsAndMisc python -m python_pkg.app_icons list PYTHONPATH=~/src/testsAndMisc python -m python_pkg.app_icons list
PYTHONPATH=~/testsAndMisc python -m python_pkg.app_icons preview -o sheet.png PYTHONPATH=~/src/testsAndMisc python -m python_pkg.app_icons preview -o sheet.png
PYTHONPATH=~/testsAndMisc python -m python_pkg.app_icons generate --app todo PYTHONPATH=~/src/testsAndMisc python -m python_pkg.app_icons generate --app todo
""" """
from __future__ import annotations from __future__ import annotations

View File

@ -31,9 +31,11 @@ from python_pkg.app_icons._glyph_art import (
) )
from python_pkg.app_icons._glyph_art_more import ( from python_pkg.app_icons._glyph_art_more import (
_BOWL_STAR, _BOWL_STAR,
_HOUSE_TICK,
_NOTE_ASCENDER, _NOTE_ASCENDER,
_PADLOCK_CLOSED, _PADLOCK_CLOSED,
_PUNCH_CARD, _PUNCH_CARD,
_REMOTE_WAVE,
_TRACK_BARS, _TRACK_BARS,
) )
@ -87,6 +89,12 @@ GLYPHS: Final[dict[str, Glyph]] = {
_PADLOCK_CLOSED, _PADLOCK_CLOSED,
), ),
Glyph("bowl-star", "Filled star above an open bowl", _BOWL_STAR), Glyph("bowl-star", "Filled star above an open bowl", _BOWL_STAR),
Glyph("house-tick", "House outline with a tick inside", _HOUSE_TICK),
Glyph(
"remote-wave",
"Handset with punched buttons under a broadcast arc",
_REMOTE_WAVE,
),
) )
} }

View File

@ -22,7 +22,7 @@ search.
./install.sh ./install.sh
# Manual refresh. # Manual refresh.
PYTHONPATH=~/testsAndMisc python3 -m python_pkg.archwiki_rag sync --reindex PYTHONPATH=~/src/testsAndMisc python3 -m python_pkg.archwiki_rag sync --reindex
# Query it. # Query it.
claude-archwiki claude-archwiki

View File

@ -1,7 +1,7 @@
"""Module entry point for the offline Arch Wiki RAG corpus builder. """Module entry point for the offline Arch Wiki RAG corpus builder.
Usage: Usage:
PYTHONPATH=~/testsAndMisc python3 -m python_pkg.archwiki_rag --help PYTHONPATH=~/src/testsAndMisc python3 -m python_pkg.archwiki_rag --help
""" """
from __future__ import annotations from __future__ import annotations

View File

@ -1,8 +1,8 @@
"""Command-line entry point for the offline Arch Wiki RAG corpus. """Command-line entry point for the offline Arch Wiki RAG corpus.
Usage: Usage:
PYTHONPATH=~/testsAndMisc python -m python_pkg.archwiki_rag sync PYTHONPATH=~/src/testsAndMisc python -m python_pkg.archwiki_rag sync
PYTHONPATH=~/testsAndMisc python -m python_pkg.archwiki_rag sync --reindex PYTHONPATH=~/src/testsAndMisc python -m python_pkg.archwiki_rag sync --reindex
""" """
from __future__ import annotations from __future__ import annotations

View File

@ -2,7 +2,7 @@
# "AI slop" is most detectable. # "AI slop" is most detectable.
# #
# CALIBRATION (measured, not guessed) over 250 genuine 32x32 CC0 icons from # CALIBRATION (measured, not guessed) over 250 genuine 32x32 CC0 icons from
# ~/opengameart-downloads: # ~/data/opengameart-downloads:
# p50=7 p75=10 p90=13 p95=14 p99=26 max=30 # p50=7 p75=10 p90=13 p95=14 p99=26 max=30
# Rejection rate of real hand-made art by cap: # Rejection rate of real hand-made art by cap:
# cap 16 -> 3.6% cap 24 -> 1.2% cap 32 -> 0.0% # cap 16 -> 3.6% cap 24 -> 1.2% cap 32 -> 0.0%

View File

@ -6,7 +6,7 @@
# a lantern's glass panes all mush at 32px and read clearly at 64px. # a lantern's glass panes all mush at 32px and read clearly at 64px.
# #
# CALIBRATION. `max_colors` is deliberately NOT raised from pixel_icon's 32. # CALIBRATION. `max_colors` is deliberately NOT raised from pixel_icon's 32.
# Measured over the 300 genuine 48x48 icons in ~/opengameart-downloads -- the # Measured over the 300 genuine 48x48 icons in ~/data/opengameart-downloads -- the
# largest real-art sample available locally above 32px: # largest real-art sample available locally above 32px:
# p50=9 p75=11 p90=13 p95=13 p99=14 max=14 # p50=9 p75=11 p90=13 p95=13 p99=14 max=14
# Rejection rate of that real art: cap 32 -> 0.00%, and every larger cap also # Rejection rate of that real art: cap 32 -> 0.00%, and every larger cap also

View File

@ -4,7 +4,7 @@ Run from a systemd user timer. Configuration comes from the environment:
ENDURAIN_URL base URL (default http://127.0.0.1:8085) ENDURAIN_URL base URL (default http://127.0.0.1:8085)
ENDURAIN_API_KEY API key with activities:upload (required) ENDURAIN_API_KEY API key with activities:upload (required)
ENDURAIN_INBOX WebDAV inbox (default ~/cloud/RunnerUp) ENDURAIN_INBOX WebDAV inbox (default ~/data/cloud/RunnerUp)
ENDURAIN_STATE ledger directory (default ~/.local/state/endurain-import) ENDURAIN_STATE ledger directory (default ~/.local/state/endurain-import)
ENDURAIN_BULK_DIR bulk_import dir for files the server rejected ENDURAIN_BULK_DIR bulk_import dir for files the server rejected
ENDURAIN_NO_ADB set to 1 to disable the phone fallback ENDURAIN_NO_ADB set to 1 to disable the phone fallback
@ -59,7 +59,7 @@ if TYPE_CHECKING:
_logger = logging.getLogger("endurain_import") _logger = logging.getLogger("endurain_import")
_DEFAULT_URL = "http://127.0.0.1:8085" _DEFAULT_URL = "http://127.0.0.1:8085"
_DEFAULT_INBOX = Path.home() / "cloud" / "RunnerUp" _DEFAULT_INBOX = Path.home() / "data/cloud" / "RunnerUp"
_DEFAULT_STATE = Path.home() / ".local" / "state" / "endurain-import" _DEFAULT_STATE = Path.home() / ".local" / "state" / "endurain-import"
_DEFAULT_USER_ID = 1 _DEFAULT_USER_ID = 1

View File

@ -10,10 +10,10 @@ Type=simple
# DISPLAY/PYTHONPATH mirror workout-locker.service so Tk can open the X server; # DISPLAY/PYTHONPATH mirror workout-locker.service so Tk can open the X server;
# the short sleep lets the session export DISPLAY first. # the short sleep lets the session export DISPLAY first.
Environment=DISPLAY=:0 Environment=DISPLAY=:0
Environment=PYTHONPATH=%h/testsAndMisc Environment=PYTHONPATH=%h/src/testsAndMisc
ExecStartPre=/bin/sleep 1 ExecStartPre=/bin/sleep 1
ExecStart=/usr/bin/python -m python_pkg.morning_routine._orchestrator --production ExecStart=/usr/bin/python -m python_pkg.morning_routine._orchestrator --production
WorkingDirectory=%h/testsAndMisc WorkingDirectory=%h/src/testsAndMisc
Restart=on-failure Restart=on-failure
RestartSec=2s RestartSec=2s
RestartPreventExitStatus=0 RestartPreventExitStatus=0

Some files were not shown because too many files have changed in this diff Show More