Compare commits
17 Commits
GUI
...
entertaiment
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e1c06a63e | |||
| 5244c21ba3 | |||
| e62626b55c | |||
| 494eae2bdf | |||
| 6d3482939e | |||
| fe751bbc0c | |||
| d591e71363 | |||
| 93f3044c2a | |||
| 49fb248454 | |||
| 0c9d09c331 | |||
| 070524bafb | |||
| e7c8c176ed | |||
| db7bf4de86 | |||
| 60718b360e | |||
| 54592a5839 | |||
| 2a2b4d81d1 | |||
| cf7ba2d58a |
@@ -19,3 +19,5 @@ Thumbs.db
|
||||
# Docker Compose — user's active stacks and secrets
|
||||
compose/custom/
|
||||
compose/config.env
|
||||
|
||||
session
|
||||
@@ -0,0 +1,17 @@
|
||||
# Linux_post_install — Agent Instructions
|
||||
|
||||
Personal bootstrap & homelab toolkit for Debian/Ubuntu (Bash). `install.sh` bootstraps a machine; `bin/pos` is the unified CLI.
|
||||
|
||||
## External File Loading
|
||||
|
||||
CRITICAL: real guidance lives in DOC/. When you encounter a reference below, use your Read tool to load it on a need-to-know basis — do NOT preemptively load all of them. Once loaded, treat the content as mandatory instructions.
|
||||
|
||||
- @DOC/AGENT_Context_Project.md — project overview, directory structure, `pos` dispatch table, "How to modify" table. Read FIRST for any non-trivial task.
|
||||
- @DOC/DEV.md — conventions, verification, and the "Adding a new Feature/App/Tool" checklists. Read before creating or changing code/docs.
|
||||
- @DOC/POS.md — `pos` CLI reference (dispatcher + every command). Read when working on `bin/pos*` scripts or their docs.
|
||||
- @DOC/README.md — index of all docs. Read to find the right doc.
|
||||
|
||||
## Quick facts
|
||||
|
||||
- Each tool is `bin/pos-<category>-<command>`; `bin/pos` dispatches via smart arg matching; bash completion derives from filenames.
|
||||
- Verify edits with `bash -n` on touched scripts and smoke-test dispatch; keep line counts in `DOC/AGENT_Context_Project.md`'s file table in sync.
|
||||
@@ -29,26 +29,48 @@ 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
|
||||
│ ├── pos-network-ip # Show interfaces, routes, public IP
|
||||
│ ├── pos-network-checkport # TCP port checker
|
||||
│ ├── pos-network-scan # Parallel ping sweep of CIDR subnet
|
||||
│ ├── pos-docker-ps # Enhanced docker ps (health, IPs, ports, uptime)
|
||||
│ ├── pos-docker-health # Quick one-glance health dashboard
|
||||
│ ├── pos-docker-compose # Docker Compose service manager (largest script, 317 lines)
|
||||
│ ├── pos-media-mp3 # Audio downloader (yt-dlp → MP3)
|
||||
│ ├── pos-media-mp4 # Video downloader (yt-dlp → MP4, interactive format select)
|
||||
│ ├── 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)
|
||||
<!-- GEN:START tree -->
|
||||
│ ├── pos-docker-compose # Docker Compose service manager (ls/up/down/restart/logs/update/config)
|
||||
│ ├── pos-docker-health # One-glance container health dashboard (exits 1 if unhealthy)
|
||||
│ ├── pos-docker-ps # Enhanced container overview (health, IPs, ports, uptime)
|
||||
│ ├── pos-docker-vbox # Disposable Docker-based VMs (create/enter/start/stop/rm/ls)
|
||||
│ ├── pos-media-mp3 # Download audio as MP3 (yt-dlp)
|
||||
│ ├── pos-media-mp4 # Download video as MP4 (interactive format select)
|
||||
│ ├── pos-network-checkport # Check TCP port connectivity
|
||||
│ ├── pos-network-hotspot # Wi-Fi hotspot via create_ap + wihotspot-gui
|
||||
│ ├── pos-network-ip # Show interfaces, routes, public IP + location
|
||||
│ ├── pos-network-scan # Parallel ping sweep of CIDR
|
||||
│ ├── pos-ssh-load-keys # Load all SSH keys into the agent
|
||||
│ ├── pos-system-backup # Encrypted (AES-256) folder snapshots (tar + gpg)
|
||||
│ ├── pos-system-firewall # Interactive UFW management
|
||||
│ ├── pos-usb-server # USB Redirector server control (--ls, --share; prompts when args omitted)
|
||||
<!-- GEN:END tree -->
|
||||
│ ├── 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)
|
||||
│
|
||||
├── 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
|
||||
│ └── wihotspot-gui # GTK3 hotspot GUI (x86-64 ELF)
|
||||
│ # future: arm64_bin/ picked up automatically on aarch64
|
||||
│
|
||||
├── apps/ # Optional desktop app installers (by category)
|
||||
│ ├── install.sh # Interactive picker / orchestrator
|
||||
│ ├── browsers/
|
||||
@@ -78,6 +100,9 @@ Linux_post_install/
|
||||
├── completions/
|
||||
│ └── pos.bash # Bash tab-completion for the pos CLI
|
||||
│
|
||||
├── config/
|
||||
│ └── authorized_keys # SSH public keys (gitignored)
|
||||
│
|
||||
├── compose/
|
||||
│ └── scale-tail/ # Git submodule → ScaleTail templates (119+ services)
|
||||
│
|
||||
@@ -85,8 +110,18 @@ Linux_post_install/
|
||||
│ ├── autostart.service # Runs autostart.sh on boot
|
||||
│ └── ssh-agent.service # System-wide SSH agent socket
|
||||
│
|
||||
├── README.md # User-facing documentation
|
||||
├── DEV.md # Developer guide
|
||||
├── README.md # User-facing intro + quick start (links into DOC/)
|
||||
│
|
||||
├── DOC/ # All documentation
|
||||
│ ├── README.md # Docs index
|
||||
│ ├── SCRIPTS.md # Installer scripts, libs, features — reference
|
||||
│ ├── POS.md # pos CLI reference
|
||||
│ ├── APPS.md # Optional apps reference
|
||||
│ ├── SYSTEMD.md # Systemd units + completion
|
||||
│ ├── DEV.md # Developer guide
|
||||
│ ├── AGENT_Context_Project.md # This file — AI agent context
|
||||
│ └── algorithm.md # Algorithm diagrams
|
||||
│
|
||||
├── .gitignore # Excludes secrets, Python artifacts, OS files
|
||||
└── .gitmodules # Submodule: compose/scale-tail → ScaleTail
|
||||
```
|
||||
@@ -96,20 +131,24 @@ 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)
|
||||
│ └─ Copies x64_bin/* → /usr/local/bin/ on x86_64 (arm64_bin/ on aarch64)
|
||||
│ └─ [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
|
||||
@@ -126,6 +165,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` |
|
||||
@@ -158,28 +198,30 @@ All non-interactive `pos` commands log output to `~/.local/share/linux_post_inst
|
||||
|
||||
| Category | Command | Script | Description |
|
||||
|----------|---------|--------|-------------|
|
||||
| network | ip | `pos-network-ip` | Show interfaces, routes, public IP |
|
||||
<!-- GEN:START dispatch -->
|
||||
| docker | compose | `pos-docker-compose` | Docker Compose service manager (ls/up/down/restart/logs/update/config) |
|
||||
| docker | health | `pos-docker-health` | One-glance container health dashboard (exits 1 if unhealthy) |
|
||||
| docker | ps | `pos-docker-ps` | Enhanced container overview (health, IPs, ports, uptime) |
|
||||
| docker | vbox | `pos-docker-vbox` | Disposable Docker-based VMs (create/enter/start/stop/rm/ls) |
|
||||
| media | mp3 | `pos-media-mp3` | Download audio as MP3 (yt-dlp) |
|
||||
| media | mp4 | `pos-media-mp4` | Download video as MP4 (interactive format select) |
|
||||
| network | checkport | `pos-network-checkport` | Check TCP port connectivity |
|
||||
| network | hotspot | `pos-network-hotspot` | Wi-Fi hotspot via create_ap + wihotspot-gui |
|
||||
| network | ip | `pos-network-ip` | Show interfaces, routes, public IP + location |
|
||||
| network | scan | `pos-network-scan` | Parallel ping sweep of CIDR |
|
||||
| docker | ps | `pos-docker-ps` | Enhanced container overview |
|
||||
| docker | health | `pos-docker-health` | Quick health dashboard (exits 1 if unhealthy) |
|
||||
| docker | compose | `pos-docker-compose` | Service manager (ls/up/down/restart/logs/update/config) |
|
||||
| media | mp3 | `pos-media-mp3` | Download audio as MP3 |
|
||||
| media | mp4 | `pos-media-mp4` | Download video with format select |
|
||||
| ssh | load-keys | `pos-ssh-load-keys` | Load all SSH keys into the agent |
|
||||
| system | backup | `pos-system-backup` | Encrypted (AES-256) folder snapshots (tar + gpg) |
|
||||
| system | firewall | `pos-system-firewall` | Interactive UFW management |
|
||||
| ssh | load-keys | `pos-ssh-load-keys` | Load SSH keys into agent |
|
||||
| vbox | create | `pos-vbox create` | Create disposable VM (asks "Enter now?") |
|
||||
| vbox | enter | `pos-vbox enter` | Start and exec into container |
|
||||
| vbox | ls | `pos-vbox ls` | List vbox-managed containers only (label-filtered) |
|
||||
| vbox | start/stop/rm | `pos-vbox start/stop/rm` | Lifecycle management |
|
||||
| usb | server | `pos-usb-server` | USB Redirector server control (--ls, --share; prompts when args omitted) |
|
||||
<!-- GEN:END dispatch -->
|
||||
|
||||
### Legacy Wrappers
|
||||
|
||||
These forward to `pos` transparently: `wr-ip`, `wr-checkport`, `wr-scan-ping`, `wr-docker`, `wr-compose`, `wr-ufw`, `mp3`, `mp4`, `vbox`, `ssh-load-all`.
|
||||
|
||||
### pos vbox Details
|
||||
### pos docker vbox Details
|
||||
|
||||
`pos-vbox` manages disposable Docker containers as lightweight VMs:
|
||||
`pos-docker-vbox` manages disposable Docker containers as lightweight VMs:
|
||||
|
||||
- **Container labeling:** All created containers get `linux_post_install.vbox=true` label
|
||||
- **`ls` filtering:** `docker ps --filter label=linux_post_install.vbox=true` — only shows vbox-managed containers
|
||||
@@ -213,7 +255,10 @@ Sourced by most scripts. Provides:
|
||||
source "$(dirname "$0")/../lib/common.sh"
|
||||
```
|
||||
|
||||
**Scripts that do NOT source common.sh** (self-contained): `bin/pos`, `pos-network-ip`, `pos-network-checkport`, `pos-network-scan`, `pos-media-mp3`, `pos-media-mp4`, `pos-ssh-load-keys`, `pos-system-firewall`.
|
||||
**Scripts that do NOT source common.sh** (self-contained):
|
||||
<!-- GEN:START selfcontained -->
|
||||
`bin/pos`, `pos-network-ip`, `pos-network-checkport`, `pos-network-scan`, `pos-network-hotspot`, `pos-media-mp3`, `pos-media-mp4`, `pos-ssh-load-keys`, `pos-system-firewall`, `pos-communication-telegram`.
|
||||
<!-- GEN:END selfcontained -->
|
||||
|
||||
---
|
||||
|
||||
@@ -266,8 +311,10 @@ ScaleTail provides 119+ Docker Compose templates with a Tailscale sidecar patter
|
||||
|
||||
- `apps/install.sh` auto-discovers all `apps/<category>/*.sh` files (excluding itself)
|
||||
- Three modes: interactive (default), `--all`, or specific app names as arguments
|
||||
- `--uninstall` switches to uninstall mode (same selection, invokes app scripts with `uninstall` argument)
|
||||
- Interactive TUI groups apps by category with section headers
|
||||
- Each app script is standalone, idempotent, sources `lib/common.sh`
|
||||
- Every app script defines `install_<name>()` **and** `uninstall_<name>()`, dispatched via `case "${1:-}" in uninstall) ...`
|
||||
|
||||
### Installation Methods
|
||||
|
||||
@@ -283,7 +330,7 @@ ScaleTail provides 119+ Docker Compose templates with a Tailscale sidecar patter
|
||||
|
||||
### Adding a New App
|
||||
|
||||
1. Create `apps/<name>.sh` following the template in DEV.md
|
||||
1. Create `apps/<name>.sh` following the template in DOC/DEV.md
|
||||
2. It auto-appears in the interactive picker — no registration needed
|
||||
|
||||
---
|
||||
@@ -311,6 +358,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
|
||||
@@ -348,18 +404,28 @@ All `.service` files in `systemd/` are automatically copied to `/etc/systemd/sys
|
||||
|
||||
## 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 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 `README.md`
|
||||
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`); it auto-appears in `pos <category> --help` (filename-derived, no registration)
|
||||
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> && bin/pos <category> --help`
|
||||
|
||||
### Testing
|
||||
|
||||
@@ -391,18 +457,32 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
||||
|
||||
| File | Lines | Purpose |
|
||||
|------|-------|---------|
|
||||
| `install.sh` | ~120 | Main orchestrator — 4 phases with CLI flags |
|
||||
| `preinstall.sh` | ~52 | System packages + yt-dlp + fail2ban |
|
||||
| `postinstall.sh` | ~65 | fail2ban config, PATH, bash completion, systemd |
|
||||
| `install.sh` | 192 | Main orchestrator — 4 phases with CLI flags, `--feature`, prebuilt arch bins |
|
||||
| `preinstall.sh` | 54 | System packages + hotspot deps + yt-dlp + fail2ban |
|
||||
| `postinstall.sh` | 97 | fail2ban config, PATH, bash completion, systemd (flag-gated) |
|
||||
| `lib/common.sh` | 121 | Shared library |
|
||||
| `bin/pos` | ~130 | CLI dispatcher with smart arg matching + logging |
|
||||
| `bin/pos-docker-compose` | 317 | Largest script — full compose management |
|
||||
| `bin/pos-system-firewall` | 284 | Interactive UFW manager |
|
||||
| `bin/pos-docker-ps` | 127 | Enhanced container overview |
|
||||
| `bin/pos-docker-health` | ~90 | Quick health dashboard |
|
||||
| `bin/pos-vbox` | ~160 | Docker-based disposable VMs (label-filtered, auto-enter prompt) |
|
||||
| `completions/pos.bash` | 118 | Dynamic bash completion |
|
||||
| `apps/install.sh` | 99 | App picker/orchestrator |
|
||||
| `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-communication-telegram` | 140 | Send Telegram messages via Bot API (--send, test, config set) |
|
||||
| `bin/pos-docker-compose` | 364 | Docker Compose service manager (ls/up/down/restart/logs/update/config) |
|
||||
| `bin/pos-docker-health` | 110 | One-glance container health dashboard (exits 1 if unhealthy) |
|
||||
| `bin/pos-docker-ps` | 128 | Enhanced container overview (health, IPs, ports, uptime) |
|
||||
| `bin/pos-docker-vbox` | 157 | Disposable Docker-based VMs (create/enter/start/stop/rm/ls) |
|
||||
| `bin/pos-media-mp3` | 31 | Download audio as MP3 (yt-dlp) |
|
||||
| `bin/pos-media-mp4` | 34 | Download video as MP4 (interactive format select) |
|
||||
| `bin/pos-network-checkport` | 45 | Check TCP port connectivity |
|
||||
| `bin/pos-network-hotspot` | 93 | Wi-Fi hotspot via create_ap + wihotspot-gui |
|
||||
| `bin/pos-network-ip` | 69 | Show interfaces, routes, public IP + location |
|
||||
| `bin/pos-network-scan` | 271 | Parallel ping sweep of CIDR |
|
||||
| `bin/pos-ssh-load-keys` | 31 | Load all SSH keys into the agent |
|
||||
| `bin/pos-system-backup` | 117 | Encrypted (AES-256) folder snapshots (tar + gpg) |
|
||||
| `bin/pos-system-firewall` | 285 | Interactive UFW management |
|
||||
| `bin/pos-usb-server` | 218 | USB Redirector server control (--ls, --share; prompts when args omitted) |
|
||||
| `completions/pos.bash` | 146 | Dynamic bash completion |
|
||||
| `apps/install.sh` | 171 | App install/uninstall picker/orchestrator |
|
||||
|
||||
---
|
||||
|
||||
@@ -410,17 +490,22 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
||||
|
||||
| Task | Where to Edit |
|
||||
|------|---------------|
|
||||
| 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 apt deps in `preinstall.sh` (non-apt/manual installers: add a `command -v` guard in the tool instead) |
|
||||
| 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) |
|
||||
| Add a precompiled binary | Drop it in `x64_bin/` (or `arm64_bin/` later) — auto-copied by Phase 2 |
|
||||
| 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` |
|
||||
| Add bash completion | Edit `completions/pos.bash` |
|
||||
| Modify Docker Compose logic | Edit `bin/pos-docker-compose` |
|
||||
| Modify Docker health check | Edit `bin/pos-docker-health` |
|
||||
| Modify vbox (Docker VM) logic | Edit `bin/pos-docker-vbox` |
|
||||
| Modify USB forwarding logic | Edit `bin/pos-usb-server` |
|
||||
| Modify UFW/firewall logic | Edit `bin/pos-system-firewall` |
|
||||
| Modify pos logging | Edit log setup in `bin/pos` |
|
||||
| Modify install phases/flags | Edit arg parsing in `install.sh` |
|
||||
| Update documentation | Edit `README.md` and/or `DEV.md` |
|
||||
| Update documentation | Edit the relevant doc under `DOC/` (index: `DOC/README.md`) |
|
||||
| Add a secret config file | Add to `config/`, update `.gitignore`, add copy logic in `postinstall.sh` |
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
# Optional Apps Reference
|
||||
|
||||
`apps/` holds 15 optional desktop application installers, one script per app in `apps/<category>/<name>.sh`. They are **not** installed by the core bootstrap — run the picker explicitly.
|
||||
|
||||
- [The picker — `apps/install.sh`](#the-picker--appsinstallsh)
|
||||
- [How an app installer works](#how-an-app-installer-works)
|
||||
- [App catalog](#app-catalog)
|
||||
|
||||
---
|
||||
|
||||
## The picker — `apps/install.sh`
|
||||
|
||||
**Purpose:** discover every app under `apps/` and install/uninstall the selection. Apps are auto-discovered from the directory structure — no registration step.
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
bash apps/install.sh # interactive install selection
|
||||
bash apps/install.sh --all # install everything
|
||||
bash apps/install.sh brave vscode # install specific apps
|
||||
bash apps/install.sh --uninstall # interactive uninstall selection
|
||||
bash apps/install.sh --uninstall --all # uninstall everything
|
||||
bash apps/install.sh --uninstall brave # uninstall a specific app
|
||||
```
|
||||
|
||||
(Also reachable via `./install.sh --apps` / `--full`.)
|
||||
|
||||
### How it works
|
||||
|
||||
1. Parses `--all`, `--uninstall`, and positional app names.
|
||||
2. Scans `apps/<category>/*.sh` to build the catalog (skips non-app dirs).
|
||||
3. Picks apps three ways: named on the command line (unknown names are skipped with a warning), `--all`, or an interactive y/n picker grouped by category.
|
||||
4. Runs `bash apps/<category>/<name>.sh [uninstall]` for each selected app, with a progress header and an overall elapsed-time banner.
|
||||
|
||||
### Configuration
|
||||
|
||||
- Categories: `browsers`, `development`, `media`, `networking`, `remote-access`, `system`, `utilities`.
|
||||
- Adding an app = dropping `apps/<category>/<name>.sh` into the folder. See [DEV.md](DEV.md) for the required installer conventions.
|
||||
|
||||
---
|
||||
|
||||
## How an app installer works
|
||||
|
||||
Every app script follows the same shape:
|
||||
|
||||
```bash
|
||||
install_<name>() { … } # idempotent: checks command -v (or flatpak list) first
|
||||
uninstall_<name>() { … } # also idempotent; purges and removes any added repos/keys
|
||||
case "${1:-}" in
|
||||
uninstall) uninstall_<name> ;;
|
||||
*) install_<name> ;;
|
||||
esac
|
||||
```
|
||||
|
||||
Installation methods used across the catalog:
|
||||
|
||||
| Method | Example |
|
||||
|--------|---------|
|
||||
| `apt` package | `sudo apt install -y obs-studio` |
|
||||
| Official installer script | `curl -fsSL https://tailscale.com/install.sh \| sh` |
|
||||
| Custom apt repo (added at install, removed at uninstall) | Brave, VS Code |
|
||||
| `.deb` file | `curl` → `dpkg -i` → `apt-get install -f -y` |
|
||||
| GitHub release archive | scrcpy (tar.gz → `/usr/local/lib/`) |
|
||||
| AppImage | AFFiNE (`/opt/affine` + desktop entry) |
|
||||
| Flatpak | LocalSend (`flatpak install -y flathub …`) |
|
||||
|
||||
---
|
||||
|
||||
## App catalog
|
||||
|
||||
| App | Category | What it is | Install method |
|
||||
|-----|----------|------------|----------------|
|
||||
| Brave | browsers | Brave browser | apt repo + `apt install brave-browser` |
|
||||
| opencode | development | AI coding agent | official script → `~/.opencode/bin` |
|
||||
| VS Code | development | Code editor | Microsoft apt repo + `apt install code` |
|
||||
| OBS Studio | media | Screen recording / streaming | `apt install obs-studio` |
|
||||
| scrcpy | media | Android mirror/control | GitHub release (latest) → `/usr/local/lib/scrcpy-<v>` + desktop entry |
|
||||
| VLC | media | Media player | `apt install vlc` |
|
||||
| NetBird | networking | Mesh VPN | official script; join with `sudo netbird up --setup-key <key>` |
|
||||
| Tailscale | networking | WireGuard-based VPN | official script; start with `sudo tailscale up` |
|
||||
| ZeroTier | networking | Virtual LAN | official script; join with `sudo zerotier-cli join <id>` |
|
||||
| Termius | remote-access | SSH client | `.deb` from termius.com |
|
||||
| VNC Viewer | remote-access | VNC client (TigerVNC) | `apt install tigervnc-viewer` |
|
||||
| Docker Engine | system | Container runtime | get.docker.com; adds user to `docker` group (re-login needed) |
|
||||
| QEMU + KVM | system | Virtualization + virt-manager | `apt install` (qemu-system, libvirt, bridge-utils, virt-manager); adds user to `libvirt`/`kvm` groups |
|
||||
| AFFiNE | utilities | Knowledge base (AppImage) | GitHub release → `/opt/affine` + desktop entry |
|
||||
| btop | utilities | Resource monitor | `apt install btop` |
|
||||
| LocalSend | utilities | Local file sharing | flatpak (installs flatpak + flathub if missing) |
|
||||
+99
-13
@@ -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`). `pos <category>` or `pos <category> --help` shows a category's subcommands (derived from the `pos-<category>-*` filenames in `bin/` — no script execution, so it works even for root-only/interactive tools like `system-firewall`).
|
||||
|
||||
**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`, `usb-server`) 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`. Trade-off: it's all-or-nothing **per script** — adding a flag-style tool with *any* prompting subcommand (e.g. `usb-server --share`) means *every* subcommand of that script skips output logging (e.g. `usb server --ls` loses the `tee` log too).
|
||||
|
||||
### 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,17 @@ 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.
|
||||
- `pos <category> --help` (and bare `pos <category>`) is derived from the `pos-<category>-*` filenames too — a new tool appears in its category's help automatically, with no registration (see [The `pos` CLI](#the-pos-cli)).
|
||||
- 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 +135,42 @@ PACKAGES=(
|
||||
)
|
||||
```
|
||||
|
||||
### 3. Add config files (if needed)
|
||||
**Not in apt?** If the dependency ships as a manual installer (no package — e.g. `usbsrv`, the USB Redirector server), do **not** put it in `PACKAGES` (that would break `preinstall.sh`). Instead, add a `command -v <binary> || err "… install from <URL>"` guard in the tool itself and note the manual install in `usage()`/`DOC/POS.md`.
|
||||
|
||||
Place defaults in `config/` and add copy logic to `postinstall.sh`. If they contain secrets, add to `.gitignore` and document in README.
|
||||
### 4. Config files (if needed)
|
||||
|
||||
### 4. Add SSH keys (if needed)
|
||||
Two kinds of config, don't mix them up:
|
||||
|
||||
- **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 README
|
||||
### 6. Update the docs
|
||||
|
||||
Add a section under the relevant category in README.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
|
||||
bin/pos <category> --help # confirm category listing includes the new tool (first tool in a new category)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Adding an Optional App
|
||||
|
||||
Start from the template: `cp templates/app.sh apps/<category>/<name>.sh`.
|
||||
|
||||
### 1. Create the installer
|
||||
|
||||
```bash
|
||||
@@ -153,20 +183,34 @@ install_myapp() {
|
||||
spawn "Installing myapp" sudo apt install -y myapp
|
||||
}
|
||||
|
||||
install_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
|
||||
```
|
||||
|
||||
Place it in `apps/<category>/<name>.sh`. It auto-appears in the picker — no registration needed.
|
||||
|
||||
**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` before installing
|
||||
- APT packages → `sudo apt install -y` inside `spawn`
|
||||
- Idempotent: check `command -v` (or `flatpak list` / file existence) before installing **and** uninstalling
|
||||
- Every app **must** provide an `uninstall_<name>()` function and dispatch on `uninstall` via the `case` above — `apps/install.sh --uninstall` depends on it
|
||||
- APT packages → `sudo apt install -y` inside `spawn`, remove with `sudo apt purge -y` + `sudo apt autoremove -y`
|
||||
- Repo-based apps (apt repo added at install) → also remove the `.list` file and keyring in uninstall
|
||||
- Official scripts → `curl ... | sh` inside `spawn`
|
||||
- Flatpak → `flatpak install -y flathub <app-id>` inside `spawn`
|
||||
- Flatpak → `flatpak install -y flathub <app-id>` inside `spawn`, remove with `flatpak uninstall -y <app-id>`
|
||||
- `.deb` files → download to temp, `sudo apt install -y ./file.deb` inside `spawn`
|
||||
- File/AppImage installs → remove the installed files, symlinks, and desktop entries in uninstall
|
||||
- `usermod` for groups → print re-login reminder
|
||||
|
||||
---
|
||||
@@ -176,7 +220,7 @@ Place it in `apps/<category>/<name>.sh`. It auto-appears in the picker — no re
|
||||
1. Find the script in `bin/`
|
||||
2. Understand its contract (args, output, exit codes)
|
||||
3. Make the change — keep it idempotent
|
||||
4. Update README if behaviour changed
|
||||
4. Update `DOC/POS.md` (or the relevant doc) if behaviour changed
|
||||
5. Run `shellcheck` on the modified file
|
||||
|
||||
---
|
||||
@@ -216,8 +260,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
|
||||
|
||||
@@ -226,10 +271,51 @@ 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
|
||||
|
||||
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).
|
||||
|
||||
### 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.
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
# `pos` CLI Reference
|
||||
|
||||
`pos` is the unified command-line interface installed to `/usr/local/bin/`. Every tool is a small script in `bin/` with a `pos-<category>-<command>` name. This document explains the dispatcher and every command.
|
||||
|
||||
- [The dispatcher — `bin/pos`](#the-dispatcher--binpos)
|
||||
- [Logging behavior](#logging-behavior)
|
||||
- [Commands](#commands)
|
||||
- [network](#network)
|
||||
- [docker](#docker)
|
||||
- [media](#media)
|
||||
- [system](#system)
|
||||
- [ssh](#ssh)
|
||||
- [usb](#usb)
|
||||
- [communication](#communication)
|
||||
- [flags](#flags)
|
||||
- [Legacy wrappers](#legacy-wrappers)
|
||||
|
||||
---
|
||||
|
||||
## The dispatcher — `bin/pos`
|
||||
|
||||
**Purpose:** turn `pos <category> <command> [args]` into a call to the matching `pos-*` script.
|
||||
|
||||
### How it works
|
||||
|
||||
`pos` scans its own directory for executable `pos-*` files and tries **variable-length argument matching**, longest first. For `pos docker compose up jellyfin`:
|
||||
|
||||
```
|
||||
tries pos-docker-compose-up-jellyfin (not found)
|
||||
tries pos-docker-compose-up (not found)
|
||||
tries pos-docker-compose (found) → runs with args "up jellyfin"
|
||||
```
|
||||
|
||||
`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). `pos <category>` and `pos <category> --help` list that category's subcommands (derived from `bin/pos-<category>-*` filenames, no script execution). Running `pos` with no args prints the built-in usage text (which doubles as the category cheat-sheet).
|
||||
|
||||
---
|
||||
|
||||
## Logging behavior
|
||||
|
||||
Every non-interactive `pos` invocation logs to `~/.local/share/linux_post_install/logs/`:
|
||||
|
||||
- Per-command files: `YYYYMMDD_HHMMSS_pos_<args>.log` (full stdout + stderr).
|
||||
- `pos.log`: one line per invocation — command, log file, exit code.
|
||||
- **Interactive** commands (`pos system firewall`, `pos media mp4`, `pos system backup`) only log the invocation, not their output.
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
### network
|
||||
|
||||
| Command | File | Purpose | Configuration |
|
||||
|---------|------|---------|---------------|
|
||||
| `pos network ip` | `bin/pos-network-ip` | Show interfaces, default route, public IP + location | None. Public IP via `https://ifconfig.me`; location via `ip-api.com` (5s timeouts) |
|
||||
| `pos network checkport <ip:port>` | `bin/pos-network-checkport` | Check if a TCP port is open | None. Uses `/dev/tcp` with a 2s timeout; exit 0/1 via OPEN/CLOSED |
|
||||
| `pos network scan <cidr> [--full] [--retries N]` | `bin/pos-network-scan` | Two-phase nmap scan | See below |
|
||||
| `pos network hotspot [cmd]` | `bin/pos-network-hotspot` | Wi-Fi hotspot via `create_ap` (CLI) or `wihotspot-gui` (GUI) | Uses the precompiled binaries from `x64_bin/`; see below |
|
||||
|
||||
**`pos network scan` in detail:**
|
||||
|
||||
- Phase 1 — fast host discovery (`nmap -sn -T5`), prints the live host list.
|
||||
- Phase 2 (only with `--full`) — service/version scan (`-sV -sC`), plus OS detection and NSE scripts if run with privileges; shows ports, OS, SSH host keys, HTTP titles, NetBIOS/SMB info.
|
||||
- Accepts a bare IP (treated as `/32`) or a CIDR.
|
||||
- Auto-raises to `sudo nmap` when possible (root, passwordless sudo, or an interactive terminal with `--full`).
|
||||
- `--retries N` tunes discovery retries (default 1).
|
||||
|
||||
**`pos network hotspot` in detail:**
|
||||
|
||||
Backed by the precompiled binaries shipped in `x64_bin/` (see [SCRIPTS.md → x64_bin/](SCRIPTS.md#x64_bin--precompiled-binaries)). Needs root for the CLI commands (uses `sudo`):
|
||||
|
||||
| Command | Behavior |
|
||||
|---------|----------|
|
||||
| `pos network hotspot` | Launches the `wihotspot-gui` (GTK3 GUI) |
|
||||
| `pos network hotspot start <wifi-iface> [<internet-iface>] <ssid> [<passphrase>]` | Asks whether to run in the background; `y` starts `create_ap --daemon` (logs to `/var/log/linux_post_install_hotspot.log`), `n` runs in the foreground (blocks until Ctrl+C) |
|
||||
| `pos network hotspot start --foreground <wifi-iface> [<internet-iface>] <ssid> [<passphrase>]` | Skips the prompt, runs in the foreground |
|
||||
| `pos network hotspot stop [<id>]` | Stops the running access point via `create_ap --stop`; `<id>` is an interface name or PID, auto-detected if omitted |
|
||||
| `pos network hotspot status` | Runs `create_ap --list-running` |
|
||||
|
||||
### docker
|
||||
|
||||
| Command | File | Purpose | Configuration |
|
||||
|---------|------|---------|---------------|
|
||||
| `pos docker ps` | `bin/pos-docker-ps` | Enhanced container list: name, image, health, uptime, IPs, ports, ID, plus a healthy/unhealthy summary | None. Requires Docker + Python 3 |
|
||||
| `pos docker health` | `bin/pos-docker-health` | One-glance health dashboard; **exits 1** if any container is unhealthy | None. Checks all containers including stopped ones |
|
||||
| `pos docker compose …` | `bin/pos-docker-compose` | ScaleTail service manager | See [Docker Compose / ScaleTail](#docker-compose--scaletail) below |
|
||||
|
||||
#### Docker Compose / ScaleTail
|
||||
|
||||
**`pos docker compose ls`** — list available ScaleTail service templates.
|
||||
|
||||
**`pos docker compose installed`** — list deployed services under `$SERVICES_BASE`.
|
||||
|
||||
**`pos docker compose up <service>`** — deploy a service:
|
||||
|
||||
1. If not yet deployed, creates `$SERVICES_BASE/<service>/` with `config/` and `data/`, copies the template's `compose.yaml`.
|
||||
2. If no `.env` exists, copies the template's `.env` (or writes a default) and fills in your global config values (`TS_AUTHKEY`, `TZ`, `DNS_SERVER`).
|
||||
3. If `TS_AUTHKEY` is still empty, prompts for it.
|
||||
4. Offers to edit `.env` before starting (default **yes** on first deploy).
|
||||
5. Runs `docker compose up -d`.
|
||||
|
||||
**`pos docker compose down/restart/logs <service>`** — stop, restart, or tail logs of a deployment.
|
||||
|
||||
**`pos docker compose update`** — `git pull` the ScaleTail templates, then refresh the `compose.yaml` of every deployed service. **Per-service `.env` files are never touched.**
|
||||
|
||||
**`pos docker compose config [show]`** — show the global config file and `SERVICES_BASE`.
|
||||
|
||||
**`pos docker compose config set KEY=VALUE`** — set/update a global default in `~/.config/linux_post_install/compose.env`.
|
||||
|
||||
**`pos docker compose config edit`** — open the global config in `$EDITOR` (creates a default file first).
|
||||
|
||||
Configuration (three layers, most specific wins):
|
||||
|
||||
| Layer | File | Notes |
|
||||
|-------|------|-------|
|
||||
| Template defaults | `/usr/local/share/linux_post_install/scale-tail/services/<name>/.env` | Read-only |
|
||||
| Global config | `~/.config/linux_post_install/compose.env` | Edited via `config set` / `config edit` |
|
||||
| Per-service | `$SERVICES_BASE/<service>/.env` | Created on first `up`, **never overwritten** |
|
||||
|
||||
Global config keys:
|
||||
|
||||
| Key | Required | Default | Purpose |
|
||||
|-----|----------|---------|---------|
|
||||
| `TS_AUTHKEY` | yes | — | Tailscale auth key for the sidecar |
|
||||
| `TZ` | no | `Europe/Amsterdam` | Service timezone |
|
||||
| `DNS_SERVER` | no | `9.9.9.9` | DNS server |
|
||||
| `SERVICES_BASE` | no | `/srv` | Deployment root |
|
||||
|
||||
#### Docker vbox
|
||||
|
||||
**File:** `bin/pos-docker-vbox`
|
||||
**Purpose:** manage disposable Docker containers as lightweight "VMs". Each container gets a bind-mounted host directory so files persist after the container is removed. Containers carry the label `linux_post_install.vbox=true`.
|
||||
|
||||
| Command | Behavior |
|
||||
|---------|----------|
|
||||
| `pos docker vbox create <name> [image] [--dir <path>]` | Creates a container from `ubuntu:22.04` (or the given image), bind-mounting `~/<name>` (or `--dir`, or `.` for cwd) as the working directory; prompts to enter immediately |
|
||||
| `pos docker vbox enter <name>` | Shell into the container (auto-starts it if stopped); detects the working dir from the container mounts |
|
||||
| `pos docker vbox start/stop/rm <name>` | Start, stop, or force-remove the container |
|
||||
| `pos docker vbox ls` | List vbox containers only (label filter) |
|
||||
|
||||
The standalone `vbox` command still works and forwards to `pos docker vbox` (see [Legacy wrappers](#legacy-wrappers)).
|
||||
|
||||
### media
|
||||
|
||||
| Command | File | Purpose | Configuration |
|
||||
|---------|------|---------|---------------|
|
||||
| `pos media mp3 <url>` | `bin/pos-media-mp3` | Download audio as MP3 via yt-dlp, with thumbnail + metadata | Output to `~/Music/%(title)s.%(ext)s`, `--audio-quality 0` |
|
||||
| `pos media mp4 <url>` | `bin/pos-media-mp4` | Download video via yt-dlp with **interactive format selection** | Lists formats (`yt-dlp -F`), asks for a format ID, saves to `~/Videos/` |
|
||||
|
||||
### system
|
||||
|
||||
| Command | File | Purpose | Configuration |
|
||||
|---------|------|---------|---------------|
|
||||
| `sudo pos system firewall` | `bin/pos-system-firewall` | Interactive UFW ("UFW POWER") menu: add/delete rules, status, enable/disable/reset, default policies | Must run as root. Every command is previewed and confirmed before execution; supports `--dry-run`; keeps a history of executed commands |
|
||||
| `pos system backup <folder-path>` | `bin/pos-system-backup` | Create a gpg-encrypted (AES-256) `tar.gz` snapshot of a folder and verify it | Prompts twice for a password (never stored). Uses `sudo tar`; needs `gnupg` (in `preinstall.sh` PACKAGES). Artifact `<name>_<date>.tar.gz.gpg` in the current directory, `chmod 600` |
|
||||
| `pos system backup --service` | `bin/pos-system-backup` | Lists folders under `/srv` and `~/srv`, lets you pick one, then runs the same backup | Roots via `BACKUP_SERVICE_ROOTS` (space-separated, default `/srv $HOME/srv`) |
|
||||
|
||||
### ssh
|
||||
|
||||
| Command | File | Purpose | Configuration |
|
||||
|---------|------|---------|---------------|
|
||||
| `pos ssh load-keys` | `bin/pos-ssh-load-keys` | Load all `~/.ssh/id_*` private keys into the ssh-agent | Uses `SSH_AUTH_SOCK` (default `/run/ssh-agent/socket`, provided by `ssh-agent.service`); skips `.pub`, `known_hosts`, `authorized_keys`, `config`; validates keys before adding |
|
||||
|
||||
### usb
|
||||
|
||||
**File:** `bin/pos-usb-server`
|
||||
**Purpose:** control the USB Redirector server (`usbsrv`) — share local USB devices over the network and manage connected clients. Requires `usbsrv` (manual install from incentivespro.com — not in `PACKAGES`).
|
||||
|
||||
| Command | Behavior |
|
||||
|---------|----------|
|
||||
| `pos usb server --ls` | List host USB devices and connected clients |
|
||||
| `pos usb server --ls-shared` | List shared or in-use devices only |
|
||||
| `pos usb server --share [dev-id] [client-id]` | Share a device and connect it to a client; interactive picker when IDs are omitted (`-share` + `-connect-to CLIENT-DEV`) |
|
||||
| `pos usb server --unshare [dev-id]` | Stop sharing a device |
|
||||
| `pos usb server --auto-share on\|off` | Toggle automatic sharing of new devices |
|
||||
| `pos usb server --callback [addr:port]` | Create a callback connection to a client |
|
||||
| `pos usb server --close-callback [target\|all]` | Close a client callback |
|
||||
| `pos usb server --auto-connect on\|off [client]` | Toggle remote auto-connect for a client |
|
||||
| `pos usb server --disconnect [dev-id\|all]` | Disconnect a device from its clients |
|
||||
| `pos usb server --nickname [dev-id] [nick]` | Set a device nickname (empty nick removes it) |
|
||||
| `pos usb server --timeout [dev-id] [sec]` | Set device inactivity timeout (0 disables) |
|
||||
| `pos usb server --port [num]` | Set the TCP port (restart server to apply) |
|
||||
| `pos usb server --info` / `--version` | Show server info / version |
|
||||
|
||||
Subcommands that need input prompt interactively when args are omitted.
|
||||
|
||||
### communication
|
||||
|
||||
| Command | File | Purpose | Configuration |
|
||||
|---------|------|---------|---------------|
|
||||
| `pos communication telegram --send "text"` | `bin/pos-communication-telegram` | Send a text message to a Telegram chat via the Bot API | Token + chat ID from `~/.config/linux_post_install/telegram.env` (`TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID`, chmod 600). Precedence: `--token`/`--chat-id` flags > env > config file |
|
||||
|
||||
`pos communication telegram` in detail:
|
||||
|
||||
| Command | Behavior |
|
||||
|---------|----------|
|
||||
| `pos communication telegram --send "text"` | POSTs `sendMessage` to the Bot API (20s timeout); prints `[+] Message sent to chat <id>` or fails with a nonzero exit |
|
||||
| `pos communication telegram --send "text" --token <t> --chat-id <id>` | One-shot override of token/chat ID |
|
||||
| `pos communication telegram test` | Sends a canned test message using the current config |
|
||||
| `pos communication telegram config` | Shows current config (bot token masked) |
|
||||
| `pos communication telegram config set TELEGRAM_BOT_TOKEN=...` | Saves a bot token (600 perms) |
|
||||
| `pos communication telegram config set TELEGRAM_CHAT_ID=...` | Saves the target chat ID |
|
||||
|
||||
The bot token is a secret — it is stored only in `~/.config/linux_post_install/telegram.env` and never in the repo. Requires network access to `api.telegram.org`.
|
||||
|
||||
### flags
|
||||
|
||||
Feature-flag management CLIs (see [SCRIPTS.md → lib/flags.sh](SCRIPTS.md#libflagssh--feature-flags)):
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `flag-reader` | List all flags + status (`set: <name>` / `unset: <name>`) |
|
||||
| `flag-reader <name>` | Check one flag; exit 0 if set, 1 if not |
|
||||
| `flag-reader --raw <name>` | Print only the stored value (script-friendly) |
|
||||
| `flag-set <name> [value]` | Set a flag, optionally with a value (requires sudo) |
|
||||
| `flag-clear <name>` | Unset a flag (requires sudo) |
|
||||
|
||||
---
|
||||
|
||||
## Legacy wrappers
|
||||
|
||||
Thin 2-line scripts that `exec pos … "$@"`. All of them still work:
|
||||
|
||||
| Wrapper | Forwards to |
|
||||
|---------|-------------|
|
||||
| `wr-ip` | `pos network ip` |
|
||||
| `wr-checkport` | `pos network checkport` |
|
||||
| `wr-scan-ping` | `pos network scan` |
|
||||
| `wr-docker` | `pos docker` |
|
||||
| `wr-compose` | `pos docker compose` |
|
||||
| `wr-ufw` | `pos system firewall` |
|
||||
| `mp3` | `pos media mp3` |
|
||||
| `mp4` | `pos media mp4` |
|
||||
| `vbox` | `pos docker vbox` |
|
||||
| `ssh-load-all` | `pos ssh load-keys` |
|
||||
@@ -0,0 +1,20 @@
|
||||
# Documentation
|
||||
|
||||
Everything in this folder is reference material for the `Linux_post_install` project. The root [README](../README.md) is the short intro + quick start; this folder holds the detail.
|
||||
|
||||
| Document | What it covers |
|
||||
|----------|----------------|
|
||||
| [SCRIPTS.md](SCRIPTS.md) | Core installer scripts: `install.sh`, `preinstall.sh`, `postinstall.sh`, `lib/common.sh`, `lib/flags.sh`, `features/autostart.sh` — purpose, how each works, configuration |
|
||||
| [POS.md](POS.md) | The `pos` CLI: dispatcher, every `pos-*` command, Docker Compose / ScaleTail config, legacy wrappers, flag CLIs |
|
||||
| [APPS.md](APPS.md) | Optional apps: `apps/install.sh` picker, installer conventions, full app catalog |
|
||||
| [SYSTEMD.md](SYSTEMD.md) | Systemd units (`autostart.service`, `ssh-agent.service`), feature-flag gating, bash completion |
|
||||
| [DEV.md](DEV.md) | Developer guide: architecture, conventions, how to add tools/apps/features, commit guidelines |
|
||||
| [AGENT_Context_Project.md](AGENT_Context_Project.md) | Single-source context doc for AI agents working on the repo |
|
||||
| [algorithm.md](algorithm.md) | ASCII diagrams: install flow, `pos` dispatch, compose `up`, config cascade, logging, vbox lifecycle |
|
||||
|
||||
## Quick navigation
|
||||
|
||||
- Just installed and want to use it? → [POS.md](POS.md)
|
||||
- Adding a package? → [SCRIPTS.md → preinstall.sh](SCRIPTS.md#preinstallsh--system-packages)
|
||||
- Adding a CLI tool or app? → [DEV.md](DEV.md)
|
||||
- First deploy of a self-hosted service? → [POS.md → Docker Compose](POS.md#docker-compose--scaletail)
|
||||
+213
@@ -0,0 +1,213 @@
|
||||
# Core Scripts Reference
|
||||
|
||||
Everything that runs during the bootstrap install: `install.sh`, `preinstall.sh`, `postinstall.sh`, the shared libraries, and `features/`. For the `pos` CLI tools see [POS.md](POS.md), for apps see [APPS.md](APPS.md), for services see [SYSTEMD.md](SYSTEMD.md).
|
||||
|
||||
---
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [install.sh — the orchestrator](#installsh--the-orchestrator)
|
||||
- [preinstall.sh — system packages](#preinstallsh--system-packages)
|
||||
- [postinstall.sh — user configuration](#postinstallsh--user-configuration)
|
||||
- [lib/common.sh — shared library](#libcommonsh--shared-library)
|
||||
- [lib/flags.sh — feature flags](#libflagssh--feature-flags)
|
||||
- [features/autostart.sh — boot-time feature](#featuresautostartsh--boot-time-feature)
|
||||
- [x64_bin/ — precompiled binaries](#x64_bin--precompiled-binaries)
|
||||
|
||||
---
|
||||
|
||||
## install.sh — the orchestrator
|
||||
|
||||
**File:** `install.sh` (run as `./install.sh`)
|
||||
**Purpose:** the entry point. Coordinates all four install phases and the optional apps/features installs.
|
||||
|
||||
### How it works
|
||||
|
||||
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:
|
||||
- `--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).
|
||||
|
||||
The phases:
|
||||
|
||||
| # | Phase | Script/action |
|
||||
|---|-------|----------------|
|
||||
| 1 | preinstall | `preinstall.sh` — apt packages + yt-dlp |
|
||||
| 2 | scripts | Copies `bin/*` → `/usr/local/bin/` (755), `lib/common.sh` + `lib/flags.sh` → `/usr/local/bin/` (644). Copies precompiled arch binaries from `x64_bin/` (or `arm64_bin/`) → `/usr/local/bin/`. With `--feature`: also installs `features/*` (see below) |
|
||||
| 3 | postinstall | `postinstall.sh` — PATH, completion, SSH keys, systemd |
|
||||
| 4 | scalepoint | Shallow-clones ScaleTail templates to `/usr/local/share/linux_post_install/scale-tail` |
|
||||
| 5 (opt) | apps | `apps/install.sh` when `--apps` (interactive) or `--full` (all, non-interactive) |
|
||||
|
||||
**Precompiled arch binaries (Phase 2):** `install.sh` picks the source folder from the machine architecture — `x86_64` → `x64_bin/`, `aarch64`/`arm64` → `arm64_bin/` (added later) — and copies every file in it to `/usr/local/bin/` (755). These are manually-compiled tools not available as internet builds (currently `create_ap`, `wihotspot`, `wihotspot-gui`). Dropping an `arm64_bin/` folder later needs no code change.
|
||||
|
||||
**Feature block (Phase 2, only with `--feature`):** for every file in `features/` it copies it to `/usr/local/bin/<name>`. If the destination already exists it asks **"Overwrite existing …? [y/N]"** (default keeps your file), then always sets the feature flag via `flag_set` (name derived as `<filename without .sh>`).
|
||||
|
||||
### Configuration
|
||||
|
||||
No config file — everything is command-line:
|
||||
|
||||
| Option | Effect |
|
||||
|--------|--------|
|
||||
| `--apps` | Run the interactive app picker after core install |
|
||||
| `--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` |
|
||||
| `--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 |
|
||||
| `-h`, `--help` | Show usage |
|
||||
|
||||
---
|
||||
|
||||
## preinstall.sh — system packages
|
||||
|
||||
**File:** `preinstall.sh`
|
||||
**Purpose:** Phase 1 — installs the base system packages and yt-dlp.
|
||||
**Run:** automatically by `install.sh`, or standalone with `--dry-run`.
|
||||
|
||||
### How it works
|
||||
|
||||
1. `apt update`.
|
||||
2. Installs the package list.
|
||||
3. Downloads the latest `yt-dlp` binary to `/usr/local/bin/yt-dlp` and makes it executable.
|
||||
4. Verifies a couple of tools (`git --version`, `yt-dlp --version`).
|
||||
|
||||
### Configuration
|
||||
|
||||
The package list is the `PACKAGES` array:
|
||||
|
||||
```bash
|
||||
PACKAGES=(
|
||||
git curl wget vim nano tmux tree jq
|
||||
unzip zip rsync htop btop telnet
|
||||
net-tools iputils-ping traceroute tcpdump nmap
|
||||
openssh-client openssh-server ufw fail2ban
|
||||
hostapd dnsmasq iptables iw
|
||||
ca-certificates gnupg lsb-release
|
||||
python3 python3-pip rclone
|
||||
libqrencode4 libgtk-3-0
|
||||
)
|
||||
```
|
||||
|
||||
Add or remove package names here. `nmap` and `fail2ban` are used later by `pos network scan` and `postinstall.sh`; `hostapd`, `dnsmasq`, `iptables`, `iw` and the GTK/Qr libs support the precompiled hotspot tools (see [x64_bin/ — precompiled binaries](#x64_bin--precompiled-binaries)).
|
||||
|
||||
---
|
||||
|
||||
## postinstall.sh — user configuration
|
||||
|
||||
**File:** `postinstall.sh` (runs as your user)
|
||||
**Purpose:** Phase 3 — configures the user environment, SSH keys, and systemd services.
|
||||
|
||||
### How it works
|
||||
|
||||
1. **rclone config** — if `config/rclone.conf` exists (gitignored), installs it to `~/.config/rclone/rclone.conf` (600).
|
||||
2. **PATH** — appends a `PATH` line to `~/.bashrc` if not already present.
|
||||
3. **pos bash completion** — installs `completions/pos.bash` to `/usr/local/share/bash-completion/completions/` and sources it from `~/.bashrc`.
|
||||
4. **SSH authorized keys** — if `config/authorized_keys` exists, appends missing keys to `~/.ssh/authorized_keys` (skips comments and duplicates, chmod 600).
|
||||
5. **systemd services** — copies `systemd/*.service` to `/etc/systemd/system/`, daemon-reloads, then enables each service. **`autostart.service` is only enabled when the `autostart` feature flag is set** (see [lib/flags.sh](#libflagssh--feature-flags)); otherwise it's skipped with a hint to run `./install.sh --feature`.
|
||||
|
||||
### Configuration
|
||||
|
||||
- SSH keys: `config/authorized_keys` (one per line, gitignored).
|
||||
- rclone config: `config/rclone.conf` (gitignored).
|
||||
- The PATH line and completion line are embedded strings at the top of the file — edit there to change them.
|
||||
- The `autostart` flag (set by `./install.sh --feature`) controls whether `autostart.service` gets enabled.
|
||||
|
||||
---
|
||||
|
||||
## lib/common.sh — shared library
|
||||
|
||||
**File:** `lib/common.sh` (installed to `/usr/local/bin/common.sh`)
|
||||
**Purpose:** colors, logging, timers, spinners, dry-run-aware execution, and prompts. Sourced by most scripts.
|
||||
|
||||
### How it works
|
||||
|
||||
Auto-disables colors when stdout is not a TTY. The `run` helper is the dry-run hook: scripts that want `--dry-run` support run every side-effecting command through `run`.
|
||||
|
||||
### Configuration / API
|
||||
|
||||
| Function | Purpose |
|
||||
|----------|---------|
|
||||
| `log "msg"` | Green `[+]` status line |
|
||||
| `warn "msg"` | Yellow `[!]` warning |
|
||||
| `err "msg"` | Red `ERROR:` line to stderr, then `exit 1` |
|
||||
| `ok "msg"` | Green `OK` prefix line |
|
||||
| `section "title"` | Cyan-bordered section header |
|
||||
| `step N T "msg"` | Numbered step header (`[N/T] msg`) |
|
||||
| `run cmd…` | Executes the command, or logs `(dry-run)` when `DRY_RUN=1` |
|
||||
| `spawn "msg" cmd…` | Runs with an animated spinner + elapsed time; prints captured stderr and exits on failure |
|
||||
| `timer_start` / `timer_stop` | Track and print elapsed time |
|
||||
| `confirm "prompt" [default]` | Yes/no prompt; default `y` (`[Y/n]`) unless `n` given (`[y/N]`) |
|
||||
|
||||
---
|
||||
|
||||
## lib/flags.sh — feature flags
|
||||
|
||||
**File:** `lib/flags.sh` (installed to `/usr/local/bin/flags.sh`)
|
||||
**Purpose:** a system-wide, per-feature flag store. Flags mark features as installed/opted-in and gate behavior (e.g. systemd enablement) elsewhere.
|
||||
|
||||
### How it works
|
||||
|
||||
One file per flag in `$FLAGS_DIR`. **Presence = set, file content = optional value.** Reads are plain file ops; writes go through `run` + `sudo` so they respect `--dry-run`. Installed by `./install.sh --feature`; also usable directly:
|
||||
|
||||
```bash
|
||||
source lib/flags.sh
|
||||
flag_set autostart # bare flag
|
||||
flag_set app "2.1" # flag with a value
|
||||
flag_is_set autostart # 0 if set, 1 if not
|
||||
flag_value app # prints "2.1"
|
||||
flag_list # names of all set flags
|
||||
flag_clear autostart
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
| Setting | Location |
|
||||
|---------|----------|
|
||||
| `FLAGS_DIR` (env) | Default `/usr/local/share/linux_post_install/flags` (dir 755, files 644). Overridable via environment for testing |
|
||||
| CLI wrappers | `flag-reader`, `flag-set`, `flag-clear` (see [POS.md](POS.md)) |
|
||||
|
||||
---
|
||||
|
||||
## features/autostart.sh — boot-time feature
|
||||
|
||||
**File:** `features/autostart.sh` (installed to `/usr/local/bin/autostart.sh` by `./install.sh --feature`)
|
||||
**Purpose:** runs once at boot via `autostart.service` (only when the `autostart` flag is green) and logs basic connectivity status.
|
||||
|
||||
### How it works
|
||||
|
||||
Appends timestamped lines to `~/.autostart.log`:
|
||||
|
||||
```
|
||||
[<date>] autostart running
|
||||
[<date>] Network: online # ping 8.8.8.8 succeeded
|
||||
[<date>] autostart complete
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
- Log file: `$HOME/.autostart.log` (edit the `LOG` variable at the top).
|
||||
- The script is the one you're *most* likely to customize — this is exactly why it lives in `features/` instead of `bin/`: a plain reinstall never overwrites your edits.
|
||||
|
||||
---
|
||||
|
||||
## x64_bin/ — precompiled binaries
|
||||
|
||||
**Folder:** `x64_bin/` (future: `arm64_bin/`)
|
||||
**Purpose:** manually-compiled tools that are **not available as prebuilt binaries on the internet**. `install.sh` copies them verbatim into `/usr/local/bin/` on the matching architecture (see [install.sh — the orchestrator](#installsh--the-orchestrator)).
|
||||
|
||||
### Contents
|
||||
|
||||
| File | Type | Purpose | Runtime deps |
|
||||
|------|------|---------|--------------|
|
||||
| `create_ap` | bash script | Create a Wi-Fi access point from the CLI (NAT/Internet sharing) | `hostapd`, `dnsmasq`, `iptables`, `iw` (in `preinstall.sh`) |
|
||||
| `wihotspot` | POSIX wrapper | Launches `wihotspot-gui` (path points at `/usr/local/bin/`) | — |
|
||||
| `wihotspot-gui` | ELF x86-64 | GTK3 GUI for the hotspot (QR code via libqrencode) | `libgtk-3-0`, `libqrencode4` |
|
||||
|
||||
### Configuration
|
||||
|
||||
- Managed through the `pos network hotspot` command (see [POS.md → network](POS.md#network)): `hotspot` launches the GUI; `start`/`stop`/`status` wrap `create_ap`.
|
||||
- Add a future `arm64_bin/` folder with the same filenames and it is installed automatically on `aarch64` machines — no `install.sh` change needed.
|
||||
@@ -0,0 +1,92 @@
|
||||
# Systemd & Shell Integration Reference
|
||||
|
||||
The units installed and enabled by `postinstall.sh`, plus the `pos` bash completion.
|
||||
|
||||
- [Services](#services)
|
||||
- [`autostart.service`](#autostartservice)
|
||||
- [`ssh-agent.service`](#ssh-agentservice)
|
||||
- [Feature-flag gating](#feature-flag-gating)
|
||||
- [Bash completion](#bash-completion)
|
||||
|
||||
---
|
||||
|
||||
## Services
|
||||
|
||||
`postinstall.sh` copies every `systemd/*.service` to `/etc/systemd/system/`, runs `systemctl daemon-reload`, then enables each one (see the gating rule below).
|
||||
|
||||
### autostart.service
|
||||
|
||||
**Purpose:** run `features/autostart.sh` at boot (after the network is online) and keep retrying if it fails.
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=My Linux Autostart Script
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/local/bin/autostart.sh
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
**Configuration:** point `ExecStart` at your boot script. Because the target script is a *feature*, this unit is only **enabled** when the `autostart` flag is set — the file is still copied, but a skipped feature leaves the unit present-but-disabled.
|
||||
|
||||
### ssh-agent.service
|
||||
|
||||
**Purpose:** a system-wide SSH agent, one shared socket for all sessions (so `pos ssh load-keys` and everyday ssh work without per-login agents).
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=SSH Authentication Agent
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStartPre=mkdir -p /run/ssh-agent
|
||||
ExecStart=/usr/bin/ssh-agent -D -a /run/ssh-agent/socket
|
||||
ExecStartPost=/bin/sh -c 'chmod 666 /run/ssh-agent/socket'
|
||||
ExecStopPost=/bin/sh -c 'rm -f /run/ssh-agent/socket'
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
**Configuration:** socket at `/run/ssh-agent/socket` (world-readable/writable). `~/.bashrc` (set by `postinstall.sh`) exports `SSH_AUTH_SOCK` to it. Not gated on any feature flag.
|
||||
|
||||
---
|
||||
|
||||
## Feature-flag gating
|
||||
|
||||
The systemd loop in `postinstall.sh` special-cases `autostart.service`:
|
||||
|
||||
```bash
|
||||
if [ "$svc_name" = "autostart.service" ] && ! flag_is_set autostart; then
|
||||
warn "autostart feature not installed — skipping autostart.service (run ./install.sh --feature)"
|
||||
continue
|
||||
fi
|
||||
```
|
||||
|
||||
Set the flag with `./install.sh --feature` (or `flag-set autostart`). See [SCRIPTS.md → lib/flags.sh](SCRIPTS.md#libflagssh--feature-flags).
|
||||
|
||||
---
|
||||
|
||||
## Bash completion
|
||||
|
||||
**File:** `completions/pos.bash`
|
||||
**Purpose:** tab-completion for the `pos` CLI.
|
||||
|
||||
### How it works
|
||||
|
||||
- **Dynamically discovers** subcommands by listing executable `pos-*` files next to the `pos` binary — no hard-coded command list, so new tools complete automatically.
|
||||
- Works with the `bash-completion` package (`_init_completion`) and falls back to a manual init if it isn't loaded.
|
||||
- Provides completion for the first two words of `pos <category> <command>`.
|
||||
|
||||
### Configuration
|
||||
|
||||
Installed by `postinstall.sh` to `/usr/local/share/bash-completion/completions/pos.bash` and sourced from `~/.bashrc`. To load it manually: `source completions/pos.bash` (or copy into `/etc/bash_completion.d/`).
|
||||
@@ -325,7 +325,7 @@ pos docker compose up jellyfin
|
||||
|
||||
---
|
||||
|
||||
## 6. `pos-vbox` Lifecycle
|
||||
## 6. `pos docker vbox` Lifecycle
|
||||
|
||||
```
|
||||
create lab1
|
||||
@@ -2,22 +2,21 @@
|
||||
|
||||
> One command turns a bare Debian/Ubuntu install into a fully productive machine.
|
||||
|
||||
---
|
||||
## What is this
|
||||
|
||||
## What Is This
|
||||
After reinstalling Linux you usually need to install packages, set up SSH, configure a firewall, and install apps. This repo automates all of that in one go.
|
||||
|
||||
After reinstalling Linux, you usually need to install packages, set up SSH, configure the firewall, and install apps. This repo automates all of that in one go.
|
||||
It is a **personal toolkit** — a bootstrap script, a unified `pos` CLI for everyday tasks, optional app installers, and self-hosted services via ScaleTail + Tailscale.
|
||||
|
||||
**What you get:**
|
||||
|
||||
- **25+ system packages** installed automatically (git, curl, tmux, ufw, fail2ban, etc.)
|
||||
- **Unified `pos` CLI** — one command for network, Docker, media, system, and SSH tasks
|
||||
- **119+ self-hosted services** via ScaleTail + Tailscale (Jellyfin, Home Assistant, etc.)
|
||||
- **15 optional desktop apps** (VS Code, Docker Desktop, Brave, OBS, etc.) — pick what you want
|
||||
- **systemd services** for SSH agent and boot-time automation
|
||||
- **Everything in `/usr/local/bin/`** — you can delete the repo after install
|
||||
|
||||
---
|
||||
- 25+ system packages installed automatically
|
||||
- The `pos` CLI: network, Docker (compose + vbox VMs), media, system, SSH, USB, and communication (Telegram) tools
|
||||
- Wi-Fi hotspot tools (`create_ap`, `wihotspot-gui`) via `pos network hotspot`
|
||||
- 15 optional desktop apps (VS Code, Brave, OBS, Tailscale, …) — pick what you want
|
||||
- 119+ self-hosted services with Tailscale access (Jellyfin, Home Assistant, …)
|
||||
- systemd services for SSH agent and boot-time automation
|
||||
- Everything lands in `/usr/local/bin/` — you can delete the repo after install
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -25,168 +24,28 @@ After reinstalling Linux, you usually need to install packages, set up SSH, conf
|
||||
git clone https://gitea.skink-platy.ts.net/admin/Linux_post_install.git
|
||||
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 # also install features/ scripts (asks before overwriting)
|
||||
./install.sh --apps # also install optional desktop apps (interactive)
|
||||
```
|
||||
|
||||
**Flags:**
|
||||
|
||||
| Flag | What it does |
|
||||
|------|-------------|
|
||||
| `--apps` | Run interactive app picker after core install |
|
||||
| `--full` | Core install + all apps (no prompts) |
|
||||
| `--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` |
|
||||
| `--feature` | Install `features/` scripts to `/usr/local/bin/`, sets their flags |
|
||||
| `--apps` | Interactive app picker after core install |
|
||||
| `--full` | Core install + all apps (non-interactive) |
|
||||
| `--dry-run` | Preview without executing |
|
||||
| `--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 |
|
||||
|
||||
---
|
||||
## Documentation
|
||||
|
||||
## What Gets Installed
|
||||
|
||||
| Phase | Script | What happens |
|
||||
|-------|--------|-------------|
|
||||
| 1 | `preinstall.sh` | `apt update` + 25+ packages + yt-dlp + fail2ban |
|
||||
| 2 | `install.sh` | Copies all `bin/` tools to `/usr/local/bin/` |
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
## The `pos` CLI
|
||||
|
||||
After install, use the `pos` command for everything:
|
||||
|
||||
```bash
|
||||
pos # list available categories
|
||||
pos help network # help for a specific category
|
||||
```
|
||||
|
||||
### Network
|
||||
|
||||
```bash
|
||||
pos network ip # Show interfaces, routes, public IP
|
||||
pos network checkport 192.168.1.1:80 # Check if a TCP port is open
|
||||
pos network scan 192.168.8.0/24 # Fast parallel ping sweep
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
pos docker ps # List containers with health, IPs, ports
|
||||
pos docker health # Health dashboard (exits 1 if unhealthy)
|
||||
```
|
||||
|
||||
#### Compose (ScaleTail — 119+ self-hosted services)
|
||||
|
||||
Each service runs with a Tailscale sidecar and gets its own `tail-xxxxx.ts.net` URL.
|
||||
|
||||
**Quick start:**
|
||||
|
||||
```bash
|
||||
# 1. Set your Tailscale auth key (required once)
|
||||
pos docker compose config set TS_AUTHKEY=tskey-auth-xxxxx
|
||||
|
||||
# 2. Deploy a service
|
||||
pos docker compose up jellyfin
|
||||
|
||||
# 3. Open https://jellyfin.tail-xxxxx.ts.net
|
||||
```
|
||||
|
||||
**All commands:**
|
||||
|
||||
```bash
|
||||
pos docker compose ls # List available service templates
|
||||
pos docker compose installed # List deployed services
|
||||
pos docker compose up jellyfin # Deploy or start a service
|
||||
pos docker compose down actual-budget # Stop a service
|
||||
pos docker compose logs home-assistant -f # Tail logs
|
||||
pos docker compose restart home-assistant # Restart a service
|
||||
pos docker compose update # Pull latest templates + refresh deployed compose files
|
||||
pos docker compose config # Show current configuration
|
||||
pos docker compose config set TZ=Asia/Tokyo # Set a global default
|
||||
pos docker compose config edit # Open config in editor
|
||||
```
|
||||
|
||||
**Config strategy — three layers:**
|
||||
|
||||
| Layer | File | Purpose |
|
||||
|-------|------|---------|
|
||||
| Template defaults | `/usr/local/share/linux_post_install/scale-tail/services/<name>/.env` | Per-service defaults from ScaleTail |
|
||||
| Global config | `~/.config/linux_post_install/compose.env` | Your defaults — applies to all services |
|
||||
| Per-service | `/srv/<service>/.env` | Actual config — created on first deploy, **never overwritten** |
|
||||
|
||||
Set global defaults once, then every `up` fills them into the new service's `.env`.
|
||||
|
||||
**Paths:**
|
||||
|
||||
- Templates: `/usr/local/share/linux_post_install/scale-tail/services/`
|
||||
- Deployments: `/srv/<service>/` (configurable via `SERVICES_BASE`)
|
||||
- Global config: `~/.config/linux_post_install/compose.env`
|
||||
|
||||
**Config keys:**
|
||||
|
||||
| Key | What it does |
|
||||
|-----|-------------|
|
||||
| `TS_AUTHKEY` | Tailscale auth key (required for sidecar networking) |
|
||||
| `TZ` | Timezone for the service |
|
||||
| `DNS_SERVER` | Custom DNS server |
|
||||
| `SERVICES_BASE` | Where services are deployed (default: `/srv`)
|
||||
|
||||
### VBox (disposable Docker containers)
|
||||
|
||||
```bash
|
||||
pos vbox create lab1 # Create — prompts to enter
|
||||
pos vbox create lab1 --dir . # Bind mount current directory
|
||||
pos vbox enter lab1 # Auto-starts if stopped
|
||||
pos vbox ls # List vbox containers only
|
||||
pos vbox stop/start/rm lab1
|
||||
```
|
||||
|
||||
### Media
|
||||
|
||||
```bash
|
||||
pos media mp3 https://youtube.com/watch?v=... # Audio → MP3
|
||||
pos media mp4 https://youtube.com/watch?v=... # Video with format selection
|
||||
```
|
||||
|
||||
### System
|
||||
|
||||
```bash
|
||||
sudo pos system firewall # Interactive UFW manager
|
||||
```
|
||||
|
||||
### SSH
|
||||
|
||||
```bash
|
||||
pos ssh load-keys # Load all SSH keys into agent
|
||||
```
|
||||
|
||||
The `ssh-agent.service` runs at boot. `SSH_AUTH_SOCK` is set in `~/.bashrc`.
|
||||
|
||||
### Legacy wrappers
|
||||
|
||||
These still work and forward to `pos`: `wr-ip`, `wr-checkport`, `wr-scan-ping`, `wr-docker`, `wr-compose`, `wr-ufw`, `mp3`, `mp4`, `vbox`, `ssh-load-all`.
|
||||
|
||||
---
|
||||
|
||||
## Optional Apps
|
||||
|
||||
Install with `./apps/install.sh` (interactive), `./apps/install.sh --all`, or by name:
|
||||
|
||||
| Category | Apps |
|
||||
|----------|------|
|
||||
| Browsers | Brave |
|
||||
| Development | opencode, VS Code |
|
||||
| Media | OBS Studio, scrcpy, VLC |
|
||||
| Networking | NetBird, Tailscale, ZeroTier |
|
||||
| Remote Access | Termius, VNC Viewer |
|
||||
| System | Docker Engine, QEMU + KVM |
|
||||
| Utilities | AFFiNE, btop, LocalSend |
|
||||
|
||||
---
|
||||
|
||||
## Development
|
||||
|
||||
See [DEV.md](DEV.md) for architecture, conventions, and how to add or modify tools.
|
||||
| Topic | Where |
|
||||
|-------|-------|
|
||||
| Docs index | [DOC/README.md](DOC/README.md) |
|
||||
| Core scripts (installer, libs, features) — how they work + config | [DOC/SCRIPTS.md](DOC/SCRIPTS.md) |
|
||||
| `pos` CLI reference (all commands, compose config, wrappers) | [DOC/POS.md](DOC/POS.md) |
|
||||
| Optional apps (picker + full catalog) | [DOC/APPS.md](DOC/APPS.md) |
|
||||
| Systemd services & bash completion | [DOC/SYSTEMD.md](DOC/SYSTEMD.md) |
|
||||
| Developer guide (add tools/apps/features) | [DOC/DEV.md](DOC/DEV.md) |
|
||||
| Algorithm diagrams | [DOC/algorithm.md](DOC/algorithm.md) |
|
||||
|
||||
+11
-1
@@ -15,4 +15,14 @@ install_brave() {
|
||||
spawn "Installing brave-browser" sudo apt install -y brave-browser
|
||||
}
|
||||
|
||||
install_brave
|
||||
uninstall_brave() {
|
||||
command -v brave-browser &>/dev/null || { log "brave not installed"; return 0; }
|
||||
spawn "Removing brave-browser" sudo apt purge -y brave-browser
|
||||
spawn "Cleaning up dependencies" sudo apt autoremove -y
|
||||
spawn "Removing brave apt repo" sudo rm -f /etc/apt/sources.list.d/brave-browser-release.list /usr/share/keyrings/brave-browser-archive-keyring.gpg
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
uninstall) uninstall_brave ;;
|
||||
*) install_brave ;;
|
||||
esac
|
||||
|
||||
@@ -13,4 +13,13 @@ install_opencode() {
|
||||
log "Add to PATH: export PATH=\"\$HOME/.opencode/bin:\$PATH\""
|
||||
}
|
||||
|
||||
install_opencode
|
||||
uninstall_opencode() {
|
||||
[ -d "$HOME/.opencode" ] || { log "opencode not installed"; return 0; }
|
||||
spawn "Removing opencode" rm -rf "$HOME/.opencode"
|
||||
warn "Config/data remains in ~/.config/opencode — remove manually if desired"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
uninstall) uninstall_opencode ;;
|
||||
*) install_opencode ;;
|
||||
esac
|
||||
|
||||
@@ -15,4 +15,14 @@ install_vscode() {
|
||||
spawn "Installing code" sudo apt install -y code
|
||||
}
|
||||
|
||||
install_vscode
|
||||
uninstall_vscode() {
|
||||
command -v code &>/dev/null || { log "vscode not installed"; return 0; }
|
||||
spawn "Removing code" sudo apt purge -y code
|
||||
spawn "Cleaning up dependencies" sudo apt autoremove -y
|
||||
spawn "Removing vscode apt repo" sudo rm -f /etc/apt/sources.list.d/vscode.list /usr/share/keyrings/packages.microsoft.gpg
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
uninstall) uninstall_vscode ;;
|
||||
*) install_vscode ;;
|
||||
esac
|
||||
|
||||
+47
-19
@@ -6,26 +6,32 @@ usage() {
|
||||
cat <<EOF
|
||||
Usage: bash apps/install.sh [OPTIONS] [app ...]
|
||||
|
||||
Install optional desktop applications.
|
||||
Install or uninstall optional desktop applications.
|
||||
|
||||
Options:
|
||||
--all Install all available apps without prompting
|
||||
-h, --help Show this help message
|
||||
--all Apply action to all available apps without prompting
|
||||
--uninstall Uninstall the selected apps instead of installing
|
||||
-h, --help Show this help message
|
||||
|
||||
Examples:
|
||||
bash apps/install.sh # interactive selection
|
||||
bash apps/install.sh --all # install everything
|
||||
bash apps/install.sh brave vscode # install specific apps
|
||||
bash apps/install.sh # interactive install selection
|
||||
bash apps/install.sh --all # install everything
|
||||
bash apps/install.sh brave vscode # install specific apps
|
||||
bash apps/install.sh --uninstall # interactive uninstall selection
|
||||
bash apps/install.sh --uninstall --all # uninstall everything
|
||||
bash apps/install.sh --uninstall brave # uninstall specific apps
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
ALL=0
|
||||
UNINSTALL=0
|
||||
POSITIONAL=()
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--all) ALL=1; shift ;;
|
||||
--uninstall) UNINSTALL=1; shift ;;
|
||||
-h|--help) usage ;;
|
||||
-*) err "Unknown option: $1" ;;
|
||||
*) POSITIONAL+=("$1"); shift ;;
|
||||
@@ -62,11 +68,11 @@ for cat_dir in "$APPS_DIR"/*/; do
|
||||
[ "$has_apps" -eq 0 ] && continue
|
||||
|
||||
CATEGORIES+=("$cat_name")
|
||||
CAT_APPS["$cat_name"]=()
|
||||
CAT_APPS["$cat_name"]=""
|
||||
for f in "$cat_dir"*.sh; do
|
||||
[ -f "$f" ] || continue
|
||||
app_name=$(basename "$f" .sh)
|
||||
CAT_APPS["$cat_name"]+=("$app_name")
|
||||
CAT_APPS["$cat_name"]+=" $app_name"
|
||||
done
|
||||
done
|
||||
|
||||
@@ -74,7 +80,7 @@ done
|
||||
find_app_category() {
|
||||
local app="$1"
|
||||
for cat in "${CATEGORIES[@]}"; do
|
||||
for a in "${CAT_APPS[$cat]}"; do
|
||||
for a in ${CAT_APPS[$cat]}; do
|
||||
[ "$a" == "$app" ] && { echo "$cat"; return 0; }
|
||||
done
|
||||
done
|
||||
@@ -96,7 +102,7 @@ if [ "${#POSITIONAL[@]}" -gt 0 ]; then
|
||||
# ── Mode 2: --all ─────────────────────────────────────────────
|
||||
elif [ "$ALL" -eq 1 ]; then
|
||||
for cat in "${CATEGORIES[@]}"; do
|
||||
for app in "${CAT_APPS[$cat]}"; do
|
||||
for app in ${CAT_APPS[$cat]}; do
|
||||
SELECTED+=("$app")
|
||||
done
|
||||
done
|
||||
@@ -104,14 +110,22 @@ elif [ "$ALL" -eq 1 ]; then
|
||||
# ── Mode 3: interactive TUI ───────────────────────────────────
|
||||
else
|
||||
section "Optional Applications"
|
||||
echo "Select apps to install (y/n for each):"
|
||||
if [ "$UNINSTALL" -eq 1 ]; then
|
||||
echo "Select apps to uninstall (y/n for each):"
|
||||
else
|
||||
echo "Select apps to install (y/n for each):"
|
||||
fi
|
||||
echo
|
||||
|
||||
for cat in "${CATEGORIES[@]}"; do
|
||||
display="${CAT_NAMES[$cat]:-$cat}"
|
||||
echo " $display"
|
||||
for app in "${CAT_APPS[$cat]}"; do
|
||||
read -rp " Install ${app}? [y/N]: " yn
|
||||
for app in ${CAT_APPS[$cat]}; do
|
||||
if [ "$UNINSTALL" -eq 1 ]; then
|
||||
read -rp " Remove ${app}? [y/N]: " yn
|
||||
else
|
||||
read -rp " Install ${app}? [y/N]: " yn
|
||||
fi
|
||||
if [[ "$yn" =~ ^[Yy] ]]; then
|
||||
SELECTED+=("$app")
|
||||
fi
|
||||
@@ -120,11 +134,15 @@ else
|
||||
done
|
||||
fi
|
||||
|
||||
# ── Install selected apps ──────────────────────────────────────
|
||||
# ── Apply action to selected apps ─────────────────────────────
|
||||
[ "${#SELECTED[@]}" -eq 0 ] && { warn "No apps selected"; exit 0; }
|
||||
|
||||
echo
|
||||
section "Installing ${SELECTED[*]}"
|
||||
if [ "$UNINSTALL" -eq 1 ]; then
|
||||
section "Uninstalling ${SELECTED[*]}"
|
||||
else
|
||||
section "Installing ${SELECTED[*]}"
|
||||
fi
|
||||
|
||||
timer_start
|
||||
count=1
|
||||
@@ -132,12 +150,22 @@ total=${#SELECTED[@]}
|
||||
for app in "${SELECTED[@]}"; do
|
||||
cat=$(find_app_category "$app")
|
||||
step "$count" "$total" "$app"
|
||||
bash "$APPS_DIR/$cat/$app.sh"
|
||||
if [ "$UNINSTALL" -eq 1 ]; then
|
||||
bash "$APPS_DIR/$cat/$app.sh" uninstall
|
||||
else
|
||||
bash "$APPS_DIR/$cat/$app.sh"
|
||||
fi
|
||||
count=$((count + 1))
|
||||
echo
|
||||
done
|
||||
|
||||
echo
|
||||
echo "${GREEN}════════════════════════════════════════════${RESET}"
|
||||
echo "${GREEN} Apps installed ($(timer_stop))${RESET}"
|
||||
echo "${GREEN}════════════════════════════════════════════${RESET}"
|
||||
if [ "$UNINSTALL" -eq 1 ]; then
|
||||
echo "${GREEN}════════════════════════════════════════════${RESET}"
|
||||
echo "${GREEN} Apps uninstalled ($(timer_stop))${RESET}"
|
||||
echo "${GREEN}════════════════════════════════════════════${RESET}"
|
||||
else
|
||||
echo "${GREEN}════════════════════════════════════════════${RESET}"
|
||||
echo "${GREEN} Apps installed ($(timer_stop))${RESET}"
|
||||
echo "${GREEN}════════════════════════════════════════════${RESET}"
|
||||
fi
|
||||
|
||||
+10
-1
@@ -7,4 +7,13 @@ install_obs() {
|
||||
spawn "Installing obs-studio" sudo apt install -y obs-studio
|
||||
}
|
||||
|
||||
install_obs
|
||||
uninstall_obs() {
|
||||
command -v obs &>/dev/null || { log "obs-studio not installed"; return 0; }
|
||||
spawn "Removing obs-studio" sudo apt purge -y obs-studio
|
||||
spawn "Cleaning up dependencies" sudo apt autoremove -y
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
uninstall) uninstall_obs ;;
|
||||
*) install_obs ;;
|
||||
esac
|
||||
|
||||
+10
-1
@@ -50,4 +50,13 @@ EOF
|
||||
log "scrcpy $version installed (adb included in the bundle)"
|
||||
}
|
||||
|
||||
install_scrcpy
|
||||
uninstall_scrcpy() {
|
||||
command -v scrcpy &>/dev/null || { log "scrcpy not installed"; return 0; }
|
||||
spawn "Removing scrcpy files" sudo rm -rf /usr/local/lib/scrcpy-* /usr/local/bin/scrcpy /usr/share/applications/scrcpy.desktop
|
||||
log "scrcpy removed (bundled adb was removed with it)"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
uninstall) uninstall_scrcpy ;;
|
||||
*) install_scrcpy ;;
|
||||
esac
|
||||
|
||||
+10
-1
@@ -7,4 +7,13 @@ install_vlc() {
|
||||
spawn "Installing vlc" sudo apt install -y vlc
|
||||
}
|
||||
|
||||
install_vlc
|
||||
uninstall_vlc() {
|
||||
command -v vlc &>/dev/null || { log "vlc not installed"; return 0; }
|
||||
spawn "Removing vlc" sudo apt purge -y vlc
|
||||
spawn "Cleaning up dependencies" sudo apt autoremove -y
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
uninstall) uninstall_vlc ;;
|
||||
*) install_vlc ;;
|
||||
esac
|
||||
|
||||
@@ -11,4 +11,14 @@ install_netbird() {
|
||||
log "Join a network: sudo netbird up --setup-key <key>"
|
||||
}
|
||||
|
||||
install_netbird
|
||||
uninstall_netbird() {
|
||||
command -v netbird &>/dev/null || { log "netbird not installed"; return 0; }
|
||||
spawn "Removing netbird" sudo apt purge -y netbird
|
||||
spawn "Cleaning up dependencies" sudo apt autoremove -y
|
||||
spawn "Removing netbird apt repo" sudo rm -f /etc/apt/sources.list.d/netbird.list
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
uninstall) uninstall_netbird ;;
|
||||
*) install_netbird ;;
|
||||
esac
|
||||
|
||||
@@ -11,4 +11,14 @@ install_tailscale() {
|
||||
log "Start tailscale: sudo tailscale up"
|
||||
}
|
||||
|
||||
install_tailscale
|
||||
uninstall_tailscale() {
|
||||
command -v tailscale &>/dev/null || { log "tailscale not installed"; return 0; }
|
||||
spawn "Removing tailscale" sudo apt purge -y tailscale
|
||||
spawn "Cleaning up dependencies" sudo apt autoremove -y
|
||||
spawn "Removing tailscale apt repo" sudo rm -f /etc/apt/sources.list.d/tailscale.list /usr/share/keyrings/tailscale-archive-keyring.gpg
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
uninstall) uninstall_tailscale ;;
|
||||
*) install_tailscale ;;
|
||||
esac
|
||||
|
||||
@@ -11,4 +11,14 @@ install_zerotier() {
|
||||
log "Join a network: sudo zerotier-cli join <network-id>"
|
||||
}
|
||||
|
||||
install_zerotier
|
||||
uninstall_zerotier() {
|
||||
command -v zerotier-one &>/dev/null || { log "zerotier not installed"; return 0; }
|
||||
spawn "Removing zerotier" sudo apt purge -y zerotier-one
|
||||
spawn "Cleaning up dependencies" sudo apt autoremove -y
|
||||
spawn "Removing zerotier apt repo" sudo rm -f /etc/apt/sources.list.d/zerotier.list
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
uninstall) uninstall_zerotier ;;
|
||||
*) install_zerotier ;;
|
||||
esac
|
||||
|
||||
@@ -17,4 +17,13 @@ install_termius() {
|
||||
log "Termius installed — launch with 'termius'"
|
||||
}
|
||||
|
||||
install_termius
|
||||
uninstall_termius() {
|
||||
command -v termius &>/dev/null || { log "termius not installed"; return 0; }
|
||||
spawn "Removing termius" sudo apt purge -y termius
|
||||
spawn "Cleaning up dependencies" sudo apt autoremove -y
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
uninstall) uninstall_termius ;;
|
||||
*) install_termius ;;
|
||||
esac
|
||||
|
||||
@@ -7,4 +7,13 @@ install_vnc_viewer() {
|
||||
spawn "Installing tigervnc-viewer" sudo apt install -y tigervnc-viewer
|
||||
}
|
||||
|
||||
install_vnc_viewer
|
||||
uninstall_vnc_viewer() {
|
||||
command -v vncviewer &>/dev/null || { log "tigervnc-viewer not installed"; return 0; }
|
||||
spawn "Removing tigervnc-viewer" sudo apt purge -y tigervnc-viewer
|
||||
spawn "Cleaning up dependencies" sudo apt autoremove -y
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
uninstall) uninstall_vnc_viewer ;;
|
||||
*) install_vnc_viewer ;;
|
||||
esac
|
||||
|
||||
+20
-1
@@ -12,4 +12,23 @@ install_docker() {
|
||||
warn "Log out and back in for docker group to take effect"
|
||||
}
|
||||
|
||||
install_docker
|
||||
uninstall_docker() {
|
||||
command -v docker &>/dev/null || { log "docker not installed"; return 0; }
|
||||
|
||||
local pkgs=(docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-compose-v2)
|
||||
local installed=()
|
||||
for p in "${pkgs[@]}"; do
|
||||
dpkg -s "$p" &>/dev/null && installed+=("$p")
|
||||
done
|
||||
if [ "${#installed[@]}" -gt 0 ]; then
|
||||
spawn "Removing docker engine" sudo apt purge -y "${installed[@]}"
|
||||
spawn "Cleaning up dependencies" sudo apt autoremove -y
|
||||
fi
|
||||
spawn "Removing docker apt repo" sudo rm -f /etc/apt/sources.list.d/docker.list /etc/apt/keyrings/docker.asc /etc/apt/keyrings/docker.gpg
|
||||
warn "Docker data remains in /var/lib/docker — remove manually if desired"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
uninstall) uninstall_docker ;;
|
||||
*) install_docker ;;
|
||||
esac
|
||||
|
||||
+13
-1
@@ -15,4 +15,16 @@ install_qemu() {
|
||||
warn "Log out and back in for libvirt/kvm groups to take effect"
|
||||
}
|
||||
|
||||
install_qemu
|
||||
uninstall_qemu() {
|
||||
command -v qemu-system-x86_64 &>/dev/null || { log "qemu not installed"; return 0; }
|
||||
spawn "Removing qemu and libvirt" sudo apt purge -y \
|
||||
qemu-system qemu-utils qemu-kvm \
|
||||
libvirt-daemon-system libvirt-clients \
|
||||
bridge-utils virt-manager
|
||||
spawn "Cleaning up dependencies" sudo apt autoremove -y
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
uninstall) uninstall_qemu ;;
|
||||
*) install_qemu ;;
|
||||
esac
|
||||
|
||||
@@ -32,4 +32,12 @@ EOF
|
||||
"
|
||||
}
|
||||
|
||||
install_affine
|
||||
uninstall_affine() {
|
||||
command -v affine &>/dev/null || { log "affine not installed"; return 0; }
|
||||
spawn "Removing AFFiNE" sudo rm -rf /opt/affine /usr/local/bin/affine /usr/share/applications/affine.desktop
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
uninstall) uninstall_affine ;;
|
||||
*) install_affine ;;
|
||||
esac
|
||||
|
||||
+10
-1
@@ -7,4 +7,13 @@ install_btop() {
|
||||
spawn "Installing btop" sudo apt install -y btop
|
||||
}
|
||||
|
||||
install_btop
|
||||
uninstall_btop() {
|
||||
command -v btop &>/dev/null || { log "btop not installed"; return 0; }
|
||||
spawn "Removing btop" sudo apt purge -y btop
|
||||
spawn "Cleaning up dependencies" sudo apt autoremove -y
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
uninstall) uninstall_btop ;;
|
||||
*) install_btop ;;
|
||||
esac
|
||||
|
||||
@@ -16,4 +16,15 @@ install_localsend() {
|
||||
spawn "Installing localsend" sudo flatpak install -y flathub org.localsend.localsend_app
|
||||
}
|
||||
|
||||
install_localsend
|
||||
uninstall_localsend() {
|
||||
if ! flatpak list 2>/dev/null | grep -q org.localsend.localsend_app; then
|
||||
log "localsend not installed"
|
||||
return 0
|
||||
fi
|
||||
spawn "Removing localsend" sudo flatpak uninstall -y org.localsend.localsend_app
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
uninstall) uninstall_localsend ;;
|
||||
*) install_localsend ;;
|
||||
esac
|
||||
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/../../lib/common.sh"
|
||||
|
||||
install_tsui() {
|
||||
command -v tsui &>/dev/null && { log "tsui already installed"; return 0; }
|
||||
|
||||
spawn "Installing tsui" bash -c "
|
||||
curl -fsSL https://neuralink.com/tsui/install.sh | bash
|
||||
"
|
||||
log "Run tsui: sudo tsui"
|
||||
}
|
||||
|
||||
uninstall_tsui() {
|
||||
command -v tsui &>/dev/null || { log "tsui not installed"; return 0; }
|
||||
spawn "Removing tsui" sudo rm -f /usr/local/bin/tsui
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
uninstall) uninstall_tsui ;;
|
||||
*) install_tsui ;;
|
||||
esac
|
||||
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"
|
||||
@@ -25,9 +25,60 @@ _pos_commands() {
|
||||
echo "${cmds[*]}"
|
||||
}
|
||||
|
||||
# ── Category list (sorted, filename-derived) ─────────────────────
|
||||
_pos_category_list() {
|
||||
local -A cats=()
|
||||
local cmd cat f
|
||||
for f in "$self"/pos-*; do
|
||||
[ -x "$f" ] || continue
|
||||
cmd="${f##*/pos-}"
|
||||
cat="${cmd%%-*}"
|
||||
[ "$cat" = "$cmd" ] && continue
|
||||
cats["$cat"]+="${cmd#*-} "
|
||||
done
|
||||
local c line subs joined
|
||||
for c in $(printf '%s\n' "${!cats[@]}" | sort); do
|
||||
subs=(${cats[$c]})
|
||||
joined="$(IFS='|'; echo "${subs[*]}")"
|
||||
printf " %-14s%s\n" "$c" "$joined"
|
||||
done
|
||||
}
|
||||
|
||||
# ── Category helpers ────────────────────────────────────────────
|
||||
_pos_category_exists() {
|
||||
local cat="${1:-}" f
|
||||
[ -n "$cat" ] || return 1
|
||||
for f in "$self"/pos-"$cat"-*; do
|
||||
[ -x "$f" ] && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
_pos_category_help() {
|
||||
local cat="$1" sub subs=()
|
||||
local f
|
||||
for f in "$self"/pos-"$cat"-*; do
|
||||
[ -x "$f" ] || continue
|
||||
subs+=("${f##*/pos-$cat-}")
|
||||
done
|
||||
|
||||
echo "pos $cat — $cat tools"
|
||||
echo
|
||||
echo "USAGE"
|
||||
echo " pos $cat <command> [args]"
|
||||
echo
|
||||
echo "COMMANDS"
|
||||
printf " %s\n" "${subs[@]}"
|
||||
echo
|
||||
echo "Run 'pos $cat <command> --help' for details on a command."
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ── Help text ──────────────────────────────────────────────────
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
local cats
|
||||
cats="$(_pos_category_list)"
|
||||
cat <<EOF
|
||||
pos — Personal OS Toolkit
|
||||
|
||||
A unified CLI for network, docker, media, system,
|
||||
@@ -37,37 +88,41 @@ USAGE
|
||||
pos <category> <command> [args]
|
||||
|
||||
CATEGORIES
|
||||
network ip | checkport | scan
|
||||
docker ps | compose
|
||||
media mp3 | mp4
|
||||
system firewall
|
||||
ssh load-keys
|
||||
vbox create | enter | stop | start | rm | ls
|
||||
|
||||
$cats
|
||||
EXAMPLES
|
||||
pos network ip Show interfaces, routes, public IP
|
||||
pos network checkport 10.0.0.1:80 Check if a TCP port is open
|
||||
pos network scan 192.168.1.0/24 Fast parallel ping sweep
|
||||
pos network hotspot Launch wihotspot-gui
|
||||
|
||||
pos docker ps List containers (health, IPs, ports)
|
||||
pos docker compose ls List available ScaleTail services
|
||||
pos docker compose up jellyfin Deploy a service with Tailscale
|
||||
pos docker health One-glance health dashboard
|
||||
|
||||
pos media mp3 <url> Download audio as MP3
|
||||
pos media mp4 <url> Download video as MP4
|
||||
|
||||
pos system firewall Interactive UFW manager
|
||||
pos system backup /srv/project Encrypted (AES-256) folder snapshot
|
||||
pos system backup --service Pick a folder from /srv or ~/srv
|
||||
|
||||
pos ssh load-keys Load all SSH keys into agent
|
||||
|
||||
pos vbox create lab1 Create disposable Docker VM
|
||||
pos vbox create lab1 --dir . Create VM using current directory
|
||||
pos vbox enter lab1 Shell into a Docker VM
|
||||
pos vbox ls List Docker VMs
|
||||
pos usb server --ls List USB devices + connected clients
|
||||
|
||||
pos communication telegram --send "Backup done"
|
||||
Send a Telegram message
|
||||
|
||||
pos docker vbox create lab1 Create disposable Docker VM
|
||||
pos docker vbox create lab1 --dir . Create VM using current directory
|
||||
pos docker vbox enter lab1 Shell into a Docker VM
|
||||
pos docker vbox ls List Docker VMs
|
||||
|
||||
HELP
|
||||
pos help <command> Show help for a command
|
||||
pos --help Show this help
|
||||
pos <category> --help Show a category's commands
|
||||
pos help <command> Show help for a command
|
||||
pos --help Show this help
|
||||
|
||||
LEGACY WRAPPERS
|
||||
wr-ip, wr-checkport, wr-scan-ping, wr-docker,
|
||||
@@ -86,12 +141,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
|
||||
@@ -100,6 +156,13 @@ fi
|
||||
args=("$@")
|
||||
n=${#args[@]}
|
||||
|
||||
# ── Category help: pos <category> [--help] ─────────────────────
|
||||
if _pos_category_exists "${args[0]:-}"; then
|
||||
if [ "$n" -eq 1 ] || { [ "$n" -eq 2 ] && { [ "${args[1]}" = "-h" ] || [ "${args[1]}" = "--help" ]; }; }; then
|
||||
_pos_category_help "${args[0]}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Logging setup ──────────────────────────────────────────────
|
||||
LOG_DIR="$HOME/.local/share/linux_post_install/logs"
|
||||
mkdir -p "$LOG_DIR" 2>/dev/null || true
|
||||
@@ -109,7 +172,7 @@ MAIN_LOG="$LOG_DIR/pos.log"
|
||||
log_cmd() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $* → exit $2" >> "$MAIN_LOG"; }
|
||||
|
||||
# Commands that read from stdin interactively — only log invocation
|
||||
INTERACTIVE_CMDS="system-firewall media-mp4"
|
||||
INTERACTIVE_CMDS="system-firewall media-mp4 system-backup usb-server"
|
||||
|
||||
for ((i=n-1; i>=0; i--)); do
|
||||
cmd="pos"
|
||||
|
||||
Executable
+140
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# POS: communication telegram — Send Telegram messages via Bot API (--send, test, config set)
|
||||
# POS_FLAGS: --send
|
||||
|
||||
CONFIG_DIR="$HOME/.config/linux_post_install"
|
||||
CONFIG_FILE="$CONFIG_DIR/telegram.env"
|
||||
API="https://api.telegram.org"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: pos communication telegram [command] [args]
|
||||
|
||||
Send Telegram messages via the Bot API.
|
||||
|
||||
Commands:
|
||||
--send "text" Send a text message to the configured chat
|
||||
--send "text" --token <t> --chat-id <id> Override token/chat for one send
|
||||
test Send a test message using the current config
|
||||
config Show current config (token masked)
|
||||
config set KEY=VALUE Set TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID
|
||||
|
||||
Config file: $CONFIG_FILE
|
||||
Keys: TELEGRAM_BOT_TOKEN | TELEGRAM_CHAT_ID
|
||||
|
||||
Precedence: CLI flags > environment > config file.
|
||||
|
||||
Examples:
|
||||
pos communication telegram config set TELEGRAM_BOT_TOKEN=123456:ABC-DEF
|
||||
pos communication telegram config set TELEGRAM_CHAT_ID=987654321
|
||||
pos communication telegram --send "Backup finished"
|
||||
pos communication telegram test
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
err() { echo "ERROR: $*" >&2; exit 1; }
|
||||
|
||||
load_config() {
|
||||
[ -f "$CONFIG_FILE" ] || return 0
|
||||
local k v
|
||||
while IFS='=' read -r k v; do
|
||||
[ -n "$k" ] || continue
|
||||
case "$k" in
|
||||
\#*) continue ;;
|
||||
esac
|
||||
v="${v%\"}"; v="${v#\"}"; v="${v%\'}"; v="${v#\'}"
|
||||
if [ -z "${!k:-}" ]; then
|
||||
export "$k"="$v"
|
||||
fi
|
||||
done < <(grep -E '^[A-Z_]+=' "$CONFIG_FILE" || true)
|
||||
}
|
||||
|
||||
mask_token() {
|
||||
local t="$1"
|
||||
[ -z "$t" ] && { echo "(not set)"; return; }
|
||||
if [ "${#t}" -le 12 ]; then
|
||||
echo "${t:0:4}... (${#t} chars)"
|
||||
else
|
||||
echo "${t:0:6}...${t: -4} (${#t} chars)"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_send() {
|
||||
local text="$1"
|
||||
[ -z "${TELEGRAM_BOT_TOKEN:-}" ] && err "No bot token — run 'pos communication telegram config set TELEGRAM_BOT_TOKEN=...'"
|
||||
[ -z "${TELEGRAM_CHAT_ID:-}" ] && err "No chat id — run 'pos communication telegram config set TELEGRAM_CHAT_ID=...'"
|
||||
curl -fsS -m 20 -X POST "$API/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
|
||||
--data-urlencode "chat_id=${TELEGRAM_CHAT_ID}" \
|
||||
--data-urlencode "text=${text}" >/dev/null
|
||||
echo "[+] Message sent to chat ${TELEGRAM_CHAT_ID}"
|
||||
}
|
||||
|
||||
cmd_config() {
|
||||
load_config
|
||||
echo "Config: $CONFIG_FILE"
|
||||
echo " TELEGRAM_BOT_TOKEN = $(mask_token "${TELEGRAM_BOT_TOKEN:-}")"
|
||||
echo " TELEGRAM_CHAT_ID = ${TELEGRAM_CHAT_ID:-}"
|
||||
}
|
||||
|
||||
cmd_config_set() {
|
||||
local kv="$1"
|
||||
local key="${kv%%=*}"
|
||||
local val="${kv#*=}"
|
||||
case "$key" in
|
||||
TELEGRAM_BOT_TOKEN|TELEGRAM_CHAT_ID) ;;
|
||||
*) err "Unknown key '$key' (allowed: TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID)" ;;
|
||||
esac
|
||||
[ -z "$val" ] && err "No value given (expected $key=...)"
|
||||
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
if grep -q "^${key}=" "$CONFIG_FILE"; then
|
||||
sed -i "s|^${key}=.*|${key}=${val}|" "$CONFIG_FILE"
|
||||
else
|
||||
echo "${key}=${val}" >>"$CONFIG_FILE"
|
||||
fi
|
||||
else
|
||||
echo "${key}=${val}" >"$CONFIG_FILE"
|
||||
fi
|
||||
chmod 600 "$CONFIG_FILE"
|
||||
echo "[+] $key saved to $CONFIG_FILE"
|
||||
}
|
||||
|
||||
cmd="${1:-}"
|
||||
|
||||
case "$cmd" in
|
||||
-h|--help) usage ;;
|
||||
--send)
|
||||
shift
|
||||
[ $# -ge 1 ] || usage
|
||||
text="$1"
|
||||
shift
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--token) TELEGRAM_BOT_TOKEN="$2"; shift 2 ;;
|
||||
--chat-id) TELEGRAM_CHAT_ID="$2"; shift 2 ;;
|
||||
*) err "Unknown option '$1'" ;;
|
||||
esac
|
||||
done
|
||||
load_config
|
||||
cmd_send "$text"
|
||||
;;
|
||||
test)
|
||||
load_config
|
||||
cmd_send "Test message from pos $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
;;
|
||||
config)
|
||||
shift
|
||||
case "${1:-}" in
|
||||
set) shift; [ $# -eq 1 ] || usage; cmd_config_set "$1" ;;
|
||||
*) cmd_config ;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: Unknown telegram command '$cmd'"
|
||||
echo "Run 'pos communication telegram --help' for usage."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# POS: docker compose — Docker Compose service manager (ls/up/down/restart/logs/update/config)
|
||||
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
||||
|
||||
SCALE_DIR="/usr/local/share/linux_post_install/scale-tail/services"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# POS: docker health — One-glance container health dashboard (exits 1 if unhealthy)
|
||||
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
||||
|
||||
usage() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# POS: docker ps — Enhanced container overview (health, IPs, ports, uptime)
|
||||
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
||||
|
||||
usage() {
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# POS: docker vbox — Disposable Docker-based VMs (create/enter/start/stop/rm/ls)
|
||||
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage:
|
||||
pos vbox create <name> [image] [--dir <path>]
|
||||
pos vbox enter <name>
|
||||
pos vbox stop <name>
|
||||
pos vbox start <name>
|
||||
pos vbox rm <name>
|
||||
pos vbox ls
|
||||
pos docker vbox create <name> [image] [--dir <path>]
|
||||
pos docker vbox enter <name>
|
||||
pos docker vbox stop <name>
|
||||
pos docker vbox start <name>
|
||||
pos docker vbox rm <name>
|
||||
pos docker vbox ls
|
||||
|
||||
Manage disposable Docker containers as lightweight VMs.
|
||||
|
||||
@@ -22,15 +23,15 @@ Options:
|
||||
Use "." for current directory
|
||||
|
||||
Examples:
|
||||
pos vbox create lab1
|
||||
pos vbox create lab1 --dir .
|
||||
pos vbox create lab1 --dir /mnt/data/lab1
|
||||
pos vbox create kali kalilinux/kali-rolling
|
||||
pos vbox enter lab1
|
||||
pos vbox stop lab1
|
||||
pos vbox start lab1
|
||||
pos vbox rm lab1
|
||||
pos vbox ls
|
||||
pos docker vbox create lab1
|
||||
pos docker vbox create lab1 --dir .
|
||||
pos docker vbox create lab1 --dir /mnt/data/lab1
|
||||
pos docker vbox create kali kalilinux/kali-rolling
|
||||
pos docker vbox enter lab1
|
||||
pos docker vbox stop lab1
|
||||
pos docker vbox start lab1
|
||||
pos docker vbox rm lab1
|
||||
pos docker vbox ls
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#This pos-entertaiment bin for configure enable or disable entertaiments elements such enable/disable weather, gold ...so on
|
||||
# and check flags to choose wich entertaiments to run or not
|
||||
set -euo pipefail
|
||||
|
||||
CONFIG_DIR="$HOME/.config/linux_post_install"
|
||||
CONFIG_FILE="$CONFIG_DIR/entertaiment.env"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# POS: media mp3 — Download audio as MP3 (yt-dlp)
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# POS: media mp4 — Download video as MP4 (interactive format select)
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# POS: network checkport — Check TCP port connectivity
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
|
||||
Executable
+93
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# POS: network hotspot — Wi-Fi hotspot via create_ap + wihotspot-gui
|
||||
# POS_FLAGS: --foreground
|
||||
|
||||
LOGFILE=/var/log/linux_post_install_hotspot.log
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: pos network hotspot [command] [args]
|
||||
|
||||
Manage Wi-Fi hotspots with create_ap (CLI) or wihotspot-gui (GUI).
|
||||
These tools are precompiled and shipped in x64_bin/ (or arm64_bin/),
|
||||
installed to /usr/local/bin/ by install.sh.
|
||||
|
||||
Commands:
|
||||
(no args) Launch the wihotspot-gui
|
||||
start [--foreground] <wifi-iface> [<internet-iface>] <ssid> [<passphrase>]
|
||||
Create an access point; asks whether to run in the
|
||||
background. Use --foreground to skip the prompt
|
||||
and block until Ctrl+C.
|
||||
stop [<id>] Stop the running access point. <id> is an interface
|
||||
name or PID; if omitted it is detected automatically.
|
||||
status List running access points
|
||||
|
||||
Examples:
|
||||
pos network hotspot
|
||||
pos network hotspot start wlan0 eth0 MyNet mypass
|
||||
pos network hotspot start --foreground wlan0 eth0 MyNet mypass
|
||||
pos network hotspot stop
|
||||
pos network hotspot status
|
||||
|
||||
Background logs: $LOGFILE
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
cmd="${1:-}"
|
||||
|
||||
case "$cmd" in
|
||||
-h|--help) usage ;;
|
||||
"") exec wihotspot-gui ;;
|
||||
start)
|
||||
shift
|
||||
fg=0
|
||||
bg=0
|
||||
if [ "${1:-}" = "--foreground" ]; then
|
||||
fg=1
|
||||
shift
|
||||
fi
|
||||
[ $# -ge 2 ] || usage
|
||||
if [ $fg -eq 0 ]; then
|
||||
read -rp "Run in background? [y/N]: " bg
|
||||
case "$bg" in
|
||||
[yY]*) bg=1 ;;
|
||||
*) bg=0 ;;
|
||||
esac
|
||||
fi
|
||||
if [ $bg -eq 1 ]; then
|
||||
sudo create_ap --daemon --logfile "$LOGFILE" "$@"
|
||||
echo "[+] Hotspot started in background"
|
||||
echo " Log : $LOGFILE"
|
||||
echo " Status: pos network hotspot status"
|
||||
echo " Stop : pos network hotspot stop"
|
||||
else
|
||||
exec sudo create_ap "$@"
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
shift
|
||||
id="${1:-}"
|
||||
if [ -z "$id" ]; then
|
||||
ids=$(sudo create_ap --list-running | awk 'NF { print $2 }')
|
||||
if [ -z "$ids" ]; then
|
||||
echo "No hotspot running."
|
||||
exit 0
|
||||
fi
|
||||
for id in $ids; do
|
||||
sudo create_ap --stop "$id"
|
||||
done
|
||||
else
|
||||
sudo create_ap --stop "$id"
|
||||
fi
|
||||
;;
|
||||
status)
|
||||
exec sudo create_ap --list-running
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: Unknown hotspot command '$cmd'"
|
||||
echo "Run 'pos network hotspot --help' for usage."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
+24
-1
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# POS: network ip — Show interfaces, routes, public IP + location
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
@@ -42,5 +43,27 @@ SEP
|
||||
echo "Public IP"
|
||||
SEP
|
||||
|
||||
curl -4 -s --max-time 5 https://ifconfig.me 2>/dev/null || echo "Unavailable"
|
||||
PUBLIC_IP=$(curl -4 -s --max-time 5 https://ifconfig.me 2>/dev/null) || true
|
||||
|
||||
if [ -n "${PUBLIC_IP:-}" ]; then
|
||||
echo "$PUBLIC_IP"
|
||||
GEO=$(curl -4 -s --max-time 5 "http://ip-api.com/json/${PUBLIC_IP}?fields=status,country,regionName,city,isp" 2>/dev/null) || true
|
||||
if [ -n "${GEO:-}" ]; then
|
||||
location=$(echo "$GEO" | python3 -c "
|
||||
import sys, json
|
||||
d = json.load(sys.stdin)
|
||||
if d.get('status') == 'success':
|
||||
print(f\"{d['city']}, {d['regionName']}, {d['country']} ({d['isp']})\")
|
||||
" 2>/dev/null) || true
|
||||
if [ -n "${location:-}" ]; then
|
||||
printf "%-20s %s\n" "Location" "$location"
|
||||
else
|
||||
echo "Location: Unavailable"
|
||||
fi
|
||||
else
|
||||
echo "Location: Unavailable"
|
||||
fi
|
||||
else
|
||||
echo "Unavailable"
|
||||
fi
|
||||
echo
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# POS: network scan — Parallel ping sweep of CIDR
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# POS: ssh load-keys — Load all SSH keys into the agent
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
|
||||
Executable
+117
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# POS: system backup — Encrypted (AES-256) folder snapshots (tar + gpg)
|
||||
# POS_FLAGS: --service
|
||||
|
||||
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: pos system backup <folder-path>
|
||||
pos system backup --service
|
||||
|
||||
Create a gpg-encrypted (AES-256) tar.gz snapshot of a folder and verify it.
|
||||
The archive password is prompted twice and never stored.
|
||||
|
||||
Modes:
|
||||
<folder-path> Back up that folder directly.
|
||||
--service List folders under /srv and ~/srv, pick one, back it up.
|
||||
|
||||
The final artifact <name>_<date>.tar.gz.gpg is written to the current directory.
|
||||
|
||||
Environment:
|
||||
BACKUP_SERVICE_ROOTS Space-separated roots for --service
|
||||
(default: /srv \$HOME/srv)
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
command -v tar &>/dev/null || err "tar not found"
|
||||
command -v gpg &>/dev/null || err "gpg not found (install gnupg)"
|
||||
|
||||
SERVICE=0
|
||||
case "${1:-}" in
|
||||
-h|--help) usage ;;
|
||||
--service) SERVICE=1 ;;
|
||||
"") err "Missing folder path (or use --service)" ;;
|
||||
*) FOLDER="$1" ;;
|
||||
esac
|
||||
|
||||
if [ "$SERVICE" -eq 1 ]; then
|
||||
if [ -n "${BACKUP_SERVICE_ROOTS:-}" ]; then
|
||||
read -r -a roots <<< "$BACKUP_SERVICE_ROOTS"
|
||||
else
|
||||
roots=(/srv "$HOME/srv")
|
||||
fi
|
||||
|
||||
idx=0
|
||||
for root in "${roots[@]}"; do
|
||||
[ -d "$root" ] || { warn "Root not found: $root"; continue; }
|
||||
|
||||
dirs=()
|
||||
while IFS= read -r d; do
|
||||
dirs+=("$d")
|
||||
done < <(find "$root" -maxdepth 1 -mindepth 1 -type d | sort)
|
||||
|
||||
[ ${#dirs[@]} -gt 0 ] || { warn "No folders in $root"; continue; }
|
||||
|
||||
echo
|
||||
echo "${root}:"
|
||||
for d in "${dirs[@]}"; do
|
||||
idx=$((idx + 1))
|
||||
names[$idx]="$d"
|
||||
printf "%2d) %s\n" "$idx" "$d"
|
||||
done
|
||||
done
|
||||
|
||||
[ "$idx" -gt 0 ] || err "No folders found under: ${roots[*]}"
|
||||
|
||||
read -rp "Select folder number: " choice
|
||||
if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > idx )); then
|
||||
err "Invalid selection: $choice"
|
||||
fi
|
||||
FOLDER="${names[$choice]}"
|
||||
fi
|
||||
|
||||
[ -d "$FOLDER" ] || err "Folder not found: $FOLDER"
|
||||
|
||||
NAME="$(basename "$FOLDER")"
|
||||
DATE="$(date +%Y-%m-%d_%H-%M-%S)"
|
||||
ARCHIVE="${NAME}_${DATE}.tar.gz"
|
||||
|
||||
echo
|
||||
log "Creating backup..."
|
||||
echo "Source : $FOLDER"
|
||||
echo "Output : $ARCHIVE"
|
||||
|
||||
sudo tar -czvf "$ARCHIVE" -C "$(dirname "$FOLDER")" "$NAME"
|
||||
|
||||
log "Verifying archive..."
|
||||
tar -tzf "$ARCHIVE" > /dev/null
|
||||
log "Archive verified"
|
||||
|
||||
while true; do
|
||||
read -s -rp "Enter backup password: " PASS
|
||||
echo
|
||||
read -s -rp "Confirm backup password: " CONFIRM
|
||||
echo
|
||||
if [ -n "$PASS" ] && [ "$PASS" = "$CONFIRM" ]; then
|
||||
break
|
||||
fi
|
||||
warn "Passwords are empty or do not match — try again"
|
||||
done
|
||||
unset CONFIRM
|
||||
|
||||
log "Encrypting backup..."
|
||||
gpg --batch --yes --passphrase "$PASS" --symmetric --cipher-algo AES256 "$ARCHIVE"
|
||||
|
||||
rm -f "$ARCHIVE"
|
||||
ARCHIVE="${ARCHIVE}.gpg"
|
||||
chmod 600 "$ARCHIVE"
|
||||
|
||||
log "Verifying encrypted backup..."
|
||||
gpg --batch --quiet --passphrase "$PASS" --decrypt "$ARCHIVE" | tar -tzf - > /dev/null
|
||||
|
||||
unset PASS
|
||||
echo
|
||||
log "Backup completed: $ARCHIVE"
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# POS: system firewall — Interactive UFW management
|
||||
IFS=$'\n\t'
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
|
||||
Executable
+218
@@ -0,0 +1,218 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# POS: usb server — USB Redirector server control (--ls, --share; prompts when args omitted)
|
||||
# POS_FLAGS: --ls --ls-shared --share --unshare --auto-share --callback --close-callback --auto-connect --disconnect --nickname --timeout --port --info --version
|
||||
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: pos usb server [flag] [args]
|
||||
|
||||
Control the USB Redirector server (usbsrv) — share local USB devices
|
||||
over the network and manage connected clients.
|
||||
|
||||
Flags:
|
||||
--ls List host USB devices and connected clients
|
||||
--ls-shared List shared or in-use devices only
|
||||
--share [dev-id] [client-id] Share a device and connect it to a client
|
||||
(interactive picker when IDs are omitted)
|
||||
--unshare [dev-id] Stop sharing a device
|
||||
--auto-share on|off Toggle automatic sharing of new devices
|
||||
--callback [addr:port] Create a callback connection to a client
|
||||
--close-callback [target|all] Close a client callback
|
||||
--auto-connect on|off [client] Toggle remote auto-connect for a client
|
||||
--disconnect [dev-id|all] Disconnect a device from its clients
|
||||
--nickname [dev-id] [nick] Set a device nickname (empty nick removes it)
|
||||
--timeout [dev-id] [sec] Set device inactivity timeout (0 disables)
|
||||
--port [num] Set the TCP port (restart server to apply)
|
||||
--info Show server info
|
||||
--version Show server version
|
||||
-h, --help Show this help
|
||||
|
||||
Examples:
|
||||
pos usb server --ls
|
||||
pos usb server --share
|
||||
pos usb server --share 0-1 3
|
||||
pos usb server --auto-share on
|
||||
pos usb server --port 32032
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
cmd_ls() {
|
||||
echo "==== HOST USB DEVICES ===="
|
||||
usbsrv -list-devices
|
||||
echo
|
||||
echo "==== CONNECTED CLIENTS ===="
|
||||
usbsrv -list-clients
|
||||
}
|
||||
|
||||
cmd_ls_shared() {
|
||||
echo "==== SHARED OR IN USE DEVICES ===="
|
||||
usbsrv -list-devices | awk '
|
||||
BEGIN {RS=""; FS="\n"}
|
||||
{
|
||||
for(i=1; i<=NF; i++) {
|
||||
if ($i ~ /Status:.*(shared|in use)/) {
|
||||
print $0 "\n"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
' || true
|
||||
}
|
||||
|
||||
cmd_share() {
|
||||
local dev="${1:-}" client="${2:-}"
|
||||
if [ -z "$dev" ]; then
|
||||
echo "==== HOST USB DEVICES ===="
|
||||
usbsrv -list-devices
|
||||
echo
|
||||
echo "==== CONNECTED CLIENTS ===="
|
||||
usbsrv -list-clients
|
||||
echo
|
||||
read -rp "Enter device ID to share: " dev
|
||||
read -rp "Enter client ID to connect to: " client
|
||||
fi
|
||||
[ -n "$dev" ] || err "no device ID given"
|
||||
[ -n "$client" ] || err "no client ID given"
|
||||
log "Sharing device ID $dev..."
|
||||
usbsrv -share "$dev"
|
||||
log "Connecting device $dev to client $client..."
|
||||
usbsrv -connect-to "${client}-${dev}"
|
||||
}
|
||||
|
||||
cmd_unshare() {
|
||||
local dev="${1:-}"
|
||||
if [ -z "$dev" ]; then
|
||||
cmd_ls_shared
|
||||
read -rp "Enter device ID to unshare: " dev
|
||||
fi
|
||||
[ -n "$dev" ] || err "no device ID given"
|
||||
log "Unsharing device ID $dev..."
|
||||
usbsrv -unshare "$dev"
|
||||
}
|
||||
|
||||
cmd_auto_share() {
|
||||
local mode="${1:-}"
|
||||
if [ -z "$mode" ]; then
|
||||
read -rp "Turn auto-share on or off? (on/off): " mode
|
||||
fi
|
||||
case "$mode" in
|
||||
on|off) ;;
|
||||
*) err "Invalid option '$mode' — must be 'on' or 'off'" ;;
|
||||
esac
|
||||
usbsrv -auto-share "$mode"
|
||||
log "Auto-share set to $mode"
|
||||
}
|
||||
|
||||
cmd_callback() {
|
||||
local addr="${1:-}"
|
||||
if [ -z "$addr" ]; then
|
||||
read -rp "Enter client address:port for callback (e.g. 192.168.1.100:32032): " addr
|
||||
fi
|
||||
[ -n "$addr" ] || err "no address given"
|
||||
usbsrv -create-callback "$addr"
|
||||
}
|
||||
|
||||
cmd_close_callback() {
|
||||
local target="${1:-}"
|
||||
if [ -z "$target" ]; then
|
||||
usbsrv -list-clients
|
||||
read -rp "Enter client or clientid or 'all' to close callback: " target
|
||||
fi
|
||||
[ -n "$target" ] || err "no target given"
|
||||
usbsrv -close-callback "$target"
|
||||
}
|
||||
|
||||
cmd_auto_connect() {
|
||||
local mode="${1:-}" client="${2:-}"
|
||||
if [ -z "$mode" ]; then
|
||||
read -rp "Enable or disable remote auto-connect? (on/off): " mode
|
||||
fi
|
||||
case "$mode" in
|
||||
on|off) ;;
|
||||
*) err "Invalid option '$mode' — must be 'on' or 'off'" ;;
|
||||
esac
|
||||
if [ -z "$client" ]; then
|
||||
usbsrv -list-clients
|
||||
read -rp "Enter client or clientid: " client
|
||||
fi
|
||||
[ -n "$client" ] || err "no client given"
|
||||
usbsrv -remote-auto-connect "$mode" "$client"
|
||||
}
|
||||
|
||||
cmd_disconnect() {
|
||||
local dev="${1:-}"
|
||||
if [ -z "$dev" ]; then
|
||||
cmd_ls_shared
|
||||
read -rp "Enter device ID or 'all' to disconnect from clients: " dev
|
||||
fi
|
||||
[ -n "$dev" ] || err "no device ID given"
|
||||
usbsrv -disconnect-from "$dev"
|
||||
}
|
||||
|
||||
cmd_nickname() {
|
||||
local dev="${1:-}" nick="${2:-}"
|
||||
if [ -z "$dev" ]; then
|
||||
usbsrv -list-devices
|
||||
read -rp "Enter device ID to set nickname (empty to remove): " dev
|
||||
read -rp "Enter nickname (or leave empty to remove): " nick
|
||||
fi
|
||||
[ -n "$dev" ] || err "no device ID given"
|
||||
usbsrv -set-nickname "$nick" "$dev"
|
||||
}
|
||||
|
||||
cmd_timeout() {
|
||||
local dev="${1:-}" sec="${2:-}"
|
||||
if [ -z "$dev" ]; then
|
||||
usbsrv -list-devices
|
||||
read -rp "Enter device ID to set timeout for: " dev
|
||||
read -rp "Enter timeout in seconds (0 to disable): " sec
|
||||
fi
|
||||
[ -n "$dev" ] || err "no device ID given"
|
||||
[[ "$sec" =~ ^[0-9]+$ ]] || err "Invalid timeout '$sec' — must be a number"
|
||||
usbsrv -set-timeout "$sec" "$dev"
|
||||
}
|
||||
|
||||
cmd_port() {
|
||||
local port="${1:-}"
|
||||
if [ -z "$port" ]; then
|
||||
read -rp "Enter new TCP port: " port
|
||||
fi
|
||||
[[ "$port" =~ ^[0-9]+$ ]] || err "Invalid port number '$port'"
|
||||
usbsrv -set-tcp-port "$port"
|
||||
warn "Restart your server for port changes to take effect."
|
||||
}
|
||||
|
||||
cmd="${1:-}"
|
||||
|
||||
case "$cmd" in
|
||||
-h|--help|"") usage ;;
|
||||
esac
|
||||
|
||||
case "$cmd" in
|
||||
--ls|--ls-shared|--share|--unshare|--auto-share|--callback|--close-callback|--auto-connect|--disconnect|--nickname|--timeout|--port|--info|--version) ;;
|
||||
*) err "Unknown flag '$cmd'" ;;
|
||||
esac
|
||||
|
||||
command -v usbsrv &>/dev/null \
|
||||
|| err "usbsrv not found — install the USB Redirector server (https://www.incentivespro.com/usb-server.html)"
|
||||
|
||||
case "$cmd" in
|
||||
--ls) cmd_ls ;;
|
||||
--ls-shared) cmd_ls_shared ;;
|
||||
--share) shift; cmd_share "$@" ;;
|
||||
--unshare) shift; cmd_unshare "$@" ;;
|
||||
--auto-share) shift; cmd_auto_share "$@" ;;
|
||||
--callback) shift; cmd_callback "$@" ;;
|
||||
--close-callback) shift; cmd_close_callback "$@" ;;
|
||||
--auto-connect) shift; cmd_auto_connect "$@" ;;
|
||||
--disconnect) shift; cmd_disconnect "$@" ;;
|
||||
--nickname) shift; cmd_nickname "$@" ;;
|
||||
--timeout) shift; cmd_timeout "$@" ;;
|
||||
--port) shift; cmd_port "$@" ;;
|
||||
--info) usbsrv -info ;;
|
||||
--version) usbsrv -version ;;
|
||||
*) err "Unknown flag '$cmd'"; usage ;;
|
||||
esac
|
||||
@@ -1,2 +1,2 @@
|
||||
#!/usr/bin/env bash
|
||||
exec pos vbox "$@"
|
||||
exec pos docker vbox "$@"
|
||||
|
||||
+34
-6
@@ -1,6 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
# Bash completion for pos — dynamically discovers pos-* subcommands
|
||||
# Install: source this file in ~/.bashrc or place in /etc/bash_completion.d/
|
||||
# GEN:START posflags
|
||||
declare -A _pos_flags
|
||||
_pos_flags[communication-telegram]="--send"
|
||||
_pos_flags[network-hotspot]="--foreground"
|
||||
_pos_flags[system-backup]="--service"
|
||||
_pos_flags[usb-server]="--ls --ls-shared --share --unshare --auto-share --callback --close-callback --auto-connect --disconnect --nickname --timeout --port --info --version"
|
||||
# GEN:END posflags
|
||||
|
||||
_pos() {
|
||||
local cur prev words cword
|
||||
@@ -45,7 +52,7 @@ _pos() {
|
||||
|
||||
_pos_complete_subcats() {
|
||||
local cat="${words[1]}"
|
||||
COMPREPLY=($(compgen -W "${cat_cmds[$cat]:-}" -- "$cur"))
|
||||
COMPREPLY=($(compgen -W "${cat_cmds[$cat]:-} --help" -- "$cur"))
|
||||
}
|
||||
|
||||
_pos_complete_compose_services() {
|
||||
@@ -63,7 +70,7 @@ _pos() {
|
||||
COMPREPLY=($(compgen -W "ls installed up down restart logs update config" -- "$cur"))
|
||||
}
|
||||
|
||||
_pos_complete_vbox_cmds() {
|
||||
_pos_complete_docker_vbox_cmds() {
|
||||
COMPREPLY=($(compgen -W "create enter stop start rm ls" -- "$cur"))
|
||||
}
|
||||
|
||||
@@ -73,6 +80,11 @@ _pos() {
|
||||
COMPREPLY=($(compgen -W "$names" -- "$cur"))
|
||||
}
|
||||
|
||||
_pos_complete_flags() {
|
||||
local tool="$1"
|
||||
COMPREPLY=($(compgen -W "${_pos_flags[$tool]:-} --help" -- "$cur"))
|
||||
}
|
||||
|
||||
# ── Dispatch ───────────────────────────────────────────────
|
||||
case "${#words[@]}" in
|
||||
2)
|
||||
@@ -93,8 +105,20 @@ _pos() {
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
vbox-*)
|
||||
_pos_complete_vbox_cmds
|
||||
docker-vbox)
|
||||
_pos_complete_docker_vbox_cmds
|
||||
;;
|
||||
usb-server)
|
||||
_pos_complete_flags usb-server
|
||||
;;
|
||||
communication-telegram)
|
||||
_pos_complete_flags communication-telegram
|
||||
;;
|
||||
network-hotspot)
|
||||
_pos_complete_flags network-hotspot
|
||||
;;
|
||||
system-backup)
|
||||
_pos_complete_flags system-backup
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
@@ -107,8 +131,12 @@ _pos() {
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
vbox-create|vbox-enter|vbox-stop|vbox-start|vbox-rm)
|
||||
_pos_complete_docker_vbox_names
|
||||
docker-vbox)
|
||||
case "${words[3]}" in
|
||||
create|enter|stop|start|rm)
|
||||
_pos_complete_docker_vbox_names
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK8u4mvN1oWUGjWZCjUT0742u6AJEIHfi+PAQgZuNnPv
|
||||
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
DATA=$(curl -s "https://api.coingecko.com/api/v3/simple/price?ids=pax-gold&vs_currencies=usd")
|
||||
|
||||
if [ -z "$DATA" ]; then
|
||||
echo "Gold API unavailable"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PRICE=$(echo "$DATA" | grep -o '"usd":[0-9.]*' | cut -d: -f2)
|
||||
|
||||
if [ -z "$PRICE" ]; then
|
||||
echo "Parse error"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
GRAM=$(echo "scale=2; $PRICE / 31.1035" | bc)
|
||||
|
||||
MESSAGE="🪙 Gold Price
|
||||
|
||||
1 oz: $PRICE USD
|
||||
1 gram: $GRAM USD"
|
||||
|
||||
echo "$MESSAGE"
|
||||
|
||||
pos communication telegram --send "$MESSAGE"
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
|
||||
# meed to be dynamic later
|
||||
LAT="52.3676"
|
||||
LON="4.9041"
|
||||
|
||||
DATA=$(curl -s "https://api.open-meteo.com/v1/forecast?latitude=$LAT&longitude=$LON¤t_weather=true")
|
||||
|
||||
TEMP=$(echo "$DATA" | grep -o '"temperature":[^,]*' | cut -d: -f2)
|
||||
WIND=$(echo "$DATA" | grep -o '"windspeed":[^,]*' | cut -d: -f2)
|
||||
|
||||
MESSAGE="🌤 Weather
|
||||
Temperature: ${TEMP}°C
|
||||
Wind: ${WIND} km/h"
|
||||
|
||||
echo "$MESSAGE"
|
||||
|
||||
pos communication telegram --send "$MESSAGE"
|
||||
+49
-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
|
||||
@@ -34,6 +37,11 @@ Options:
|
||||
(1=preinstall, 2=scripts, 3=postinstall, 4=scalepoint)
|
||||
--no-color Disable colored output
|
||||
-h, --help Show this help message
|
||||
|
||||
Uninstall optional apps later with:
|
||||
./apps/install.sh --uninstall # interactive uninstall selection
|
||||
./apps/install.sh --uninstall --all # uninstall everything
|
||||
./apps/install.sh --uninstall <app>... # uninstall specific apps
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
@@ -42,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"
|
||||
@@ -103,7 +112,46 @@ 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
|
||||
|
||||
# ── Precompiled architecture binaries ─────────────────────
|
||||
# Manually-compiled binaries (not available on the internet),
|
||||
# copied straight into /usr/local/bin for the matching arch.
|
||||
# Drop an arm64_bin/ folder later — it is picked up automatically.
|
||||
case "$(uname -m)" in
|
||||
x86_64) prebuilt_dir="x64_bin" ;;
|
||||
aarch64|arm64) prebuilt_dir="arm64_bin" ;;
|
||||
*) prebuilt_dir="" ;;
|
||||
esac
|
||||
if [ -n "$prebuilt_dir" ] && [ -d "$prebuilt_dir" ]; then
|
||||
for f in "$prebuilt_dir"/*; do
|
||||
[ -f "$f" ] || continue
|
||||
run sudo install -m 755 "$f" /usr/local/bin/
|
||||
done
|
||||
ok "$prebuilt_dir binaries -> /usr/local/bin"
|
||||
fi
|
||||
|
||||
# ── 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
|
||||
|
||||
+3
-1
@@ -26,12 +26,14 @@ while [[ $# -gt 0 ]]; do
|
||||
done
|
||||
|
||||
PACKAGES=(
|
||||
git curl wget vim nano tmux tree jq
|
||||
git curl wget bc vim nano tmux tree jq
|
||||
unzip zip rsync htop btop telnet
|
||||
net-tools iputils-ping traceroute tcpdump nmap
|
||||
openssh-client openssh-server ufw fail2ban
|
||||
hostapd dnsmasq iptables iw
|
||||
ca-certificates gnupg lsb-release
|
||||
python3 python3-pip rclone
|
||||
libqrencode4 libgtk-3-0
|
||||
)
|
||||
|
||||
spawn "apt update" sudo apt update
|
||||
|
||||
Executable
+143
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# Regenerate code-derived doc sections between GEN markers.
|
||||
#
|
||||
# scripts/gen-docs.sh rewrite files in place
|
||||
# scripts/gen-docs.sh --check verify only; exit 1 on any drift
|
||||
#
|
||||
# Sources of truth:
|
||||
# - bin/pos-* filenames → category, subcommand
|
||||
# - "# POS:" header line → one-line description
|
||||
# - "# POS_FLAGS:" line → flag completion list (flag-style tools only)
|
||||
|
||||
root="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
mode="write"
|
||||
[ "${1:-}" = "--check" ] && mode="check"
|
||||
|
||||
ctx="$root/DOC/AGENT_Context_Project.md"
|
||||
comp="$root/completions/pos.bash"
|
||||
|
||||
# ── Collect tools: "cat|sub|desc|flags" ────────────────────────
|
||||
tools=()
|
||||
for f in "$root"/bin/pos-*; do
|
||||
[ -x "$f" ] || continue
|
||||
name="${f##*/pos-}"
|
||||
cat="${name%%-*}"
|
||||
sub="${name#*-}"
|
||||
desc="$(sed -n '/^# POS: /{s/^# POS: //;p;q}' "$f")"
|
||||
[ -n "$desc" ] || { echo "gen-docs: no '# POS:' header in $f" >&2; exit 1; }
|
||||
desc="${desc#*— }"
|
||||
flags="$(sed -n '/^# POS_FLAGS: /{s/^# POS_FLAGS: //;p;q}' "$f")"
|
||||
tools+=("$cat|$sub|$desc|$flags")
|
||||
done
|
||||
mapfile -t tools < <(printf '%s\n' "${tools[@]}" | sort)
|
||||
|
||||
# ── Block generators (emit inner content only, no markers) ──────
|
||||
gen_tree() {
|
||||
local width=0 cat sub desc flags name t
|
||||
for t in "${tools[@]}"; do
|
||||
IFS='|' read -r cat sub desc flags <<<"$t"
|
||||
name="pos-$cat-$sub"
|
||||
[ ${#name} -gt "$width" ] && width=${#name}
|
||||
done
|
||||
for t in "${tools[@]}"; do
|
||||
IFS='|' read -r cat sub desc flags <<<"$t"
|
||||
name="pos-$cat-$sub"
|
||||
printf '│ ├── %-*s# %s\n' "$((width + 1))" "$name" "$desc"
|
||||
done
|
||||
}
|
||||
|
||||
gen_dispatch() {
|
||||
local cat sub desc flags t
|
||||
for t in "${tools[@]}"; do
|
||||
IFS='|' read -r cat sub desc flags <<<"$t"
|
||||
printf '| %s | %s | `pos-%s-%s` | %s |\n' "$cat" "$sub" "$cat" "$sub" "$desc"
|
||||
done
|
||||
}
|
||||
|
||||
gen_selfcontained() {
|
||||
local list=() f base out=""
|
||||
for f in "$root"/bin/pos "$root"/bin/pos-*; do
|
||||
[ -x "$f" ] || continue
|
||||
base="$(basename "$f")"
|
||||
grep -q 'common\.sh' "$f" || list+=("$base")
|
||||
done
|
||||
for b in "${list[@]}"; do
|
||||
out+="\`$b\`, "
|
||||
done
|
||||
echo "${out%, }."
|
||||
}
|
||||
|
||||
gen_filetable() {
|
||||
local cat sub desc flags name t
|
||||
printf '| `bin/pos` | %s | CLI dispatcher with smart arg matching + logging + category help |\n' "$(wc -l < "$root/bin/pos")"
|
||||
for t in "${tools[@]}"; do
|
||||
IFS='|' read -r cat sub desc flags <<<"$t"
|
||||
name="bin/pos-$cat-$sub"
|
||||
printf '| `%s` | %s | %s |\n' "$name" "$(wc -l < "$root/$name")" "$desc"
|
||||
done
|
||||
printf '| `completions/pos.bash` | %s | Dynamic bash completion |\n' "$(wc -l < "$comp")"
|
||||
}
|
||||
|
||||
gen_posflags() {
|
||||
local cat sub desc flags t
|
||||
echo "declare -A _pos_flags"
|
||||
for t in "${tools[@]}"; do
|
||||
IFS='|' read -r cat sub desc flags <<<"$t"
|
||||
[ -n "$flags" ] || continue
|
||||
printf '_pos_flags[%s-%s]="%s"\n' "$cat" "$sub" "$flags"
|
||||
done
|
||||
}
|
||||
|
||||
# ── Replace (write) or verify (check) one marker block ──────────
|
||||
markers() {
|
||||
# bash files use '#' comment markers, markdown uses HTML comments
|
||||
case "$1" in
|
||||
*.bash|*.sh) echo "# GEN:START $2" "# GEN:END $2" ;;
|
||||
*) echo "<!-- GEN:START $2 -->" "<!-- GEN:END $2 -->" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
regen_block() {
|
||||
local file="$1" name="$2"
|
||||
local start end newfile tmp
|
||||
read -r start end <<<"$(markers "$file" "$name")"
|
||||
newfile="$(mktemp)"
|
||||
"gen_$name" > "$newfile"
|
||||
|
||||
grep -qF "$start" "$file" || { echo "gen-docs: missing marker '$start' in $file" >&2; rm -f "$newfile"; exit 1; }
|
||||
|
||||
if [ "$mode" = "check" ]; then
|
||||
local cur
|
||||
cur="$(sed -n "/^$start$/,/^$end$/p" "$file" | sed '1d;$d')"
|
||||
if [ "$cur" != "$(cat "$newfile")" ]; then
|
||||
echo "gen-docs: DRIFT in $file ($name block)" >&2
|
||||
diff <(printf '%s\n' "$cur") <(cat "$newfile") >&2 || true
|
||||
rm -f "$newfile"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
tmp="$(mktemp)"
|
||||
awk -v start="$start" -v end="$end" -v nf="$newfile" '
|
||||
$0==start {
|
||||
print
|
||||
while ((getline line < nf) > 0) print line
|
||||
skip=1
|
||||
next
|
||||
}
|
||||
skip && $0==end { skip=0; print; next }
|
||||
skip { next }
|
||||
{ print }
|
||||
' "$file" > "$tmp"
|
||||
mv "$tmp" "$file"
|
||||
fi
|
||||
rm -f "$newfile"
|
||||
}
|
||||
|
||||
regen_block "$ctx" tree
|
||||
regen_block "$ctx" dispatch
|
||||
regen_block "$ctx" selfcontained
|
||||
regen_block "$ctx" filetable
|
||||
regen_block "$comp" posflags
|
||||
|
||||
echo "gen-docs: $mode OK"
|
||||
@@ -0,0 +1,8 @@
|
||||
[Unit]
|
||||
Description=POS Entertainment Runner
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/pos-entertainment --run
|
||||
@@ -0,0 +1,10 @@
|
||||
[Unit]
|
||||
Description=Run POS Entertainment Hourly
|
||||
|
||||
[Timer]
|
||||
OnBootSec=5min
|
||||
OnUnitActiveSec=1h
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
Executable
+67
@@ -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.
|
||||
# ────────────────────────────────────────────────────────────────
|
||||
Executable
+54
@@ -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
|
||||
Executable
+52
@@ -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.
|
||||
Executable
+2044
File diff suppressed because it is too large
Load Diff
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Start wihotspot-gui
|
||||
/usr/local/bin/wihotspot-gui
|
||||
Executable
BIN
Binary file not shown.
Reference in New Issue
Block a user