feat: add feature-flag subsystem with on-demand features install
- Move autostart.sh from bin/ to features/ so re-installs never reset it - Add lib/flags.sh (flag_set/clear/is_set/value/list/status) + flag-reader/set/clear CLIs - install.sh --feature copies features/* to /usr/local/bin with overwrite prompt, sets flags - postinstall.sh enables autostart.service only when the autostart flag is set - Document features & flags in README, DEV.md, AGENT_Context_Project.md
This commit is contained in:
@@ -29,7 +29,8 @@ Linux_post_install/
|
|||||||
├── postinstall.sh # Phase 3: PATH, bash completion, systemd services
|
├── postinstall.sh # Phase 3: PATH, bash completion, systemd services
|
||||||
│
|
│
|
||||||
├── lib/
|
├── lib/
|
||||||
│ └── common.sh # Shared library (colors, logging, spinner, timer, run)
|
│ ├── common.sh # Shared library (colors, logging, spinner, timer, run)
|
||||||
|
│ └── flags.sh # Feature flag store (flag_set/clear/is_set/value/list/status)
|
||||||
│
|
│
|
||||||
├── bin/ # CLI tools — installed to /usr/local/bin/
|
├── bin/ # CLI tools — installed to /usr/local/bin/
|
||||||
│ ├── pos # Main dispatcher — smart arg matching to pos-* scripts
|
│ ├── pos # Main dispatcher — smart arg matching to pos-* scripts
|
||||||
@@ -44,11 +45,16 @@ Linux_post_install/
|
|||||||
│ ├── pos-system-firewall # Interactive UFW manager (menu-driven, 284 lines)
|
│ ├── pos-system-firewall # Interactive UFW manager (menu-driven, 284 lines)
|
||||||
│ ├── pos-ssh-load-keys # Load SSH keys into ssh-agent
|
│ ├── pos-ssh-load-keys # Load SSH keys into ssh-agent
|
||||||
│ ├── pos-vbox # Disposable Docker-based "VMs"
|
│ ├── pos-vbox # Disposable Docker-based "VMs"
|
||||||
│ ├── autostart.sh # Boot-time script (via systemd)
|
│ ├── flag-reader # Inspect feature flags (list/status/--raw)
|
||||||
|
│ ├── flag-set # Set a feature flag (optionally with a value)
|
||||||
|
│ ├── flag-clear # Unset a feature flag
|
||||||
│ ├── wr-* # Legacy wrappers → pos (backward compat)
|
│ ├── wr-* # Legacy wrappers → pos (backward compat)
|
||||||
│ ├── mp3, mp4, vbox # Legacy convenience wrappers → pos
|
│ ├── mp3, mp4, vbox # Legacy convenience wrappers → pos
|
||||||
│ └── ssh-load-all # Legacy wrapper → pos ssh load-keys
|
│ └── ssh-load-all # Legacy wrapper → pos ssh load-keys
|
||||||
│
|
│
|
||||||
|
├── features/ # User-customizable scripts (installed via --feature)
|
||||||
|
│ └── autostart.sh # Boot-time script (via systemd, flag-gated)
|
||||||
|
│
|
||||||
├── apps/ # Optional desktop app installers (by category)
|
├── apps/ # Optional desktop app installers (by category)
|
||||||
│ ├── install.sh # Interactive picker / orchestrator
|
│ ├── install.sh # Interactive picker / orchestrator
|
||||||
│ ├── browsers/
|
│ ├── browsers/
|
||||||
@@ -99,20 +105,23 @@ Linux_post_install/
|
|||||||
## 3. Installation Flow
|
## 3. Installation Flow
|
||||||
|
|
||||||
```
|
```
|
||||||
User runs: ./install.sh [--apps|--full|--dry-run|--skip <phase>|--steps <spec>]
|
User runs: ./install.sh [--apps|--full|--feature|--dry-run|--skip <phase>|--steps <spec>]
|
||||||
│
|
│
|
||||||
├─ Phase 1: preinstall.sh (requires root)
|
├─ Phase 1: preinstall.sh (requires root)
|
||||||
│ └─ apt update + installs 25+ packages + yt-dlp + fail2ban
|
│ └─ apt update + installs 25+ packages + yt-dlp + fail2ban
|
||||||
│
|
│
|
||||||
├─ Phase 2: install.sh (requires root)
|
├─ Phase 2: install.sh (requires root)
|
||||||
│ └─ Copies bin/* → /usr/local/bin/ (chmod 755)
|
│ └─ Copies bin/* → /usr/local/bin/ (chmod 755)
|
||||||
│ └─ Copies lib/common.sh → /usr/local/bin/common.sh (chmod 644)
|
│ └─ Copies lib/common.sh + lib/flags.sh → /usr/local/bin/ (chmod 644)
|
||||||
|
│ └─ [if --feature] Copies features/* → /usr/local/bin/ (asks before overwriting),
|
||||||
|
│ then sets the matching feature flag
|
||||||
│
|
│
|
||||||
├─ Phase 3: postinstall.sh (runs as user)
|
├─ Phase 3: postinstall.sh (runs as user)
|
||||||
│ └─ Configures fail2ban (SSH jail: 5 retries, 1h ban)
|
│ └─ Configures fail2ban (SSH jail: 5 retries, 1h ban)
|
||||||
│ └─ PATH export in ~/.bashrc
|
│ └─ PATH export in ~/.bashrc
|
||||||
│ └─ Bash completion for pos CLI
|
│ └─ Bash completion for pos CLI
|
||||||
│ └─ Copies systemd/*.service → /etc/systemd/system/, enables them
|
│ └─ Copies systemd/*.service → /etc/systemd/system/, enables them
|
||||||
|
│ (autostart.service only when the `autostart` flag is set)
|
||||||
│
|
│
|
||||||
├─ Phase 4: ScaleTail clone
|
├─ Phase 4: ScaleTail clone
|
||||||
│ └─ Shallow-clones ScaleTail templates to /usr/local/share/linux_post_install/scale-tail
|
│ └─ Shallow-clones ScaleTail templates to /usr/local/share/linux_post_install/scale-tail
|
||||||
@@ -129,6 +138,7 @@ User runs: ./install.sh [--apps|--full|--dry-run|--skip <phase>|--steps <spec>]
|
|||||||
|------|---------|
|
|------|---------|
|
||||||
| `--apps` | Run interactive app picker after core install |
|
| `--apps` | Run interactive app picker after core install |
|
||||||
| `--full` | Core install + all apps (non-interactive) |
|
| `--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 |
|
| `--dry-run` | Preview without executing |
|
||||||
| `--skip <phase>` | Skip a phase (repeatable): `preinstall`, `scripts`, `postinstall`, `scalepoint`, `apps` |
|
| `--skip <phase>` | Skip a phase (repeatable): `preinstall`, `scripts`, `postinstall`, `scalepoint`, `apps` |
|
||||||
| `--steps <spec>` | Run only specific phases. Format: `1,3,4` or `1-3` |
|
| `--steps <spec>` | Run only specific phases. Format: `1,3,4` or `1-3` |
|
||||||
@@ -316,6 +326,15 @@ All `.service` files in `systemd/` are automatically copied to `/etc/systemd/sys
|
|||||||
- `~/.config/linux_post_install/compose.env` — Docker Compose global defaults
|
- `~/.config/linux_post_install/compose.env` — Docker Compose global defaults
|
||||||
- `~/.bashrc` — Modified by postinstall (PATH, bash completion)
|
- `~/.bashrc` — Modified by postinstall (PATH, bash completion)
|
||||||
|
|
||||||
|
### Feature Flags
|
||||||
|
|
||||||
|
System-wide flag store at `/usr/local/share/linux_post_install/flags/`:
|
||||||
|
- One file per flag; **presence = set**, **file content = optional value** (dir 755, files 644).
|
||||||
|
- Library: `lib/flags.sh` (installed as `/usr/local/bin/flags.sh`) — `flag_set <name> [value]`, `flag_clear <name>`, `flag_is_set <name>`, `flag_value <name>`, `flag_list`, `flag_status <name>`.
|
||||||
|
- CLI: `flag-reader` (list / status / `--raw`), `flag-set`, `flag-clear`.
|
||||||
|
- Set by `./install.sh --feature`; read by `postinstall.sh` to gate systemd enablement (e.g. `autostart.service` requires the `autostart` flag).
|
||||||
|
- Writes use `run` + `sudo`, so they respect `--dry-run`. `FLAGS_DIR` is env-overridable for tests.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 10. Coding Conventions
|
## 10. Coding Conventions
|
||||||
@@ -396,10 +415,15 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
|||||||
|
|
||||||
| File | Lines | Purpose |
|
| File | Lines | Purpose |
|
||||||
|------|-------|---------|
|
|------|-------|---------|
|
||||||
| `install.sh` | 149 | Main orchestrator — 4 phases with CLI flags |
|
| `install.sh` | 175 | Main orchestrator — 4 phases with CLI flags, `--feature` block |
|
||||||
| `preinstall.sh` | 52 | System packages + yt-dlp + fail2ban |
|
| `preinstall.sh` | 52 | System packages + yt-dlp + fail2ban |
|
||||||
| `postinstall.sh` | 90 | fail2ban config, PATH, bash completion, systemd |
|
| `postinstall.sh` | 97 | fail2ban config, PATH, bash completion, systemd (flag-gated) |
|
||||||
| `lib/common.sh` | 121 | Shared library |
|
| `lib/common.sh` | 121 | Shared library |
|
||||||
|
| `lib/flags.sh` | 60 | Feature flag store (set/clear/is_set/value/list/status) |
|
||||||
|
| `bin/flag-reader` | 58 | Inspect flags (list/status/`--raw`) |
|
||||||
|
| `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` | 145 | CLI dispatcher with smart arg matching + logging |
|
| `bin/pos` | 145 | CLI dispatcher with smart arg matching + logging |
|
||||||
| `bin/pos-docker-compose` | 363 | Largest script — full compose management |
|
| `bin/pos-docker-compose` | 363 | Largest script — full compose management |
|
||||||
| `bin/pos-system-firewall` | 284 | Interactive UFW manager |
|
| `bin/pos-system-firewall` | 284 | Interactive UFW manager |
|
||||||
@@ -417,7 +441,9 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
|||||||
|------|---------------|
|
|------|---------------|
|
||||||
| Add a new CLI tool | Create `bin/pos-<cat>-<cmd>`, add deps in `preinstall.sh` |
|
| Add a new CLI tool | Create `bin/pos-<cat>-<cmd>`, add deps in `preinstall.sh` |
|
||||||
| Add a new app installer | Create `apps/<name>.sh` (auto-discovered) |
|
| Add a new app installer | Create `apps/<name>.sh` (auto-discovered) |
|
||||||
| Add a systemd service | Create `systemd/<name>.service` (auto-installed by postinstall) |
|
| 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) |
|
||||||
|
| Inspect/set feature flags | `flag-reader`, `flag-set`, `flag-clear` (or source `lib/flags.sh`) |
|
||||||
| Modify package list | Edit `PACKAGES` array in `preinstall.sh` |
|
| Modify package list | Edit `PACKAGES` array in `preinstall.sh` |
|
||||||
| Change PATH or bash config | Edit `postinstall.sh` |
|
| Change PATH or bash config | Edit `postinstall.sh` |
|
||||||
| Modify fail2ban config | Edit jail.local section in `postinstall.sh` |
|
| Modify fail2ban config | Edit jail.local section in `postinstall.sh` |
|
||||||
|
|||||||
@@ -238,10 +238,49 @@ run sudo apt install -y git
|
|||||||
- CLI tools: `bin/pos-<category>-<command>`
|
- CLI tools: `bin/pos-<category>-<command>`
|
||||||
- Legacy wrappers: `bin/wr-*`
|
- Legacy wrappers: `bin/wr-*`
|
||||||
- App installers: `apps/<category>/<name>.sh`
|
- App installers: `apps/<category>/<name>.sh`
|
||||||
|
- Features: `features/<name>.sh`
|
||||||
- Lowercase with hyphens
|
- Lowercase with hyphens
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Features & Flags
|
||||||
|
|
||||||
|
`features/` holds scripts the user is likely to customize (e.g. `autostart.sh`). Unlike `bin/` (synced on every install), features are installed on demand and **never overwritten without asking**.
|
||||||
|
|
||||||
|
### Adding a Feature
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
### Flag System
|
||||||
|
|
||||||
|
System-wide flag store at `/usr/local/share/linux_post_install/flags/` (presence = set, content = optional value). Sourced via `lib/flags.sh` (or the installed `/usr/local/bin/flags.sh`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source "$(dirname "$0")/lib/flags.sh" 2>/dev/null || source "$(dirname "$0")/flags.sh"
|
||||||
|
|
||||||
|
flag_set autostart # green flag
|
||||||
|
flag_set app "2.1" # green flag with a value
|
||||||
|
flag_is_set autostart # test (0/1) — the primitive consumers use
|
||||||
|
flag_value app # → "2.1"
|
||||||
|
flag_list # names of all set flags
|
||||||
|
flag_clear autostart
|
||||||
|
```
|
||||||
|
|
||||||
|
Writes use `run` + `sudo`, so they respect `--dry-run`. CLI equivalents: `flag-reader`, `flag-set`, `flag-clear`.
|
||||||
|
|
||||||
|
**Example — service gated on a flag** (in `postinstall.sh`'s systemd loop):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
if [ "$svc_name" = "myapp.service" ] && ! flag_is_set myapp; then
|
||||||
|
warn "myapp feature not installed — skipping myapp.service"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Working with Systemd
|
## Working with Systemd
|
||||||
|
|
||||||
Create `systemd/<name>.service` — `postinstall.sh` copies it to `/etc/systemd/system/` and enables it automatically.
|
Create `systemd/<name>.service` — `postinstall.sh` copies it to `/etc/systemd/system/` and enables it automatically.
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ cd Linux_post_install
|
|||||||
./install.sh # core: packages + CLI + services + ScaleTail
|
./install.sh # core: packages + CLI + services + ScaleTail
|
||||||
./install.sh --apps # core + interactive app picker
|
./install.sh --apps # core + interactive app picker
|
||||||
./install.sh --full # core + all apps (non-interactive)
|
./install.sh --full # core + all apps (non-interactive)
|
||||||
|
./install.sh --feature # core + install features/ scripts (prompts on overwrite)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Flags:**
|
**Flags:**
|
||||||
@@ -35,6 +36,7 @@ cd Linux_post_install
|
|||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `--apps` | Run interactive app picker after core install |
|
| `--apps` | Run interactive app picker after core install |
|
||||||
| `--full` | Core install + all apps (no prompts) |
|
| `--full` | Core install + all apps (no prompts) |
|
||||||
|
| `--feature` | Install `features/` scripts to `/usr/local/bin/` (asks before overwriting), sets their feature flags |
|
||||||
| `--dry-run` | Preview without executing anything |
|
| `--dry-run` | Preview without executing anything |
|
||||||
| `--skip <phase>` | Skip a phase (repeatable): `preinstall`, `scripts`, `postinstall`, `scalepoint` |
|
| `--skip <phase>` | Skip a phase (repeatable): `preinstall`, `scripts`, `postinstall`, `scalepoint` |
|
||||||
| `--steps <spec>` | Run specific phases only, e.g. `--steps 1,3` or `--steps 1-3` |
|
| `--steps <spec>` | Run specific phases only, e.g. `--steps 1,3` or `--steps 1-3` |
|
||||||
@@ -51,6 +53,33 @@ cd Linux_post_install
|
|||||||
| 3 | `postinstall.sh` | Configures fail2ban, SSH agent, PATH, bash completion, systemd services |
|
| 3 | `postinstall.sh` | Configures fail2ban, SSH agent, PATH, bash completion, systemd services |
|
||||||
| 4 | ScaleTail clone | Downloads 119+ Docker Compose templates with Tailscale sidecar |
|
| 4 | ScaleTail clone | Downloads 119+ Docker Compose templates with Tailscale sidecar |
|
||||||
| 5 (opt) | `apps/install.sh` | Installs desktop apps you select |
|
| 5 (opt) | `apps/install.sh` | Installs desktop apps you select |
|
||||||
|
| opt | `./install.sh --feature` | Installs `features/` scripts (never overwrites without asking) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features & Flags
|
||||||
|
|
||||||
|
`features/` holds scripts you're likely to customize (like `autostart.sh`), kept out of `bin/` so a plain re-install never resets them.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./install.sh --feature # install features/ — asks before overwriting
|
||||||
|
```
|
||||||
|
|
||||||
|
- Each feature is copied to `/usr/local/bin/`; if the file already exists you're asked **"Overwrite? [y/N]"** — your existing config is kept by default.
|
||||||
|
- A successful install sets a **feature flag** at `/usr/local/share/linux_post_install/flags/` (presence = set, file content = optional value).
|
||||||
|
- Flags drive systemd: e.g. `autostart.service` is enabled only when the `autostart` flag is green.
|
||||||
|
|
||||||
|
Inspect and manage flags:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
flag-reader # list all flags + status
|
||||||
|
flag-reader autostart # check one flag (exit 0 if set)
|
||||||
|
flag-reader --raw autostart # print the raw value only (script-friendly)
|
||||||
|
flag-set autostart prod # set a flag, optionally with a value
|
||||||
|
flag-clear autostart # unset a flag
|
||||||
|
```
|
||||||
|
|
||||||
|
Any project script can `source lib/flags.sh` (or the installed `/usr/local/bin/flags.sh`) and use `flag_set`, `flag_is_set`, `flag_value`, `flag_clear`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Executable
+21
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
||||||
|
source "$(dirname "$0")/../lib/flags.sh" 2>/dev/null || source "$(dirname "$0")/flags.sh"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: flag-clear <name>
|
||||||
|
|
||||||
|
Unset a feature flag (remove it from $FLAGS_DIR).
|
||||||
|
Requires sudo.
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
-h|--help|"") usage ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
flag_clear "$1"
|
||||||
|
ok "flag cleared: $1"
|
||||||
Executable
+58
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
||||||
|
source "$(dirname "$0")/../lib/flags.sh" 2>/dev/null || source "$(dirname "$0")/flags.sh"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: flag-reader [OPTIONS] [name ...]
|
||||||
|
|
||||||
|
Show feature flag state. Flags live in $FLAGS_DIR
|
||||||
|
|
||||||
|
flag-reader list all flags with status
|
||||||
|
flag-reader <name>... status per flag (exit 0 if all are set)
|
||||||
|
flag-reader --raw <name> print the raw stored value only (script-friendly)
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
RAW=0
|
||||||
|
NAMES=()
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
-h|--help) usage ;;
|
||||||
|
--raw) RAW=1 ;;
|
||||||
|
-*) echo "ERROR: unknown option: $arg" >&2; exit 1 ;;
|
||||||
|
*) NAMES+=("$arg") ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$RAW" -eq 1 ]; then
|
||||||
|
[ "${#NAMES[@]}" -eq 1 ] || { echo "ERROR: --raw requires exactly one name" >&2; exit 1; }
|
||||||
|
flag_value "${NAMES[0]}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${#NAMES[@]}" -eq 0 ]; then
|
||||||
|
if [ -d "$FLAGS_DIR" ] && [ -n "$(ls -A "$FLAGS_DIR" 2>/dev/null)" ]; then
|
||||||
|
for f in "$FLAGS_DIR"/*; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
flag_status "$(basename "$f")"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
warn "no flags set"
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
rc=0
|
||||||
|
for name in "${NAMES[@]}"; do
|
||||||
|
if flag_is_set "$name"; then
|
||||||
|
ok "$(flag_status "$name")"
|
||||||
|
else
|
||||||
|
warn "$(flag_status "$name")"
|
||||||
|
rc=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
exit "$rc"
|
||||||
Executable
+21
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
||||||
|
source "$(dirname "$0")/../lib/flags.sh" 2>/dev/null || source "$(dirname "$0")/flags.sh"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: flag-set <name> [value]
|
||||||
|
|
||||||
|
Set a feature flag (green), optionally storing a value.
|
||||||
|
Writes to $FLAGS_DIR (requires sudo).
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
-h|--help|"") usage ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
flag_set "$1" "${2:-}"
|
||||||
|
ok "flag set: $1"
|
||||||
+27
-1
@@ -12,9 +12,11 @@ if [ "$NO_COLOR" -eq 1 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
source "$(dirname "$0")/lib/common.sh"
|
source "$(dirname "$0")/lib/common.sh"
|
||||||
|
source "$(dirname "$0")/lib/flags.sh"
|
||||||
|
|
||||||
DRY_RUN=0
|
DRY_RUN=0
|
||||||
RUN_APPS=0
|
RUN_APPS=0
|
||||||
|
RUN_FEATURES=0
|
||||||
SKIP_PHASES=""
|
SKIP_PHASES=""
|
||||||
STEPS_SPEC=""
|
STEPS_SPEC=""
|
||||||
|
|
||||||
@@ -27,6 +29,7 @@ Bootstrap a fresh Debian/Ubuntu install.
|
|||||||
Options:
|
Options:
|
||||||
--apps Run interactive app picker after core install
|
--apps Run interactive app picker after core install
|
||||||
--full Core install + all optional apps (non-interactive)
|
--full Core install + all optional apps (non-interactive)
|
||||||
|
--feature Install features/ scripts (prompts before overwriting)
|
||||||
--dry-run Show what would be done without executing
|
--dry-run Show what would be done without executing
|
||||||
--skip <phase> Skip a phase (repeatable):
|
--skip <phase> Skip a phase (repeatable):
|
||||||
preinstall, scripts, postinstall, scalepoint, apps
|
preinstall, scripts, postinstall, scalepoint, apps
|
||||||
@@ -47,6 +50,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
case "$1" in
|
case "$1" in
|
||||||
--apps) RUN_APPS=1; shift ;;
|
--apps) RUN_APPS=1; shift ;;
|
||||||
--full) RUN_APPS=2; shift ;;
|
--full) RUN_APPS=2; shift ;;
|
||||||
|
--feature) RUN_FEATURES=1; shift ;;
|
||||||
--dry-run) DRY_RUN=1; shift ;;
|
--dry-run) DRY_RUN=1; shift ;;
|
||||||
--skip)
|
--skip)
|
||||||
[ -z "${2:-}" ] && err "Missing value for --skip"
|
[ -z "${2:-}" ] && err "Missing value for --skip"
|
||||||
@@ -108,7 +112,29 @@ if should_run 2 scripts; then
|
|||||||
count=$((count + 1))
|
count=$((count + 1))
|
||||||
done
|
done
|
||||||
run sudo install -m 644 lib/common.sh /usr/local/bin/common.sh
|
run sudo install -m 644 lib/common.sh /usr/local/bin/common.sh
|
||||||
ok "$count scripts + lib -> /usr/local/bin"
|
run sudo install -m 644 lib/flags.sh /usr/local/bin/flags.sh
|
||||||
|
|
||||||
|
# ── Optional features ──────────────────────────────────────
|
||||||
|
if [ "$RUN_FEATURES" -eq 1 ]; then
|
||||||
|
for f in features/*; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
name=$(basename "$f")
|
||||||
|
flag_name="${name%.sh}"
|
||||||
|
dest="/usr/local/bin/$name"
|
||||||
|
if [ -e "$dest" ]; then
|
||||||
|
if confirm "Overwrite existing $dest?" n; then
|
||||||
|
run sudo install -m 755 "$f" "$dest"
|
||||||
|
else
|
||||||
|
log "Keeping existing $dest"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
run sudo install -m 755 "$f" "$dest"
|
||||||
|
fi
|
||||||
|
flag_set "$flag_name"
|
||||||
|
done
|
||||||
|
ok "features installed"
|
||||||
|
fi
|
||||||
|
ok "$count scripts + libs -> /usr/local/bin"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Phase 3: postinstall ───────────────────────────────────────
|
# ── Phase 3: postinstall ───────────────────────────────────────
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# ── Feature flag store ─────────────────────────────────────────
|
||||||
|
# System-wide flags: one file per flag in $FLAGS_DIR.
|
||||||
|
# presence = flag set, file content = optional value.
|
||||||
|
# Sourced by scripts; reads are plain file ops, writes use sudo.
|
||||||
|
# Override FLAGS_DIR via environment for testing.
|
||||||
|
FLAGS_DIR="${FLAGS_DIR:-/usr/local/share/linux_post_install/flags}"
|
||||||
|
|
||||||
|
# flag_set <name> [value] — mark a flag as set (green), optionally with a value
|
||||||
|
flag_set() {
|
||||||
|
local name="$1" value="${2:-}" tmp
|
||||||
|
run sudo install -d -m 755 "$FLAGS_DIR"
|
||||||
|
if [ -n "$value" ]; then
|
||||||
|
tmp=$(mktemp)
|
||||||
|
printf '%s' "$value" > "$tmp"
|
||||||
|
run sudo install -m 644 "$tmp" "$FLAGS_DIR/$name"
|
||||||
|
rm -f "$tmp"
|
||||||
|
else
|
||||||
|
run sudo touch "$FLAGS_DIR/$name"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# flag_clear <name> — remove a flag
|
||||||
|
flag_clear() {
|
||||||
|
run sudo rm -f "$FLAGS_DIR/$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# flag_is_set <name> — 0 if flag is set, 1 otherwise
|
||||||
|
flag_is_set() {
|
||||||
|
[ -f "$FLAGS_DIR/$1" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# flag_value <name> — print the stored value (empty when unset or valueless)
|
||||||
|
flag_value() {
|
||||||
|
local f="$FLAGS_DIR/$1"
|
||||||
|
[ -f "$f" ] && cat "$f"
|
||||||
|
}
|
||||||
|
|
||||||
|
# flag_list — print the names of all set flags, one per line
|
||||||
|
flag_list() {
|
||||||
|
[ -d "$FLAGS_DIR" ] || return 0
|
||||||
|
for f in "$FLAGS_DIR"/*; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
printf '%s\n' "$(basename "$f")"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# flag_status <name> — print a human-readable status line
|
||||||
|
flag_status() {
|
||||||
|
local name="$1" v
|
||||||
|
if flag_is_set "$name"; then
|
||||||
|
v=$(flag_value "$name")
|
||||||
|
if [ -n "$v" ]; then
|
||||||
|
echo "set: $name=$v"
|
||||||
|
else
|
||||||
|
echo "set: $name"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "unset: $name"
|
||||||
|
fi
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source "$(dirname "$0")/lib/common.sh"
|
source "$(dirname "$0")/lib/common.sh"
|
||||||
|
source "$(dirname "$0")/lib/flags.sh"
|
||||||
|
|
||||||
log "Running post-install..."
|
log "Running post-install..."
|
||||||
|
|
||||||
@@ -79,6 +80,12 @@ if [ -d systemd ] && [ -n "$(ls -A systemd/*.service 2>/dev/null)" ]; then
|
|||||||
|
|
||||||
for svc in systemd/*.service; do
|
for svc in systemd/*.service; do
|
||||||
svc_name=$(basename "$svc")
|
svc_name=$(basename "$svc")
|
||||||
|
# autostart.service runs features/autostart.sh — enable only when
|
||||||
|
# the autostart feature flag is green (set by ./install.sh --feature)
|
||||||
|
if [ "$svc_name" = "autostart.service" ] && ! flag_is_set autostart; then
|
||||||
|
warn "autostart feature not installed — skipping autostart.service (run ./install.sh --feature)"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
run sudo systemctl enable --now "$svc_name" 2>/dev/null || \
|
run sudo systemctl enable --now "$svc_name" 2>/dev/null || \
|
||||||
run sudo systemctl enable "$svc_name"
|
run sudo systemctl enable "$svc_name"
|
||||||
done
|
done
|
||||||
|
|||||||
Reference in New Issue
Block a user