chore: add doc/code sync gate (make gen + make check + pre-commit hook)

- bin/pos: usage() CATEGORIES now auto-derived from pos-* filenames
  (kill the stale cheat-sheet bug class); only EXAMPLES stays hand-written
- # POS: / # POS_FLAGS: headers on all 15 tools = single source of truth
  for generated docs; template updated
- scripts/gen-docs.sh: regenerates GEN-marker sections (bin tree, dispatch
  table, no-common.sh list, line-count table, completion flags)
- scripts/check-sync.sh: bash -n + exec bits + doc/code drift + smoke
- Makefile: make gen / make check; scripts/install-hooks.sh: opt-in hook
- DEV.md + AGENT_Context: add-a-tool flow is now header + make check
This commit is contained in:
Your Name
2026-08-05 14:11:14 +00:00
parent e62626b55c
commit 7e2ecba219
7 changed files with 120 additions and 31 deletions
+15 -7
View File
@@ -35,6 +35,7 @@ Linux_post_install/
├── bin/ # CLI tools — installed to /usr/local/bin/
│ ├── pos # Main dispatcher — smart arg matching to pos-* scripts
<!-- GEN:START tree -->
│ ├── pos-communication-telegram # Send Telegram messages via Bot API (--send, test, config set)
│ ├── pos-docker-compose # Docker Compose service manager (ls/up/down/restart/logs/update/config)
│ ├── pos-docker-health # One-glance container health dashboard (exits 1 if unhealthy)
│ ├── pos-docker-ps # Enhanced container overview (health, IPs, ports, uptime)
@@ -199,6 +200,7 @@ All non-interactive `pos` commands log output to `~/.local/share/linux_post_inst
| Category | Command | Script | Description |
|----------|---------|--------|-------------|
<!-- GEN:START dispatch -->
| communication | telegram | `pos-communication-telegram` | Send Telegram messages via Bot API (--send, test, config set) |
| docker | compose | `pos-docker-compose` | Docker Compose service manager (ls/up/down/restart/logs/update/config) |
| docker | health | `pos-docker-health` | One-glance container health dashboard (exits 1 if unhealthy) |
| docker | ps | `pos-docker-ps` | Enhanced container overview (health, IPs, ports, uptime) |
@@ -257,7 +259,7 @@ source "$(dirname "$0")/../lib/common.sh"
**Scripts that do NOT source common.sh** (self-contained):
<!-- GEN:START selfcontained -->
`bin/pos`, `pos-network-ip`, `pos-network-checkport`, `pos-network-scan`, `pos-network-hotspot`, `pos-media-mp3`, `pos-media-mp4`, `pos-ssh-load-keys`, `pos-system-firewall`, `pos-communication-telegram`.
`pos`, `pos-communication-telegram`, `pos-media-mp3`, `pos-media-mp4`, `pos-network-checkport`, `pos-network-hotspot`, `pos-network-ip`, `pos-network-scan`, `pos-ssh-load-keys`, `pos-system-firewall`.
<!-- GEN:END selfcontained -->
---
@@ -421,11 +423,12 @@ System-wide flag store at `/usr/local/share/linux_post_install/flags/`:
### Adding a New Tool
1. Create `bin/pos-<category>-<command>` from `templates/pos-tool.sh` — must be executable (`100755`); it auto-appears in `pos <category> --help` (filename-derived, no registration)
2. Register in `bin/pos` `usage()` CATEGORIES/EXAMPLES; add to `INTERACTIVE_CMDS` in `bin/pos` if it reads stdin
3. Add system deps to `PACKAGES` array in `preinstall.sh` (if needed)
4. Add config logic to `postinstall.sh` (if needed, with `.gitignore` for secrets); runtime tool config → `~/.config/linux_post_install/<tool>.env` (600)
5. Update docs: `DOC/POS.md` (section table + detail), `DOC/AGENT_Context_Project.md` (bin tree, dispatch table, self-contained list, file line-count table), root `README.md` only if the category list changes
6. Test: `bash -n bin/your-tool && shellcheck bin/your-tool && bin/pos help <full command> && bin/pos <category> --help`
2. Add the `# POS: <cat> <cmd> — <one-line description>` header right after the shebang (plus `# POS_FLAGS: ...` for flag-style tools) — this is the single source of truth for the generated docs
3. Add to `INTERACTIVE_CMDS` in `bin/pos` if it reads stdin
4. Add system deps to `PACKAGES` array in `preinstall.sh` (if needed); non-apt/manual installers → `command -v` guard in the tool instead
5. Add config logic to `postinstall.sh` (if needed, with `.gitignore` for secrets); runtime tool config → `~/.config/linux_post_install/<tool>.env` (600)
6. Update docs: `DOC/POS.md` (section table + detail — hand-written); `DOC/AGENT_Context_Project.md` generated sections (bin tree, dispatch table, self-contained list, line-count table) and completion flags update via `make gen` — never hand-edit between `GEN:START`/`GEN:END` markers; root `README.md` only if the category list changes
7. Test: `make gen && make check``make check` (bash -n + doc/code sync + smoke) is the definition of done; also `bin/pos help <full command> && bin/pos <category> --help`
### Testing
@@ -466,6 +469,8 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
| `bin/flag-set` | 21 | Set a flag (optionally with a value) |
| `bin/flag-clear` | 21 | Unset a flag |
| `features/autostart.sh` | 14 | Boot-time feature (moved from `bin/`, flag-gated service) |
<!-- GEN:START filetable -->
| `bin/pos` | 208 | CLI dispatcher with smart arg matching + logging + category help |
| `bin/pos-communication-telegram` | 140 | Send Telegram messages via Bot API (--send, test, config set) |
| `bin/pos-docker-compose` | 364 | Docker Compose service manager (ls/up/down/restart/logs/update/config) |
| `bin/pos-docker-health` | 110 | One-glance container health dashboard (exits 1 if unhealthy) |
@@ -482,6 +487,7 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
| `bin/pos-system-firewall` | 285 | Interactive UFW management |
| `bin/pos-usb-server` | 218 | USB Redirector server control (--ls, --share; prompts when args omitted) |
| `completions/pos.bash` | 146 | Dynamic bash completion |
<!-- GEN:END filetable -->
| `apps/install.sh` | 171 | App install/uninstall picker/orchestrator |
---
@@ -490,7 +496,9 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
| Task | Where to Edit |
|------|---------------|
| Add a new CLI tool | Create `bin/pos-<cat>-<cmd>`, add apt deps in `preinstall.sh` (non-apt/manual installers: add a `command -v` guard in the tool instead) |
| Add a new CLI tool | Create `bin/pos-<cat>-<cmd>` with a `# POS:` header, chmod +x, add deps (apt → `preinstall.sh` PACKAGES; non-apt → `command -v` guard), then `make gen && make check` |
| Regenerate doc tables / completion flags | `make gen` (see `scripts/gen-docs.sh`; never hand-edit between `GEN:START`/`GEN:END` markers) |
| Verify repo self-consistency | `make check` (runs `scripts/check-sync.sh`; also the pre-commit hook after `make hook`)
| Add a new app installer | Create `apps/<name>.sh` (auto-discovered) |
| Add a feature | Create `features/<name>.sh` (installed on demand via `./install.sh --feature`) |
| Add a systemd service | Create `systemd/<name>.service` (auto-installed by postinstall; gate on a flag if it backs a feature) |
+16 -7
View File
@@ -51,9 +51,9 @@ All non-interactive commands log to `~/.local/share/linux_post_install/logs/`.
`pos help <full command>` shows a tool's help, e.g. `pos help communication telegram` (all words joined with dashes → `pos-communication-telegram --help`). `pos <category>` or `pos <category> --help` shows a category's subcommands (derived from the `pos-<category>-*` filenames in `bin/` — no script execution, so it works even for root-only/interactive tools like `system-firewall`).
**When adding a command, `bin/pos` itself has two things to keep in sync:**
**When adding a command, `bin/pos` itself has one thing to keep in sync:**
- **The usage text** (`usage()` function) — the CATEGORIES and EXAMPLES blocks are the built-in cheat-sheet (`pos --help`). Add the new command there or it stays invisible.
- **The usage text** (`usage()` function) — the CATEGORIES block is **auto-derived** from the `pos-*` filenames in `bin/` (no manual edit, can't drift). The EXAMPLES block is the only hand-maintained part: add a line there only if you want the tool showcased in `pos --help`.
- **`INTERACTIVE_CMDS`** (space-separated list above the dispatch loop) — commands that **read stdin** (password prompts, selection menus: `media-mp4`, `system-backup`, `usb-server`) must be added here. Everything else is piped through `tee` for logging, which would hang or swallow an interactive prompt. sudo's own password prompt is unaffected — it reads from `/dev/tty`. Trade-off: it's all-or-nothing **per script** — adding a flag-style tool with *any* prompting subcommand (e.g. `usb-server --share`) means *every* subcommand of that script skips output logging (e.g. `usb server --ls` loses the `tee` log too).
### Shared Library (`lib/common.sh`)
@@ -114,15 +114,20 @@ esac
warn() { echo "[!] $*"; }
err() { echo "ERROR: $*" >&2; exit 1; }
```
If you skip `common.sh`, add the tool to the "Scripts that do NOT source common.sh" list in `DOC/AGENT_Context_Project.md`.
If you skip `common.sh`, `make gen` adds the tool to the "Scripts that do NOT source common.sh" list in `DOC/AGENT_Context_Project.md` automatically.
### 2. Make it discoverable
- The dispatcher auto-discovers executable `bin/pos-*` files — no registration needed. The file **must be executable** (`chmod +x`, committed as mode `100755`); the dispatcher and `install.sh` skip non-executables.
- `pos <category> --help` (and bare `pos <category>`) is derived from the `pos-<category>-*` filenames too — a new tool appears in its category's help automatically, with no registration (see [The `pos` CLI](#the-pos-cli)).
- Add the command to the `usage()` CATEGORIES/EXAMPLES blocks in `bin/pos` (see [The `pos` CLI](#the-pos-cli)).
- **Add the `# POS:` header** (single source of truth for the docs) right after the shebang/strict-mode lines:
```bash
# POS: <category> <command> — one-line description rendered by `make gen`
# POS_FLAGS: --flag1 --flag2 # ONLY for flag-style tools
```
The description feeds the dispatch table, bin tree and file table in `DOC/AGENT_Context_Project.md`; `POS_FLAGS` feeds flag completion in `completions/pos.bash`. Both update via `make gen`.
- Optionally add an EXAMPLES line in `bin/pos` `usage()` to showcase the tool in `pos --help`.
- If the command **reads stdin** (prompts/selection), add it to `INTERACTIVE_CMDS` in `bin/pos` — see [The `pos` CLI](#the-pos-cli).
- If it takes flag-style args (e.g. `--send "text"`), consider extending `completions/pos.bash`; category/subcommand names are auto-discovered from the filename.
### 3. Add system dependencies
@@ -150,8 +155,8 @@ Place public keys in `config/authorized_keys` (one per line). `postinstall.sh` r
### 6. Update the docs
- `DOC/POS.md`: add the command to the section table + a detail block (commands, behavior, configuration).
- `DOC/AGENT_Context_Project.md`: update the bin tree, the dispatch table, the "scripts that do NOT source common.sh" list (if applicable), and the file line-count table.
- `DOC/POS.md`: add the command to the section table + a detail block (commands, behavior, configuration). This is the one hand-written doc.
- `DOC/AGENT_Context_Project.md` generated sections (bin tree, dispatch table, no-common.sh list, line-count table) and the `completions/pos.bash` flags block are produced by `make gen` — do **not** hand-edit between the `GEN:START`/`GEN:END` markers.
- Root `README.md`: only if the `pos` category list in the help text changes.
### 7. Test
@@ -163,8 +168,12 @@ shellcheck bin/your-tool
./bin/your-tool --help
bin/pos help <full command> # confirm dispatch works
bin/pos <category> --help # confirm category listing includes the new tool (first tool in a new category)
make gen # regenerate doc tables + completion flags
make check # full self-consistency gate (syntax, exec bits, doc/code sync, smoke)
```
`make check` is the definition of done — the same check runs as a pre-commit hook once you've run `make hook`.
---
## Adding an Optional App
+13
View File
@@ -0,0 +1,13 @@
.PHONY: check gen hook
## check — verify repo self-consistency (syntax, exec bits, doc/code sync, smoke)
check:
./scripts/check-sync.sh
## gen — regenerate code-derived doc sections + completion flags
gen:
./scripts/gen-docs.sh
## hook — install the opt-in pre-commit hook (runs `make check`)
hook:
./scripts/install-hooks.sh
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -euo pipefail
# Repo self-consistency check — run via `make check` (also as a pre-commit hook).
# bash -n every script, executability, doc<->code sync (gen-docs --check),
# and a dispatch smoke test. Exit 1 on any failure.
root="$(cd "$(dirname "$0")/.." && pwd)"
cd "$root"
fail=0
note() { echo "$*"; }
# ── 1. Syntax ───────────────────────────────────────────────────
for f in bin/pos bin/pos-* lib/*.sh install.sh preinstall.sh postinstall.sh \
completions/pos.bash scripts/*.sh; do
[ -f "$f" ] || continue
bash -n "$f" 2>/dev/null || { note "syntax error in $f"; fail=1; }
done
# ── 2. Executability ────────────────────────────────────────────
for f in bin/pos bin/pos-*; do
[ -x "$f" ] || { note "not executable: $f"; fail=1; }
done
# ── 3. Doc <-> code sync ────────────────────────────────────────
if ! bash scripts/gen-docs.sh --check >/dev/null 2>&1; then
note "doc/code drift — run 'make gen' and commit the regenerated files"
fail=1
fi
# ── 4. Dispatch smoke test ──────────────────────────────────────
bash bin/pos --help >/dev/null 2>&1 || { note "pos --help failed"; fail=1; }
bash bin/pos docker --help >/dev/null 2>&1 || { note "pos docker --help failed"; fail=1; }
bash bin/pos bogus >/dev/null 2>&1 && { note "pos bogus should have failed"; fail=1; }
if [ "$fail" -eq 0 ]; then
echo "check-sync: OK"
else
echo "check-sync: FAILED" >&2
exit 1
fi
+4 -9
View File
@@ -90,18 +90,13 @@ gen_posflags() {
}
# ── Replace (write) or verify (check) one marker block ──────────
markers() {
# bash files use '#' comment markers, markdown uses HTML comments
case "$1" in
*.bash|*.sh) echo "# GEN:START $2" "# GEN:END $2" ;;
*) echo "<!-- GEN:START $2 -->" "<!-- GEN:END $2 -->" ;;
esac
}
regen_block() {
local file="$1" name="$2"
local start end newfile tmp
read -r start end <<<"$(markers "$file" "$name")"
case "$file" in
*.bash|*.sh) start="# GEN:START $name"; end="# GEN:END $name" ;;
*) start="<!-- GEN:START $name -->"; end="<!-- GEN:END $name -->" ;;
esac
newfile="$(mktemp)"
"gen_$name" > "$newfile"
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -euo pipefail
# Opt-in git pre-commit hook that runs `make check` before every commit.
# Run `make hook` (or ./scripts/install-hooks.sh) once per clone.
#
# Skips itself when the commit is purely merge/conflict-resolution driven.
root="$(cd "$(dirname "$0")/.." && pwd)"
hook="$root/.git/hooks/pre-commit"
cat > "$hook" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
root="$(git rev-parse --show-toplevel)"
cd "$root"
exec make check
EOF
chmod +x "$hook"
echo "pre-commit hook installed: $hook"
+12 -8
View File
@@ -5,15 +5,19 @@ set -euo pipefail
# TEMPLATE — new `pos` CLI tool
#
# 1. Copy: cp templates/pos-tool.sh bin/pos-<category>-<command>
# 2. Exec bit: chmod +x bin/pos-<category>-<command>
# 3. Register: add the command to usage() CATEGORIES/EXAMPLES in bin/pos
# If it reads stdin (password/selection prompts), also add it to
# 2. Header: add a `# POS:` line right after the shebang/strict-mode
# lines (single source of truth for generated docs):
# # POS: <category> <command> — one-line description
# # POS_FLAGS: --flag1 --flag2 (flag-style tools only)
# 3. Exec bit: chmod +x bin/pos-<category>-<command>
# 4. If it reads stdin (password/selection prompts), add it to
# INTERACTIVE_CMDS in bin/pos or its prompt breaks under the log tee.
# 4. Docs: DOC/POS.md section table + detail block,
# DOC/AGENT_Context_Project.md (bin tree, dispatch table,
# self-contained list, file line-count table), root README.md only
# when the category list changes.
# 5. Deps: add packages to PACKAGES in preinstall.sh if needed.
# 5. Docs: DOC/POS.md section table + detail block (hand-written).
# The AGENT_Context tables + completion flags come from
# `make gen` — never hand-edit between GEN markers.
# 6. Deps: add apt packages to PACKAGES in preinstall.sh; non-apt
# installers → `command -v <bin> || err "install from <URL>"`.
# 7. Done: `make gen && make check`
#
# Invoked as: pos <category> <command> [args]
# ────────────────────────────────────────────────────────────────