docs: add dev templates + clarify conventions; fix pos help for multi-word commands

This commit is contained in:
Your Name
2026-08-05 03:03:56 -04:00
parent 070524bafb
commit 0c9d09c331
7 changed files with 238 additions and 18 deletions
+22 -7
View File
@@ -58,6 +58,11 @@ Linux_post_install/
├── features/ # User-customizable scripts (installed via --feature)
│ └── autostart.sh # Boot-time script (via systemd, flag-gated)
├── templates/ # Dev-only scaffolds — NOT installed by install.sh
│ ├── pos-tool.sh # New `pos` CLI tool (→ bin/pos-<cat>-<cmd>)
│ ├── app.sh # New optional app installer (→ apps/<cat>/<name>.sh)
│ └── feature.sh # New feature script (→ features/<name>.sh)
├── x64_bin/ # Precompiled binaries, copied to /usr/local/bin on x86_64
│ ├── create_ap # Wi-Fi AP CLI (bash script)
│ ├── wihotspot # Wrapper → wihotspot-gui
@@ -394,18 +399,28 @@ System-wide flag store at `/usr/local/share/linux_post_install/flags/`:
## 11. Development Workflow
### Adding a New Feature
1. Create `features/<name>.sh` from `templates/feature.sh` (installed on demand via `./install.sh --feature`; never overwritten without asking)
2. `install.sh` auto-discovers it and sets its flag — no registration needed
3. If a systemd service depends on it, gate the service on `flag_is_set <name>` in `postinstall.sh`
4. Update `DOC/AGENT_Context_Project.md` file table if line counts change
### Adding a New App
1. Create `apps/<category>/<name>.sh` following the template in DOC/DEV.md
1. Create `apps/<category>/<name>.sh` from `templates/app.sh` (per DOC/DEV.md)
2. It auto-appears in the interactive picker — no registration needed
3. Update `DOC/APPS.md` catalog table (name, category, purpose, install method)
4. Test: `bash -n apps/<cat>/<name>.sh && shellcheck apps/<cat>/<name>.sh`
### Adding a New Tool
1. Create `bin/pos-<category>-<command>` following conventions
2. Add system deps to `PACKAGES` array in `preinstall.sh` (if needed)
3. Add config logic to `postinstall.sh` (if needed, with `.gitignore` for secrets)
4. Update `DOC/POS.md` (and root `README.md` only if the category list changes)
5. Test: `bash -n bin/your-tool && shellcheck bin/your-tool`
1. Create `bin/pos-<category>-<command>` from `templates/pos-tool.sh` — must be executable (`100755`)
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>`
### Testing
@@ -446,7 +461,7 @@ 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) |
| `bin/pos` | 153 | CLI dispatcher with smart arg matching + logging |
| `bin/pos` | 154 | CLI dispatcher with smart arg matching + logging |
| `bin/pos-docker-compose` | 363 | Largest script — full compose management |
| `bin/pos-system-firewall` | 284 | Interactive UFW manager |
| `bin/pos-system-backup` | 115 | Encrypted folder snapshots: path mode + `--service` (`/srv`, `~/srv` picker), tar + gpg AES-256 |
+39 -8
View File
@@ -49,6 +49,13 @@ pos docker compose up jellyfin
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`).
**When adding a command, `bin/pos` itself has two things 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.
- **`INTERACTIVE_CMDS`** (space-separated list above the dispatch loop) — commands that **read stdin** (password prompts, selection menus: `media-mp4`, `system-backup`) 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`.
### Shared Library (`lib/common.sh`)
Sourced by most scripts. Key functions:
@@ -70,6 +77,8 @@ Sourced by most scripts. Key functions:
## Adding a New CLI Tool
Start from the template: `cp templates/pos-tool.sh bin/pos-<category>-<command> && chmod +x bin/pos-<category>-<command>`.
### 1. Create the script
```bash
@@ -105,8 +114,16 @@ 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`.
### 2. Add system dependencies
### 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.
- Add the command to the `usage()` CATEGORIES/EXAMPLES blocks in `bin/pos` (see [The `pos` CLI](#the-pos-cli)).
- 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
Add package names to the `PACKAGES` array in `preinstall.sh`:
@@ -117,30 +134,39 @@ PACKAGES=(
)
```
### 3. Add config files (if needed)
### 4. Config files (if needed)
Place defaults in `config/` and add copy logic to `postinstall.sh`. If they contain secrets, add to `.gitignore` and document in `DOC/`.
Two kinds of config, don't mix them up:
### 4. Add SSH keys (if needed)
- **Machine defaults shipped by the installer:** place the file in `config/` and add copy logic to `postinstall.sh`. If it contains secrets, add to `.gitignore` and document in `DOC/`.
- **Runtime tool config set by the user:** `~/.config/linux_post_install/<tool>.env` with `chmod 600`. Load it with env-var precedence (flags > environment > file). Patterns: `pos-docker-compose` (`compose.env`) and `pos-communication-telegram` (`telegram.env`, token masked in `config` output). Never store tokens in the repo.
### 5. Add SSH keys (if needed)
Place public keys in `config/authorized_keys` (one per line). `postinstall.sh` reads this file automatically.
### 5. Update the docs
### 6. Update the docs
Add a section for the new command in `DOC/POS.md`.
- `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.
- Root `README.md`: only if the `pos` category list in the help text changes.
### 6. Test
### 7. Test
```bash
chmod +x bin/your-tool
bash -n bin/your-tool
shellcheck bin/your-tool
./bin/your-tool --help
bin/pos help <full command> # confirm dispatch works
```
---
## Adding an Optional App
Start from the template: `cp templates/app.sh apps/<category>/<name>.sh`.
### 1. Create the installer
```bash
@@ -169,6 +195,8 @@ Place it in `apps/<category>/<name>.sh`. It auto-appears in the picker — no re
**Categories:** `browsers`, `development`, `media`, `networking`, `remote-access`, `system`, `utilities`
**Docs:** add a row to the catalog table in `DOC/APPS.md` (name, category, purpose, install method).
### 2. Conventions
- Idempotent: check `command -v` (or `flatpak list` / file existence) before installing **and** uninstalling
@@ -228,8 +256,9 @@ run sudo apt install -y git
### Security
- Never hardcode secrets — put them in `config/` (gitignored)
- Never hardcode secrets — put them in `config/` (gitignored) or, for runtime tool config, `~/.config/linux_post_install/<tool>.env`
- `chmod 600` for sensitive files
- Mask secrets in `config` output (see `pos-communication-telegram`'s `mask_token`)
- Validate input before shell commands
- Use `sudo` only where needed
@@ -249,6 +278,8 @@ run sudo apt install -y git
### Adding a Feature
Start from the template: `cp templates/feature.sh features/<name>.sh`.
1. Create `features/<name>.sh` following the CLI tool template (shebang, `set -euo pipefail`, `--help`).
2. Nothing else is registered — `./install.sh --feature` auto-discovers it, copies it to `/usr/local/bin/`, asks before overwriting an existing file, and sets its flag.
3. If the feature backs a systemd service, gate the service on the flag in `postinstall.sh` (see below).
+1 -1
View File
@@ -31,7 +31,7 @@ tries pos-docker-compose-up (not found)
tries pos-docker-compose (found) → runs with args "up jellyfin"
```
`pos help <command>` runs `<that command> --help`. Running `pos` with no args prints the built-in usage text (which doubles as the category cheat-sheet).
`pos help <full command>` runs that tool's `--help` (e.g. `pos help communication telegram`, `pos help docker compose` — the words are joined with dashes). Running `pos` with no args prints the built-in usage text (which doubles as the category cheat-sheet).
---
+3 -2
View File
@@ -94,12 +94,13 @@ esac
if [ "${1:-}" = "help" ]; then
shift
[ $# -eq 0 ] && usage
cmd="pos-${1// /-}"
cmd="pos-$*"
cmd="${cmd// /-}"
if command -v "$cmd" &>/dev/null; then
exec "$cmd" --help
fi
[ -x "$self/$cmd" ] && exec "$self/$cmd" --help
echo "pos: unknown command '$1'" >&2
echo "pos: unknown command '$*'" >&2
echo "Run 'pos --help' to see available commands." >&2
exit 1
fi
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env bash
set -euo pipefail
# ────────────────────────────────────────────────────────────────
# TEMPLATE — new optional app installer
#
# 1. Copy: cp templates/app.sh apps/<category>/<name>.sh
# Categories: browsers, development, media, networking,
# remote-access, system, utilities.
# 2. Fill in install_myapp() / uninstall_myapp() (rename to your app).
# 3. Docs: add a row to the catalog table in DOC/APPS.md.
#
# Auto-appears in the `apps/install.sh` picker — no registration.
# Run as: bash apps/install.sh <name> (install)
# bash apps/install.sh --uninstall <name>
# ────────────────────────────────────────────────────────────────
source "$(dirname "$0")/../../lib/common.sh"
install_myapp() {
command -v myapp &>/dev/null && { log "myapp already installed"; return 0; }
# Pick one method (see conventions below) and wrap it in spawn:
spawn "Installing myapp" sudo apt install -y myapp
log "Run myapp: myapp"
}
uninstall_myapp() {
command -v myapp &>/dev/null || { log "myapp not installed"; return 0; }
spawn "Removing myapp" sudo apt purge -y myapp
spawn "Cleaning up dependencies" sudo apt autoremove -y
}
case "${1:-}" in
uninstall) uninstall_myapp ;;
*) install_myapp ;;
esac
# ────────────────────────────────────────────────────────────────
# CONVENTIONS — pick the install method that fits:
#
# APT package sudo apt install -y myapp
# remove: sudo apt purge -y myapp + apt autoremove -y
#
# Repo-based app add the apt repo in install; in uninstall also
# remove the .list file and keyring:
# sudo rm -f /etc/apt/sources.list.d/myapp.list /usr/share/keyrings/...
#
# Official script spawn "Installing myapp" bash -c "curl -fsSL https://.../install.sh | sh"
# uninstall: remove the installed binary/files
#
# Flatpak sudo flatpak install -y flathub <app-id>
# remove: sudo flatpak uninstall -y <app-id>
#
# .deb download download to temp, sudo apt install -y ./file.deb
#
# File/AppImage install under /opt/<app>; in uninstall remove the
# files, symlinks, and desktop entries
#
# usermod for groups sudo usermod -aG <group> "$USER" + print a
# re-login reminder
#
# Every app MUST be idempotent (guard install AND uninstall) and MUST
# provide uninstall_myapp() + the uninstall case dispatch above.
# ────────────────────────────────────────────────────────────────
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env bash
set -euo pipefail
# ────────────────────────────────────────────────────────────────
# TEMPLATE — new feature script
#
# 1. Copy: cp templates/feature.sh features/<name>.sh
# 2. Docs: DOC/AGENT_Context_Project.md file table line counts.
#
# Installed on demand with: ./install.sh --feature
# → copied to /usr/local/bin/<name>.sh (chmod 755)
# → flag "<name>" is set (basename of the file, minus .sh)
# Features are user-customizable — install.sh never overwrites an
# existing /usr/local/bin copy without asking.
#
# If a systemd service depends on this feature, gate it in
# postinstall.sh's systemd loop:
# if [ "$svc_name" = "<name>.service" ] && ! flag_is_set <name>; then
# warn "<name> feature not installed — skipping <name>.service"
# continue
# fi
# ────────────────────────────────────────────────────────────────
# Robust flags.sh load — works from the repo checkout AND from
# /usr/local/bin after install.sh (which copies lib/flags.sh there).
source "$(dirname "$0")/../lib/flags.sh" 2>/dev/null || source "$(dirname "$0")/flags.sh"
# Self-name → matches the flag install.sh sets for this feature.
FEATURE_NAME="$(basename "$0")"
FEATURE_NAME="${FEATURE_NAME%.sh}"
usage() {
cat <<EOF
Usage: ${FEATURE_NAME}.sh [options]
<describe what this feature does>
Installed via: ./install.sh --feature
Flag: ${FEATURE_NAME}
EOF
exit 0
}
case "${1:-}" in
-h|--help) usage ;;
esac
# ── script logic ────────────────────────────────────────────────
# Feature scripts may run repeatedly (e.g. at every boot via a
# systemd service), so keep them idempotent.
#
# flag_is_set "$FEATURE_NAME" || exit 0 # bail when not installed
# v=$(flag_value "$FEATURE_NAME") # read an optional value
# flag_clear "$FEATURE_NAME" # uninstall behavior
+52
View File
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
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
# 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.
#
# Invoked as: pos <category> <command> [args]
# ────────────────────────────────────────────────────────────────
# Robust common.sh load — works from the repo checkout AND from
# /usr/local/bin after install.sh (which copies lib/common.sh there).
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
# Optional runtime config (see DEV.md "Config files"):
# CONFIG_FILE="$HOME/.config/linux_post_install/<tool>.env"
usage() {
cat <<EOF
Usage: pos <category> <command> [args]
<describe what this command does>
Examples:
pos <category> <command> arg1
EOF
exit 0
}
case "${1:-}" in
-h|--help) usage ;;
esac
# ── script logic ────────────────────────────────────────────────
# Use helpers from common.sh: log / warn / err / ok / section /
# step / run (respects --dry-run) / spawn / confirm.
#
# command -v <dep> &>/dev/null || err "<dep> not found"
# run sudo <command> # dry-run aware
# log "done" # green [+] message
#
# Exit 0 on success, err() exits 1 on failure.