feat: install.sh version gate — --force + per-commit 0.0cN
gates / consistency-and-conventions (push) Successful in 33s
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:
@@ -42,6 +42,9 @@ summary (newest last).
|
||||
|
||||
## Done
|
||||
|
||||
- **2026-09-08** — `install.sh` version gate (Architect→Builder): skip+abort when installed version == current version, `--force` to bypass, version scheme `0.0c<git commit count>` (auto-bumps per commit). `install_version()` derives `0.0c$(git rev-list --count HEAD)`; empty when `.git` absent → gate skipped (silently); `INSTALL_VERSION_OVERRIDE` env var (presence-check) = test seam. Gate after arg-parse, before phases, numeric comparison (strip `0.0c`, `-eq`); `log "Already installed ($CURRENT_VERSION). Use --force to re-install."` / `--dry-run` → `(dry-run) Would skip install: already at version $CURRENT_VERSION`, both exit 0. `FORCE=0` init, `--force` parse + usage. `flag_set installed_version "$CURRENT_VERSION"` after "Bootstrap complete" banner (only when DRY_RUN≠1 and version non-empty; even under --force). New `tests/t-install-version.sh` (21 checks / 9 cases). Docs: README/SCRIPTS/AGENT_Context (flags, flow, line count 248→301, tests/README row). Verified: `bash -n` clean; `make gen` idempotent; `make check` OK; `make lint` 0 FAIL / 0 WARN; `make test` suite green.
|
||||
|
||||
|
||||
- **2026-09-07** — `pos ai` API-key contract mismatch fixed (Architect→Builder→Reviewer; docs/history evidence): docs claimed `AI_API_KEY` was the required primary key, but `resolve_key()` read only provider-specific keys (`7ae2e77` had removed shared-key priority to fix cross-provider leakage; docs never updated). Decision C: provider key stays primary (leakage guard intact), legacy `AI_API_KEY` honored as backward-compat fallback when the provider's own key is empty; `cmd_providers()` "configured" mirrors it; `require_key()` messages byte-stable; `AI_API_KEY` NOT re-added to `# POS_CONFIG:`/`# PROVIDER_CONFIG:` registry. Docs reworded (POS.md rows 91/96/98 + precedence, howto/ai.md first-run hints, HOWTO.md, AGENT_Context 2 prose spots, config/ai.env comment). New regression `tests/t-ai-key-resolution.sh` (24 checks / 10 cases: gemini+openrouter via provider key only, via AI_API_KEY only, both→provider wins, env-wins, llamacpp no-key, missing-key message, providers configured status). Verified: suite 19 files / 440 checks / 0 fail / 0 skip; `make gen` byte-idempotent; `make check` OK; `make lint` 0 FAIL, 0 WARN; Reviewer APPROVE_WITH_NOTES with mutation-based disproof (inverted precedence → C3/C6 fail).
|
||||
|
||||
- **2026-09-07** — opencode project skill: created `.opencode/skills/linux-post-install/SKILL.md` (repo had no `.opencode/`). Skill encodes the repo's operational playbook for agents: repo shape, `pos` tool model (`# POS:` header system, exec-bit, deps-guard-before-help, INTERACTIVE_CMDS, determinism), doc authority order (MAINTENANCE Phase 0: templates → DEV.md → AGENTS.md → code), Definition of Done gates (`make gen` ×2 idempotent → `make check` → `make lint` 0/0 → `make test` 18 files/416 checks), test conventions (hard-skip contract, negative controls), and repo commands (ci-status, gitea API, pos tree/config). Frontmatter validated (name matches folder, description with trigger keywords); auto-discovered at `.opencode/skills/` — no opencode.json change needed; restart opencode to load.
|
||||
|
||||
@@ -11,18 +11,18 @@
|
||||
<!-- GEN:START docmap -->
|
||||
| ## 1. Project Overview | 28–43 |
|
||||
| ## 2. Directory Structure | 44–210 |
|
||||
| ## 3. Installation Flow | 211–264 |
|
||||
| ## 4. The `pos` CLI System | 265–346 |
|
||||
| ## 5. Shared Library — `lib/common.sh` | 347–378 |
|
||||
| ## 6. Docker Compose / ScaleTail | 379–421 |
|
||||
| ## 7. Optional Apps (`apps/`) | 422–451 |
|
||||
| ## 8. Entertainment Module | 452–465 |
|
||||
| ## 9. Systemd Services | 466–477 |
|
||||
| ## 10. Configuration Files | 478–504 |
|
||||
| ## 11. Coding Conventions | 505–537 |
|
||||
| ## 12. Development Workflow | 538–590 |
|
||||
| ## 13. Key File Quick Reference | 591–666 |
|
||||
| ## 14. Common Tasks for Agents | 667–700 |
|
||||
| ## 3. Installation Flow | 211–269 |
|
||||
| ## 4. The `pos` CLI System | 270–351 |
|
||||
| ## 5. Shared Library — `lib/common.sh` | 352–383 |
|
||||
| ## 6. Docker Compose / ScaleTail | 384–426 |
|
||||
| ## 7. Optional Apps (`apps/`) | 427–456 |
|
||||
| ## 8. Entertainment Module | 457–470 |
|
||||
| ## 9. Systemd Services | 471–482 |
|
||||
| ## 10. Configuration Files | 483–509 |
|
||||
| ## 11. Coding Conventions | 510–542 |
|
||||
| ## 12. Development Workflow | 543–595 |
|
||||
| ## 13. Key File Quick Reference | 596–671 |
|
||||
| ## 14. Common Tasks for Agents | 672–705 |
|
||||
<!-- 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
@@ -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 |
|
||||
|
||||
@@ -35,6 +35,7 @@ cd Linux_post_install
|
||||
| `--apps` | Interactive app picker after core install |
|
||||
| `--full` | Core install + all apps (non-interactive) |
|
||||
| `--dry-run` | Preview without executing |
|
||||
| `--force` | Re-install even if the version matches |
|
||||
| `--skip <phase>` | Skip a phase: `preinstall`, `scripts`, `postinstall`, `scalepoint`, `apps` |
|
||||
| `--steps <spec>` | Run only specific phases, e.g. `--steps 1,3` |
|
||||
| `--no-color` | Disable colored output |
|
||||
|
||||
+40
@@ -14,11 +14,26 @@ fi
|
||||
source "$(dirname "$0")/lib/common.sh"
|
||||
source "$(dirname "$0")/lib/flags.sh"
|
||||
|
||||
# Return the current install.sh version (e.g. "0.0c174").
|
||||
# Derived from git commit count. Empty when .git is absent.
|
||||
# Override with INSTALL_VERSION_OVERRIDE for testing.
|
||||
install_version() {
|
||||
if [ "${INSTALL_VERSION_OVERRIDE+x}" ]; then
|
||||
printf '%s' "$INSTALL_VERSION_OVERRIDE"
|
||||
return
|
||||
fi
|
||||
local repo_dir count
|
||||
repo_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
count="$(git -C "$repo_dir" rev-list --count HEAD 2>/dev/null)" || { printf ''; return; }
|
||||
printf '0.0c%s' "$count"
|
||||
}
|
||||
|
||||
# Exported so child phases (preinstall.sh, postinstall.sh) inherit it —
|
||||
# otherwise '--dry-run' silently executes them for real.
|
||||
export DRY_RUN=0
|
||||
RUN_APPS=0
|
||||
RUN_FEATURES=0
|
||||
FORCE=0
|
||||
SKIP_PHASES=""
|
||||
STEPS_SPEC=""
|
||||
|
||||
@@ -58,6 +73,7 @@ Options:
|
||||
--full Core install + all optional apps (non-interactive)
|
||||
--feature Install features/ scripts (prompts before overwriting)
|
||||
--dry-run Show what would be done 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
|
||||
@@ -79,6 +95,7 @@ while [[ $# -gt 0 ]]; do
|
||||
--full) RUN_APPS=2; shift ;;
|
||||
--feature) RUN_FEATURES=1; shift ;;
|
||||
--dry-run) DRY_RUN=1; shift ;;
|
||||
--force) FORCE=1; shift ;;
|
||||
--skip)
|
||||
[ -z "${2:-}" ] && err "Missing value for --skip"
|
||||
SKIP_PHASES="${SKIP_PHASES:+$SKIP_PHASES,}$2"
|
||||
@@ -95,6 +112,24 @@ while [[ $# -gt 0 ]]; do
|
||||
esac
|
||||
done
|
||||
|
||||
# ── Version gate ────────────────────────────────────────────────
|
||||
CURRENT_VERSION="$(install_version)"
|
||||
if [ "${FORCE:-0}" -ne 1 ] && [ -n "$CURRENT_VERSION" ]; then
|
||||
INSTALLED_VERSION="$(flag_value installed_version 2>/dev/null)" || true
|
||||
if [ -n "$INSTALLED_VERSION" ]; then
|
||||
current_num="${CURRENT_VERSION#0.0c}"
|
||||
installed_num="${INSTALLED_VERSION#0.0c}"
|
||||
if [ "$current_num" -eq "$installed_num" ] 2>/dev/null; then
|
||||
if [ "${DRY_RUN:-0}" -eq 1 ]; then
|
||||
log "(dry-run) Would skip install: already at version $CURRENT_VERSION"
|
||||
else
|
||||
log "Already installed ($CURRENT_VERSION). Use --force to re-install."
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Phase runner ────────────────────────────────────────────────
|
||||
# Phase names → numbers: preinstall=1 scripts=2 postinstall=3 scalepoint=4
|
||||
should_run() {
|
||||
@@ -248,6 +283,11 @@ echo "${GREEN}══════════════════════
|
||||
echo "${GREEN} Bootstrap complete ($(timer_stop))${RESET}"
|
||||
echo "${GREEN}════════════════════════════════════════════${RESET}"
|
||||
|
||||
# ── Record installed version ────────────────────────────────────
|
||||
if [ "${DRY_RUN:-0}" -ne 1 ] && [ -n "${CURRENT_VERSION:-}" ]; then
|
||||
flag_set installed_version "$CURRENT_VERSION"
|
||||
fi
|
||||
|
||||
# ── Optional apps ──────────────────────────────────────────────
|
||||
if [ "$RUN_APPS" -eq 1 ]; then
|
||||
echo
|
||||
|
||||
@@ -53,4 +53,5 @@ silently.
|
||||
| `t-uninstall-manifest.sh` | install.sh ↔ POS_LIBS symmetry, user-unit discovery, marker-driven plugin removal |
|
||||
| `t-gen-docs-drift.sh` | `make gen` idempotence on a pristine tracked tree (CI drift gate) |
|
||||
| `t-lint-gate.sh` | `make lint` green on the real tree; planted violations are caught and named |
|
||||
| `t-install-version.sh` | install.sh version gate: match→skip, mismatch→proceed, --force bypass, dry-run variant, flag write, numeric comparison |
|
||||
| `t-share-mountpoint.sh` | share-client `ask_mountpoint` UX: existing/new/declined/rejected paths, confirm gate, mkdir side effects, non-TTY stdin contract, static `n`→`t` guards |
|
||||
@@ -0,0 +1,174 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# t-install-version.sh — install.sh version gate contract:
|
||||
# match→skip with "Already installed", mismatch→proceed, --force bypass,
|
||||
# no-git (empty) skip, dry-run variant, flag write after run,
|
||||
# numeric comparison correctness (0.0c10 > 0.0c9).
|
||||
#
|
||||
# Seam: INSTALL_VERSION_OVERRIDE env var (presence check → empty = no-git path),
|
||||
# FLAGS_DIR env var (existing flag store seam).
|
||||
#
|
||||
# Proceed-path cases (2/3/4/5/8) use --skip all phases instead of --dry-run
|
||||
# because postinstall.sh is not fully dry-run safe (cat on non-existent file
|
||||
# after dry-run copy). Skipping all phases is functionally equivalent — the
|
||||
# gate fires before phases, and the banner + flag write still execute.
|
||||
|
||||
run_test() {
|
||||
local sandbox stubs flags_dir install_sh
|
||||
|
||||
sandbox="$(mksandbox install-version)"
|
||||
stubs="$sandbox/stubs"
|
||||
flags_dir="$sandbox/flags"
|
||||
mkdir -p "$stubs" "$flags_dir"
|
||||
|
||||
install_sh="$ROOT/install.sh"
|
||||
|
||||
# sudo stub: just exec its arguments (bypasses real sudo in sandbox)
|
||||
cat > "$stubs/sudo" <<'STUB'
|
||||
#!/usr/bin/env bash
|
||||
exec "$@"
|
||||
STUB
|
||||
chmod +x "$stubs/sudo"
|
||||
|
||||
# install stub: delegate to the real install binary (needed so -d/-m flags
|
||||
# work; we only shadow 'sudo' to avoid needing a tty/password)
|
||||
cat > "$stubs/install" <<'STUB'
|
||||
#!/usr/bin/env bash
|
||||
exec /usr/bin/install "$@"
|
||||
STUB
|
||||
chmod +x "$stubs/install"
|
||||
|
||||
# mkdir stub: just exec its arguments
|
||||
cat > "$stubs/mkdir" <<'STUB'
|
||||
#!/usr/bin/env bash
|
||||
exec "$@"
|
||||
STUB
|
||||
chmod +x "$stubs/mkdir"
|
||||
|
||||
local base_env=(PATH="$stubs:/usr/bin:/bin"
|
||||
HOME="$sandbox/home"
|
||||
FLAGS_DIR="$flags_dir")
|
||||
|
||||
# Helper: run install.sh with env overrides and capture output+rc.
|
||||
run_install() {
|
||||
local envs=() args=()
|
||||
while [ $# -gt 0 ]; do
|
||||
if [ "$1" = "--" ]; then shift; break; fi
|
||||
envs+=("$1"); shift
|
||||
done
|
||||
args=("$@")
|
||||
set +e
|
||||
TR_OUT="$(env "${base_env[@]}" "${envs[@]}" bash "$install_sh" "${args[@]}" 2>&1)"
|
||||
TR_RC=$?
|
||||
set -e
|
||||
}
|
||||
|
||||
# Helper: set a flag value directly (for pre-seeding installed_version)
|
||||
set_flag() {
|
||||
local name="$1" value="$2"
|
||||
mkdir -p "$flags_dir"
|
||||
printf '%s' "$value" > "$flags_dir/$name"
|
||||
}
|
||||
|
||||
# Helper: read a flag value
|
||||
get_flag() {
|
||||
local name="$1"
|
||||
local f="$flags_dir/$name"
|
||||
[ -f "$f" ] && cat "$f" || printf ''
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Case 1: Match → skip ("Already installed")
|
||||
# Gate exits BEFORE any phase — safe without --dry-run or --skip.
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
set_flag "installed_version" "0.0c10"
|
||||
run_install INSTALL_VERSION_OVERRIDE=0.0c10
|
||||
check_rc "C1 match→skip exit code" 0 "$TR_RC"
|
||||
check_contains "C1 match→skip message" "Already installed (0.0c10)" "$TR_OUT"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Case 2: Mismatch → proceed (gate doesn't match, phases skipped)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
rm -f "$flags_dir/installed_version"
|
||||
set_flag "installed_version" "0.0c10"
|
||||
run_install INSTALL_VERSION_OVERRIDE=0.0c11 -- --skip preinstall,scripts,postinstall,scalepoint
|
||||
check_rc "C2 mismatch→proceed exit code" 0 "$TR_RC"
|
||||
check_not_contains "C2 mismatch does not skip" "Already installed" "$TR_OUT"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Case 3: Force bypasses match
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
rm -f "$flags_dir/installed_version"
|
||||
set_flag "installed_version" "0.0c10"
|
||||
run_install INSTALL_VERSION_OVERRIDE=0.0c10 -- --skip preinstall,scripts,postinstall,scalepoint --force
|
||||
check_rc "C3 force bypasses exit code" 0 "$TR_RC"
|
||||
check_not_contains "C3 force bypasses skip" "Already installed" "$TR_OUT"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Case 4: No git (empty version via presence check)
|
||||
# INSTALL_VERSION_OVERRIDE="" with ${var+x} → empty → gate skipped
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
rm -f "$flags_dir/installed_version"
|
||||
set_flag "installed_version" "0.0c10"
|
||||
run_install INSTALL_VERSION_OVERRIDE="" -- --skip preinstall,scripts,postinstall,scalepoint
|
||||
check_rc "C4 empty override→proceeds exit code" 0 "$TR_RC"
|
||||
check_not_contains "C4 empty override does not skip" "Already installed" "$TR_OUT"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Case 5: No installed flag → gate doesn't block
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
rm -f "$flags_dir/installed_version"
|
||||
run_install INSTALL_VERSION_OVERRIDE=0.0c10 -- --skip preinstall,scripts,postinstall,scalepoint
|
||||
check_rc "C5 no flag→proceeds exit code" 0 "$TR_RC"
|
||||
check_not_contains "C5 no flag does not skip" "Already installed" "$TR_OUT"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Case 6: Dry-run + match → "Would skip" (not "Already installed")
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
rm -f "$flags_dir/installed_version"
|
||||
set_flag "installed_version" "0.0c10"
|
||||
run_install INSTALL_VERSION_OVERRIDE=0.0c10 -- --dry-run
|
||||
check_rc "C6 dry-run match exit code" 0 "$TR_RC"
|
||||
check_contains "C6 dry-run shows Would skip" "Would skip install" "$TR_OUT"
|
||||
check_not_contains "C6 dry-run not Already installed" "Already installed" "$TR_OUT"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Case 7: Flag written after successful run
|
||||
# All phases skipped; sudo/install/mkdir stubs on PATH so
|
||||
# flag_set's `run sudo install` actually creates the flag file.
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
rm -f "$flags_dir/installed_version"
|
||||
set_flag "installed_version" "0.0c10"
|
||||
run_install INSTALL_VERSION_OVERRIDE=0.0c11 -- --skip preinstall,scripts,postinstall,scalepoint
|
||||
check_rc "C7 all-skip exit code" 0 "$TR_RC"
|
||||
local flag_val
|
||||
flag_val="$(get_flag installed_version)"
|
||||
check_eq "C7 flag written with current version" "0.0c11" "$flag_val"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Case 8: Numeric comparison — 0.0c10 > 0.0c9 (not blocked)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
rm -f "$flags_dir/installed_version"
|
||||
set_flag "installed_version" "0.0c9"
|
||||
run_install INSTALL_VERSION_OVERRIDE=0.0c10 -- --skip preinstall,scripts,postinstall,scalepoint
|
||||
check_rc "C8 numeric 10>9 proceeds exit code" 0 "$TR_RC"
|
||||
check_not_contains "C8 numeric 10>9 does not skip" "Already installed" "$TR_OUT"
|
||||
|
||||
# Also test: 0.0c9 with installed 0.0c10 → NOT a match
|
||||
rm -f "$flags_dir/installed_version"
|
||||
set_flag "installed_version" "0.0c10"
|
||||
run_install INSTALL_VERSION_OVERRIDE=0.0c9 -- --skip preinstall,scripts,postinstall,scalepoint
|
||||
check_rc "C8 numeric 9≠10 proceeds exit code" 0 "$TR_RC"
|
||||
check_not_contains "C8 numeric 9≠10 does not skip" "Already installed" "$TR_OUT"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Case 9: Force + match → still writes flag
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
rm -f "$flags_dir/installed_version"
|
||||
set_flag "installed_version" "0.0c10"
|
||||
run_install INSTALL_VERSION_OVERRIDE=0.0c10 -- --skip preinstall,scripts,postinstall,scalepoint --force
|
||||
check_rc "C9 force+match exit code" 0 "$TR_RC"
|
||||
local flag_val9
|
||||
flag_val9="$(get_flag installed_version)"
|
||||
check_eq "C9 force writes flag" "0.0c10" "$flag_val9"
|
||||
}
|
||||
Reference in New Issue
Block a user