docs: reorganize into DOC/ and simplify README

- Move all docs (DEV, AGENT_Context, algorithm) into DOC/ via git mv
- Rewrite root README as short intro + quick start + links into DOC/
- Add per-area reference docs: SCRIPTS (core scripts + libs + features),
  POS (pos CLI + compose config), APPS (picker + catalog), SYSTEMD (units)
- Add DOC/README.md index; fix all cross-references
This commit is contained in:
Your Name
2026-08-03 02:50:38 -04:00
parent 2a2b4d81d1
commit 54592a5839
9 changed files with 609 additions and 215 deletions
@@ -94,8 +94,18 @@ Linux_post_install/
│ ├── autostart.service # Runs autostart.sh on boot │ ├── autostart.service # Runs autostart.sh on boot
│ └── ssh-agent.service # System-wide SSH agent socket │ └── ssh-agent.service # System-wide SSH agent socket
├── README.md # User-facing documentation ├── README.md # User-facing intro + quick start (links into DOC/)
├── DEV.md # Developer guide
├── 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 ├── .gitignore # Excludes secrets, Python artifacts, OS files
└── .gitmodules # Submodule: compose/scale-tail → ScaleTail └── .gitmodules # Submodule: compose/scale-tail → ScaleTail
``` ```
@@ -298,7 +308,7 @@ ScaleTail provides 119+ Docker Compose templates with a Tailscale sidecar patter
### Adding a New App ### 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 2. It auto-appears in the interactive picker — no registration needed
--- ---
@@ -374,7 +384,7 @@ System-wide flag store at `/usr/local/share/linux_post_install/flags/`:
### Adding a New App ### Adding a New App
1. Create `apps/<category>/<name>.sh` following the template in DEV.md 1. Create `apps/<category>/<name>.sh` following the template in DOC/DEV.md
2. It auto-appears in the interactive picker — no registration needed 2. It auto-appears in the interactive picker — no registration needed
### Adding a New Tool ### Adding a New Tool
@@ -382,7 +392,7 @@ System-wide flag store at `/usr/local/share/linux_post_install/flags/`:
1. Create `bin/pos-<category>-<command>` following conventions 1. Create `bin/pos-<category>-<command>` following conventions
2. Add system deps to `PACKAGES` array in `preinstall.sh` (if needed) 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) 3. Add config logic to `postinstall.sh` (if needed, with `.gitignore` for secrets)
4. Update `README.md` 4. Update `DOC/POS.md` (and root `README.md` only if the category list changes)
5. Test: `bash -n bin/your-tool && shellcheck bin/your-tool` 5. Test: `bash -n bin/your-tool && shellcheck bin/your-tool`
### Testing ### Testing
@@ -453,5 +463,5 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
| Modify UFW/firewall logic | Edit `bin/pos-system-firewall` | | Modify UFW/firewall logic | Edit `bin/pos-system-firewall` |
| Modify pos logging | Edit log setup in `bin/pos` | | Modify pos logging | Edit log setup in `bin/pos` |
| Modify install phases/flags | Edit arg parsing in `install.sh` | | 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` | | Add a secret config file | Add to `config/`, update `.gitignore`, add copy logic in `postinstall.sh` |
+88
View File
@@ -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) |
+4 -4
View File
@@ -119,15 +119,15 @@ PACKAGES=(
### 3. Add config files (if needed) ### 3. Add config files (if needed)
Place defaults in `config/` and add copy logic to `postinstall.sh`. If they contain secrets, add to `.gitignore` and document in README. Place defaults in `config/` and add copy logic to `postinstall.sh`. If they contain secrets, add to `.gitignore` and document in `DOC/`.
### 4. Add SSH keys (if needed) ### 4. Add SSH keys (if needed)
Place public keys in `config/authorized_keys` (one per line). `postinstall.sh` reads this file automatically. Place public keys in `config/authorized_keys` (one per line). `postinstall.sh` reads this file automatically.
### 5. Update README ### 5. Update the docs
Add a section under the relevant category in README.md. Add a section for the new command in `DOC/POS.md`.
### 6. Test ### 6. Test
@@ -188,7 +188,7 @@ Place it in `apps/<category>/<name>.sh`. It auto-appears in the picker — no re
1. Find the script in `bin/` 1. Find the script in `bin/`
2. Understand its contract (args, output, exit codes) 2. Understand its contract (args, output, exit codes)
3. Make the change — keep it idempotent 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 5. Run `shellcheck` on the modified file
--- ---
+174
View File
@@ -0,0 +1,174 @@
# `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)
- [vbox](#vbox)
- [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 <command>` runs `<that command> --help`. 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`) 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 | None. Public IP via `https://ifconfig.me` (5s timeout) |
| `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 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).
### 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 |
### 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 |
### 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 |
### vbox
**File:** `bin/pos-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 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 vbox enter <name>` | Shell into the container (auto-starts it if stopped); detects the working dir from the container mounts |
| `pos vbox start/stop/rm <name>` | Start, stop, or force-remove the container |
| `pos vbox ls` | List vbox containers only (label filter) |
### 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 vbox` |
| `ssh-load-all` | `pos ssh load-keys` |
+20
View File
@@ -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)
+188
View File
@@ -0,0 +1,188 @@
# 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)
---
## 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). 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) |
**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
ca-certificates gnupg lsb-release
python3 python3-pip rclone
)
```
Add or remove package names here. `nmap` and `fail2ban` are used later by `pos network scan` and `postinstall.sh`.
---
## 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.
+92
View File
@@ -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/`).
View File
+27 -205
View File
@@ -2,22 +2,20 @@
> One command turns a bare Debian/Ubuntu install into a fully productive machine. > 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:** **What you get:**
- **25+ system packages** installed automatically (git, curl, tmux, ufw, fail2ban, etc.) - 25+ system packages installed automatically
- **Unified `pos` CLI** — one command for network, Docker, media, system, and SSH tasks - The `pos` CLI: network, Docker, media, system, SSH, and vbox tools
- **119+ self-hosted services** via ScaleTail + Tailscale (Jellyfin, Home Assistant, etc.) - 15 optional desktop apps (VS Code, Brave, OBS, Tailscale, …) — pick what you want
- **15 optional desktop apps** (VS Code, Docker Desktop, Brave, OBS, etc.) — pick what you want - 119+ self-hosted services with Tailscale access (Jellyfin, Home Assistant, …)
- **systemd services** for SSH agent and boot-time automation - systemd services for SSH agent and boot-time automation
- **Everything in `/usr/local/bin/`** — you can delete the repo after install - Everything lands in `/usr/local/bin/` — you can delete the repo after install
---
## Quick Start ## Quick Start
@@ -25,204 +23,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 git clone https://gitea.skink-platy.ts.net/admin/Linux_post_install.git
cd Linux_post_install cd Linux_post_install
./install.sh # core: packages + CLI + services + ScaleTail ./install.sh # core: packages + CLI + services + ScaleTail
./install.sh --apps # core + interactive app picker ./install.sh --feature # also install features/ scripts (asks before overwriting)
./install.sh --full # core + all apps (non-interactive) ./install.sh --apps # also install optional desktop apps (interactive)
./install.sh --feature # core + install features/ scripts (prompts on overwrite)
``` ```
**Flags:**
| Flag | What it does | | Flag | What it does |
|------|-------------| |------|-------------|
| `--apps` | Run interactive app picker after core install | | `--feature` | Install `features/` scripts to `/usr/local/bin/`, sets their flags |
| `--full` | Core install + all apps (no prompts) | | `--apps` | Interactive app picker after core install |
| `--feature` | Install `features/` scripts to `/usr/local/bin/` (asks before overwriting), sets their feature flags | | `--full` | Core install + all apps (non-interactive) |
| `--dry-run` | Preview without executing anything | | `--dry-run` | Preview without executing |
| `--skip <phase>` | Skip a phase (repeatable): `preinstall`, `scripts`, `postinstall`, `scalepoint` | | `--skip <phase>` | Skip a phase: `preinstall`, `scripts`, `postinstall`, `scalepoint`, `apps` |
| `--steps <spec>` | Run specific phases only, e.g. `--steps 1,3` or `--steps 1-3` | | `--steps <spec>` | Run only specific phases, e.g. `--steps 1,3` |
| `--no-color` | Disable colored output | | `--no-color` | Disable colored output |
--- ## Documentation
## What Gets Installed | Topic | Where |
|-------|-------|
| Phase | Script | What happens | | Docs index | [DOC/README.md](DOC/README.md) |
|-------|--------|-------------| | Core scripts (installer, libs, features) — how they work + config | [DOC/SCRIPTS.md](DOC/SCRIPTS.md) |
| 1 | `preinstall.sh` | `apt update` + 25+ packages + yt-dlp + fail2ban | | `pos` CLI reference (all commands, compose config, wrappers) | [DOC/POS.md](DOC/POS.md) |
| 2 | `install.sh` | Copies all `bin/` tools to `/usr/local/bin/` | | Optional apps (picker + full catalog) | [DOC/APPS.md](DOC/APPS.md) |
| 3 | `postinstall.sh` | Configures fail2ban, SSH agent, PATH, bash completion, systemd services | | Systemd services & bash completion | [DOC/SYSTEMD.md](DOC/SYSTEMD.md) |
| 4 | ScaleTail clone | Downloads 119+ Docker Compose templates with Tailscale sidecar | | Developer guide (add tools/apps/features) | [DOC/DEV.md](DOC/DEV.md) |
| 5 (opt) | `apps/install.sh` | Installs desktop apps you select | | Algorithm diagrams | [DOC/algorithm.md](DOC/algorithm.md) |
| opt | `./install.sh --feature` | Installs `features/` scripts (never overwrites without asking) |
---
## Features & Flags
`features/` holds scripts you're likely to customize (like `autostart.sh`), kept out of `bin/` so a plain re-install never resets them.
```bash
./install.sh --feature # install features/ — asks before overwriting
```
- Each feature is copied to `/usr/local/bin/`; if the file already exists you're asked **"Overwrite? [y/N]"** — your existing config is kept by default.
- A successful install sets a **feature flag** at `/usr/local/share/linux_post_install/flags/` (presence = set, file content = optional value).
- Flags drive systemd: e.g. `autostart.service` is enabled only when the `autostart` flag is green.
Inspect and manage flags:
```bash
flag-reader # list all flags + status
flag-reader autostart # check one flag (exit 0 if set)
flag-reader --raw autostart # print the raw value only (script-friendly)
flag-set autostart prod # set a flag, optionally with a value
flag-clear autostart # unset a flag
```
Any project script can `source lib/flags.sh` (or the installed `/usr/local/bin/flags.sh`) and use `flag_set`, `flag_is_set`, `flag_value`, `flag_clear`.
---
## 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.
Uninstall the same way with `--uninstall`:
```bash
./apps/install.sh --uninstall # interactive uninstall selection
./apps/install.sh --uninstall --all # uninstall everything
./apps/install.sh --uninstall brave vscode # uninstall specific apps
```
| 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.