feat: install.sh version gate — --force + per-commit 0.0cN
gates / consistency-and-conventions (push) Successful in 33s

Architect-approved (C): install.sh now skips+aborts (exit 0) when the
installed version equals the current git-derived version, with a --force
flag to re-install. Version scheme 0.0cN (N = git commit count) bumps by
construction on every commit.

- install_version(): derives 0.0c$count via git rev-list; empty when .git
  absent (gate skipped); INSTALL_VERSION_OVERRIDE presence-check seam for
  tests (empty override simulates no-git deterministically)
- Gate after arg parse, before phases; numeric comparison (strip 0.0c, -eq);
  messages: 'Already installed (X). Use --force to re-install.' and
  '(dry-run) Would skip install: already at version X', both exit 0
- flag_set installed_version after 'Bootstrap complete' banner (not in
  dry-run, even under --force, never on phase failure)
- Docs: SCRIPTS.md flag table + gate description, AGENT_Context line count
  248->301 + flags + flow diagram, README --force row, tests/README row,
  AGENT_TODO Done entry

Verified: new tests/t-install-version.sh 21 checks (9 contracted cases,
real install.sh + hermetic env seams); suite 20 files / 461 checks /
0 fail / 0 skip; make gen byte-idempotent; make check OK; make lint
0 FAIL, 0 WARN; bash -n clean; git diff --check clean; Reviewer
APPROVE_WITH_NOTES with 3 mutation disproofs (8/21, 9/21, 4/21 fail)
This commit is contained in:
Your Name
2026-09-08 10:17:31 -04:00
parent 9ef42c5fd1
commit 9a164462e7
7 changed files with 241 additions and 15 deletions
+19 -14
View File
@@ -11,18 +11,18 @@
<!-- GEN:START docmap -->
| ## 1. Project Overview | 2843 |
| ## 2. Directory Structure | 44210 |
| ## 3. Installation Flow | 211264 |
| ## 4. The `pos` CLI System | 265346 |
| ## 5. Shared Library — `lib/common.sh` | 347378 |
| ## 6. Docker Compose / ScaleTail | 379421 |
| ## 7. Optional Apps (`apps/`) | 422451 |
| ## 8. Entertainment Module | 452465 |
| ## 9. Systemd Services | 466477 |
| ## 10. Configuration Files | 478504 |
| ## 11. Coding Conventions | 505537 |
| ## 12. Development Workflow | 538590 |
| ## 13. Key File Quick Reference | 591666 |
| ## 14. Common Tasks for Agents | 667700 |
| ## 3. Installation Flow | 211269 |
| ## 4. The `pos` CLI System | 270351 |
| ## 5. Shared Library — `lib/common.sh` | 352383 |
| ## 6. Docker Compose / ScaleTail | 384426 |
| ## 7. Optional Apps (`apps/`) | 427456 |
| ## 8. Entertainment Module | 457470 |
| ## 9. Systemd Services | 471482 |
| ## 10. Configuration Files | 483509 |
| ## 11. Coding Conventions | 510542 |
| ## 12. Development Workflow | 543595 |
| ## 13. Key File Quick Reference | 596671 |
| ## 14. Common Tasks for Agents | 672705 |
<!-- GEN:END docmap -->
## 1. Project Overview
@@ -211,7 +211,11 @@ Linux_post_install/
## 3. Installation Flow
```
User runs: ./install.sh [--apps|--full|--feature|--dry-run|--skip <phase>|--steps <spec>]
User runs: ./install.sh [--apps|--full|--feature|--dry-run|--force|--skip <phase>|--steps <spec>]
├─ Version gate: compares installed version (flag) vs current (0.0c<commit count>)
│ └─ match + no --force → "Already installed. Use --force to re-install." / exit 0
│ (--dry-run variant prints "Would skip install"); no git or no flag → skip gate
├─ Phase 1: preinstall.sh (requires root)
│ └─ apt update + installs 25+ packages + yt-dlp + fail2ban
@@ -249,6 +253,7 @@ User runs: ./install.sh [--apps|--full|--feature|--dry-run|--skip <phase>|--step
| `--full` | Core install + all apps (non-interactive) |
| `--feature` | Install `features/` scripts to `/usr/local/bin/` (asks before overwriting), set their flags |
| `--dry-run` | Preview without executing |
| `--force` | Re-install even if the version matches |
| `--skip <phase>` | Skip a phase (repeatable): `preinstall`, `scripts`, `postinstall`, `scalepoint`, `apps` |
| `--steps <spec>` | Run only specific phases. Format: `1,3,4` or `1-3` |
| `--no-color` | Disable colored output |
@@ -592,7 +597,7 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
| File | Lines | Purpose |
|------|-------|---------|
| `install.sh` | 248 | Main orchestrator — 4 phases with CLI flags, `--feature`, prebuilt arch bins |
| `install.sh` | 301 | Main orchestrator — 4 phases with CLI flags, `--feature`, `--force`, version gate, prebuilt arch bins |
| `preinstall.sh` | 76 | System packages + hotspot deps + yt-dlp + fail2ban |
| `postinstall.sh` | 168 | fail2ban config, PATH, bash completion, systemd (flag-gated) |
| `lib/common.sh` | 151 | Shared library (log/warn/err/run/spawn, dry-run aware, `load_system_env`, CONFIG_DIR) |
+3 -1
View File
@@ -34,7 +34,8 @@ Everything that runs during the bootstrap install: `install.sh`, `preinstall.sh`
1. **Pre-parse `--no-color`** before anything else, so colors are disabled early (`TERM=dumb` is exported).
2. Source `lib/common.sh` (logging, `run`, `spawn`, …) and `lib/flags.sh` (feature flags).
3. Parse CLI options.
4. For each phase, `should_run <num> <name>` decides whether to run it:
4. **Version gate:** derive the current version (`0.0c<git commit count>` via `install_version()`; empty when `.git` is absent). If the installed version (stored as the `installed_version` flag) matches and `--force` is not given, skip the install — with `--dry-run` it prints `(dry-run) Would skip install: already at version <v>`, otherwise `Already installed (<v>). Use --force to re-install.` and exits 0. When no `installed_version` flag exists or the current version cannot be determined (no `.git`), the gate is skipped.
5. For each phase, `should_run <num> <name>` decides whether to run it:
- `--skip <phase>` removes a phase (takes precedence).
- `--steps <spec>` restricts the run to the listed phases only (`1,3,4` or `1-3`).
- Phase map: `1=preinstall`, `2=scripts`, `3=postinstall`, `4=scalepoint` (+ `apps` handled separately).
@@ -63,6 +64,7 @@ No config file — everything is command-line:
| `--full` | Core install + every app (non-interactive) |
| `--feature` | Install `features/` scripts to `/usr/local/bin/` (prompts on overwrite), sets their flags |
| `--dry-run` | Log every action instead of executing. **Note:** applies to `install.sh` itself; `postinstall.sh` runs as a subprocess and does not inherit `DRY_RUN` |
| `--force` | Re-install even if the version matches |
| `--skip <phase>` | Skip a phase (repeatable): `preinstall`, `scripts`, `postinstall`, `scalepoint`, `apps` |
| `--steps <spec>` | Run only listed phases: `1,3,4` or `1-3` |
| `--no-color` | Disable colored output |