feat: add pos usb server (USB Redirector control); fix doc/code drift

- new bin/pos-usb-server: flag-style control of usbsrv (--ls, --share,
  --unshare, --auto-share, --callback, --close-callback, --auto-connect,
  --disconnect, --nickname, --timeout, --port, --info, --version);
  interactive prompts fall back to args; command -v usbsrv guard
- wire into bin/pos: usb category, example, INTERACTIVE_CMDS
- completions: usb-server flag completion
- docs: POS.md usb section, AGENT_Context tree/dispatch/table, README
- fix: add pos-network-hotspot to no-common.sh list (was omitted)
- fix: stale pos-docker-compose line count 317 -> 363
- fix: DEV.md/AGENT_Context note non-apt deps (command -v guard, not PACKAGES)
- fix: document INTERACTIVE_CMDS all-or-nothing per-script trade-off
- fix: usage() shows docker health (was missing from cheat-sheet)
This commit is contained in:
Your Name
2026-08-05 04:38:03 -04:00
parent 6d3482939e
commit 494eae2bdf
7 changed files with 266 additions and 9 deletions
+9 -5
View File
@@ -39,12 +39,13 @@ Linux_post_install/
│ ├── 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-docker-compose # Docker Compose service manager (largest script, 363 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-system-backup # Encrypted folder snapshots (tar + gpg AES-256, --service) (115 lines)
│ ├── pos-ssh-load-keys # Load SSH keys into ssh-agent
│ ├── pos-usb-server # USB Redirector server control (usbsrv)
│ ├── pos-communication-telegram # Send Telegram messages via Bot API
│ ├── pos-docker-vbox # Disposable Docker-based "VMs" (pos docker vbox)
│ ├── pos-network-hotspot # Wi-Fi hotspot (create_ap + wihotspot-gui)
@@ -207,6 +208,7 @@ All non-interactive `pos` commands log output to `~/.local/share/linux_post_inst
| system | firewall | `pos-system-firewall` | Interactive UFW management |
| system | backup | `pos-system-backup` | Encrypted folder snapshots (`tar` + gpg AES-256; `--service` picks from `/srv` and `~/srv`) |
| ssh | load-keys | `pos-ssh-load-keys` | Load SSH keys into agent |
| usb | server | `pos-usb-server` | USB Redirector server control: `--ls`, `--share`, `--unshare`, `--auto-share`, `--callback`, `--disconnect`, `--nickname`, `--timeout`, `--port`, `--info`, `--version` (flag-style, prompts when args omitted) |
| communication | telegram | `pos-communication-telegram` | Send Telegram messages via Bot API (`--send`, `test`, `config set`); config in `~/.config/linux_post_install/telegram.env` |
| docker | vbox | `pos-docker-vbox` | Disposable Docker "VMs" (`create/enter/start/stop/rm/ls`; label-filtered, auto-enter prompt) |
@@ -250,7 +252,7 @@ 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`, `pos-communication-telegram`.
**Scripts that do NOT source common.sh** (self-contained): `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`.
---
@@ -458,7 +460,7 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
| `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` | 191 | CLI dispatcher with smart arg matching + logging + category help |
| `bin/pos` | 195 | CLI dispatcher with smart arg matching + logging + category help |
| `bin/pos-docker-compose` | 363 | Largest script — full compose management |
| `bin/pos-system-firewall` | 284 | Interactive UFW manager |
| `bin/pos-system-backup` | 115 | Encrypted folder snapshots: path mode + `--service` (`/srv`, `~/srv` picker), tar + gpg AES-256 |
@@ -467,7 +469,8 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
| `bin/pos-docker-vbox` | 156 | Docker-based disposable VMs (`pos docker vbox`; label-filtered, auto-enter prompt) |
| `bin/pos-network-hotspot` | 91 | Wi-Fi hotspot: `create_ap` (start with background prompt/`--foreground`, stop, status) + `wihotspot-gui` |
| `bin/pos-communication-telegram` | 138 | Telegram sender via Bot API: `--send`, `test`, `config set`; token masked; config `~/.config/linux_post_install/telegram.env` |
| `completions/pos.bash` | 122 | Dynamic bash completion |
| `bin/pos-usb-server` | 216 | USB Redirector server control (`usbsrv`): `--ls`, `--share` (interactive picker), `--unshare`, `--auto-share`, `--callback`, `--close-callback`, `--auto-connect`, `--disconnect`, `--nickname`, `--timeout`, `--port`, `--info`, `--version` |
| `completions/pos.bash` | 129 | Dynamic bash completion |
| `apps/install.sh` | 171 | App install/uninstall picker/orchestrator |
---
@@ -476,7 +479,7 @@ 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 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) |
@@ -489,6 +492,7 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
| 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` |
+3 -1
View File
@@ -54,7 +54,7 @@ All non-interactive commands log to `~/.local/share/linux_post_install/logs/`.
**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`) 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`.
- **`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`)
@@ -135,6 +135,8 @@ PACKAGES=(
)
```
**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`.
### 4. Config files (if needed)
Two kinds of config, don't mix them up:
+24
View File
@@ -10,6 +10,7 @@
- [media](#media)
- [system](#system)
- [ssh](#ssh)
- [usb](#usb)
- [communication](#communication)
- [flags](#flags)
- [Legacy wrappers](#legacy-wrappers)
@@ -159,6 +160,29 @@ The standalone `vbox` command still works and forwards to `pos docker vbox` (see
|---------|------|---------|---------------|
| `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 |
+1 -1
View File
@@ -11,7 +11,7 @@ It is a **personal toolkit** — a bootstrap script, a unified `pos` CLI for eve
**What you get:**
- 25+ system packages installed automatically
- The `pos` CLI: network, Docker (compose + vbox VMs), media, system, SSH, and communication (Telegram) tools
- 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, …)
+6 -2
View File
@@ -68,10 +68,11 @@ USAGE
CATEGORIES
network ip | checkport | scan | hotspot
docker ps | compose | vbox
docker ps | compose | health | vbox
media mp3 | mp4
system firewall | backup
ssh load-keys
usb server
communication
telegram
@@ -84,6 +85,7 @@ EXAMPLES
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
@@ -94,6 +96,8 @@ EXAMPLES
pos ssh load-keys Load all SSH keys into agent
pos usb server --ls List USB devices + connected clients
pos communication telegram --send "Backup done"
Send a Telegram message
@@ -155,7 +159,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 system-backup"
INTERACTIVE_CMDS="system-firewall media-mp4 system-backup usb-server"
for ((i=n-1; i>=0; i--)); do
cmd="pos"
+216
View File
@@ -0,0 +1,216 @@
#!/usr/bin/env bash
set -euo pipefail
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
+7
View File
@@ -73,6 +73,10 @@ _pos() {
COMPREPLY=($(compgen -W "$names" -- "$cur"))
}
_pos_complete_usb_server_flags() {
COMPREPLY=($(compgen -W "--ls --ls-shared --share --unshare --auto-share --callback --close-callback --auto-connect --disconnect --nickname --timeout --port --info --version --help" -- "$cur"))
}
# ── Dispatch ───────────────────────────────────────────────
case "${#words[@]}" in
2)
@@ -96,6 +100,9 @@ _pos() {
docker-vbox)
_pos_complete_docker_vbox_cmds
;;
usb-server)
_pos_complete_usb_server_flags
;;
esac
;;
5)