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
|
||||
│
|
||||
├── 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/
|
||||
│ ├── 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-ssh-load-keys # Load SSH keys into ssh-agent
|
||||
│ ├── 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)
|
||||
│ ├── mp3, mp4, vbox # Legacy convenience wrappers → pos
|
||||
│ └── 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)
|
||||
│ ├── install.sh # Interactive picker / orchestrator
|
||||
│ ├── browsers/
|
||||
@@ -99,20 +105,23 @@ Linux_post_install/
|
||||
## 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)
|
||||
│ └─ apt update + installs 25+ packages + yt-dlp + fail2ban
|
||||
│
|
||||
├─ Phase 2: install.sh (requires root)
|
||||
│ └─ 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)
|
||||
│ └─ Configures fail2ban (SSH jail: 5 retries, 1h ban)
|
||||
│ └─ PATH export in ~/.bashrc
|
||||
│ └─ Bash completion for pos CLI
|
||||
│ └─ Copies systemd/*.service → /etc/systemd/system/, enables them
|
||||
│ (autostart.service only when the `autostart` flag is set)
|
||||
│
|
||||
├─ Phase 4: ScaleTail clone
|
||||
│ └─ 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 |
|
||||
| `--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 |
|
||||
| `--skip <phase>` | Skip a phase (repeatable): `preinstall`, `scripts`, `postinstall`, `scalepoint`, `apps` |
|
||||
| `--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
|
||||
- `~/.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
|
||||
@@ -396,10 +415,15 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
||||
|
||||
| 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 |
|
||||
| `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/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-docker-compose` | 363 | Largest script — full compose management |
|
||||
| `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 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` |
|
||||
| Change PATH or bash config | Edit `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>`
|
||||
- Legacy wrappers: `bin/wr-*`
|
||||
- App installers: `apps/<category>/<name>.sh`
|
||||
- Features: `features/<name>.sh`
|
||||
- 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
|
||||
|
||||
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 --apps # core + interactive app picker
|
||||
./install.sh --full # core + all apps (non-interactive)
|
||||
./install.sh --feature # core + install features/ scripts (prompts on overwrite)
|
||||
```
|
||||
|
||||
**Flags:**
|
||||
@@ -35,6 +36,7 @@ cd Linux_post_install
|
||||
|------|-------------|
|
||||
| `--apps` | Run interactive app picker after core install |
|
||||
| `--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 |
|
||||
| `--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` |
|
||||
@@ -51,6 +53,33 @@ cd Linux_post_install
|
||||
| 3 | `postinstall.sh` | Configures fail2ban, SSH agent, PATH, bash completion, systemd services |
|
||||
| 4 | ScaleTail clone | Downloads 119+ Docker Compose templates with Tailscale sidecar |
|
||||
| 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
|
||||
|
||||
source "$(dirname "$0")/lib/common.sh"
|
||||
source "$(dirname "$0")/lib/flags.sh"
|
||||
|
||||
DRY_RUN=0
|
||||
RUN_APPS=0
|
||||
RUN_FEATURES=0
|
||||
SKIP_PHASES=""
|
||||
STEPS_SPEC=""
|
||||
|
||||
@@ -27,6 +29,7 @@ Bootstrap a fresh Debian/Ubuntu install.
|
||||
Options:
|
||||
--apps Run interactive app picker after core install
|
||||
--full Core install + all optional apps (non-interactive)
|
||||
--feature Install features/ scripts (prompts before overwriting)
|
||||
--dry-run Show what would be done without executing
|
||||
--skip <phase> Skip a phase (repeatable):
|
||||
preinstall, scripts, postinstall, scalepoint, apps
|
||||
@@ -47,6 +50,7 @@ while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--apps) RUN_APPS=1; shift ;;
|
||||
--full) RUN_APPS=2; shift ;;
|
||||
--feature) RUN_FEATURES=1; shift ;;
|
||||
--dry-run) DRY_RUN=1; shift ;;
|
||||
--skip)
|
||||
[ -z "${2:-}" ] && err "Missing value for --skip"
|
||||
@@ -108,7 +112,29 @@ if should_run 2 scripts; then
|
||||
count=$((count + 1))
|
||||
done
|
||||
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
|
||||
|
||||
# ── 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
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/lib/common.sh"
|
||||
source "$(dirname "$0")/lib/flags.sh"
|
||||
|
||||
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
|
||||
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 "$svc_name"
|
||||
done
|
||||
|
||||
Reference in New Issue
Block a user