feat: pos docker stack — containers grouped by compose stack (project)
gates / consistency-and-conventions (push) Failing after 12s
gates / consistency-and-conventions (push) Failing after 12s
New bin/pos-docker-stack: docker ps output grouped by Docker Compose project (stack). Each stack is a sorted section (name, status, ports); containers without a compose project land in a Standalone group at the end. Running only by default, -a|--all includes stopped/exited. Status colored on a terminal; summary line 'Stacks: N containers: N standalone: N'; exit 0 when empty. Data via docker ps --format with \x1f delimiters (project label com.docker.compose.project from compose v2); parsed with awk -F'\x1f' + IFS=$'\x1f' read — tab/pipe delimiters are IFS whitespace or appear in values (DEV.md:213). Dash padding via sed, not tr (multi-byte). Deps guard (docker) before --help; no stdin. Docs: POS.md docker row + detail, howto/docker.md table + section, bin/pos usage EXAMPLES, AGENT_Context Common Tasks row. Verified: stub suite 23/23, live daemon runs, dispatch, make gen && make check, make lint 0 FAIL / 0 WARN.
This commit is contained in:
@@ -42,6 +42,7 @@ summary (newest last).
|
||||
|
||||
## Done
|
||||
|
||||
- **2026-08-15** — `pos docker stack` (`bin/pos-docker-stack`) — containers grouped by their Docker Compose project. Each stack is a section (project name, sorted) with lines `container-name status ports`; containers with no compose project land in a `Standalone` section at the end; ends with `Stacks: N containers: N standalone: N`. Running only by default, `-a|--all` includes stopped/exited (like `docker ps -a`). Status colored on a terminal (`Up*` green, `Exited*`/`Dead*`/`Created*` red, `Paused*`/`Restarting*` yellow); exit 0 also when no containers. Data via `docker ps` with `--format '{{.Names}}{{"\u001f"}}{{.Label "com.docker.compose.project"}}{{"\u001f"}}{{.Status}}{{"\u001f"}}{{.Ports}}'` (compose v2 sets the project label; `{{"\u001f"}}` escapes in the Go template), parsed with `awk -F'\x1f'` + `IFS=$'\x1f' read` everywhere — tab/pipe delimiters are IFS whitespace or inside values, so `\x1f` (DEV.md:213 gotcha); dash padding via `sed` not `tr` (tr corrupts multi-byte `─`). Deps guard (`docker`) before `--help`; no stdin → not in `INTERACTIVE_CMDS`; `# POS_FLAGS: -a --all`. Docs: POS.md docker row + detail, howto/docker.md table + section, `bin/pos` usage EXAMPLES, AGENT_Context §14 row. Verified: stub-PATH suite `/tmp/opencode/docker-stack-test/run-tests.sh` 23/23 (grouping, sorted stacks, `-a` shows exited, standalone, empty daemon rc=0, colored status, missing docker rc=1, `--help` after deps guard); live runs against the real daemon (affine/audiobookshelf/convertx/gitea stacks, `affine_migration_job Exited (0)` + `lab1 Exited (137)` under `-a`); dispatch via `pos docker stack`; `make gen && make check`, `make lint` 0 FAIL / 0 WARN.
|
||||
- **2026-08-15** — Fix `pos media sync` offering a Ventoy stick's **EFI partition** as the sync target: with the data partition unmounted, the 32 MB `VTOYEFI` ESP was the only mounted USB partition, `usb_detect` offered it with no context, and `cp` died mid-copy with `No space left on device` (live-box report). `usb_detect` now fetches `FSTYPE`/`PARTTYPENAME` and excludes EFI system partitions (Ventoy `VTOYEFI`, `/boot/efi`) from **both** the mounted list and the mount-offer list; `USB_MOUNTED` entries carry `mp|label|size|model|fs` and `usb_pick_root` shows that in the single-stick confirm and the multi-stick/partition picker (`1) /media/Ventoy (1.1T, Ventoy, exfat)`), while `USB_ROOT` stays a bare mountpoint (`${root%|*}`) so `pos system backup` (`${root%/}/backups`) is unaffected. `pos-media-sync` gained a pre-flight space check (measures exactly what `needs_copy` would copy vs `df -Pk`, `err`/`warn` before any copy) — no more mid-copy ENOSPC. Docs: howto/media.md target-picking note, SCRIPTS.md usb-lib paragraph, AGENT_Context hand-maintained lib row (194→205). Verified: new stub harness `/tmp/opencode/vtoyefi-run.sh` (ESP filtered from mounted + mount-offer, multi-pick shows only the data partition, space fit/too-small/dry-run-warn) green; `/tmp/opencode/backup-test` still green; live check `printf 'n\ns\n' | bash bin/pos-media-sync --mp3` no longer offers VTOYEFI (offers unmounted `sda1` Ventoy instead); `make gen && make check`, `make lint` 0 FAIL / 0 WARN.
|
||||
- **2026-08-15** — Fix `pos media sync` reporting success with 0 files when the source is a symlink: it enumerated with plain `find "$SRC"`, and GNU find (default `-P`) does not descend a command-line symlink to a directory — `~/Music -> /mnt/hdd/…/music` therefore yielded zero matches, the loop never ran, and the tool printed `0 added, 0 updated, 0 unchanged` without creating the target dir (live-box report). Switched to `find -H "$SRC"` (follows only command-line symlinks; inner-symlink semantics unchanged). howto/media.md sync section notes symlinked sources are followed. Caught live, not by the 46-case stub suite (which used a real temp dir source — lesson: add a symlink-root fixture). Verified: `printf 'y\n' | bash bin/pos-media-sync --mp3 --dry-run` now lists all 31 mp3s as "would copy"; `make gen && make check` green.
|
||||
- **2026-08-14** — `pos system backup` — smart USB detection: lsblk TRAN (lsusb/by-id cross-check), mount offer for plugged-in-but-unmounted sticks, sha256-verified copy (stub-suite 54/54).
|
||||
|
||||
@@ -10,19 +10,19 @@
|
||||
|
||||
<!-- GEN:START docmap -->
|
||||
| ## 1. Project Overview | 28–43 |
|
||||
| ## 2. Directory Structure | 44–194 |
|
||||
| ## 3. Installation Flow | 195–246 |
|
||||
| ## 4. The `pos` CLI System | 247–318 |
|
||||
| ## 5. Shared Library — `lib/common.sh` | 319–350 |
|
||||
| ## 6. Docker Compose / ScaleTail | 351–393 |
|
||||
| ## 7. Optional Apps (`apps/`) | 394–423 |
|
||||
| ## 8. Entertainment Module | 424–437 |
|
||||
| ## 9. Systemd Services | 438–449 |
|
||||
| ## 10. Configuration Files | 450–476 |
|
||||
| ## 11. Coding Conventions | 477–509 |
|
||||
| ## 12. Development Workflow | 510–562 |
|
||||
| ## 13. Key File Quick Reference | 563–625 |
|
||||
| ## 14. Common Tasks for Agents | 626–657 |
|
||||
| ## 2. Directory Structure | 44–195 |
|
||||
| ## 3. Installation Flow | 196–247 |
|
||||
| ## 4. The `pos` CLI System | 248–320 |
|
||||
| ## 5. Shared Library — `lib/common.sh` | 321–352 |
|
||||
| ## 6. Docker Compose / ScaleTail | 353–395 |
|
||||
| ## 7. Optional Apps (`apps/`) | 396–425 |
|
||||
| ## 8. Entertainment Module | 426–439 |
|
||||
| ## 9. Systemd Services | 440–451 |
|
||||
| ## 10. Configuration Files | 452–478 |
|
||||
| ## 11. Coding Conventions | 479–511 |
|
||||
| ## 12. Development Workflow | 512–564 |
|
||||
| ## 13. Key File Quick Reference | 565–628 |
|
||||
| ## 14. Common Tasks for Agents | 629–661 |
|
||||
<!-- GEN:END docmap -->
|
||||
|
||||
## 1. Project Overview
|
||||
@@ -70,6 +70,7 @@ Linux_post_install/
|
||||
│ ├── 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-stack # Containers grouped by compose stack (project); standalone group; -a/--all includes stopped
|
||||
│ ├── pos-docker-vbox # Disposable Docker-based VMs (create/enter/start/stop/rm/ls)
|
||||
│ ├── pos-entertainment-config # Show or edit the entertainment config (ENABLED auto-trigger list, weather location)
|
||||
│ ├── pos-entertainment-disable # Disable a plugin's auto-trigger (remove it from ENABLED)
|
||||
@@ -272,6 +273,7 @@ All non-interactive `pos` commands log output to `~/.local/share/linux_post_inst
|
||||
| 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 | stack | `pos-docker-stack` | Containers grouped by compose stack (project); standalone group; -a/--all includes stopped |
|
||||
| docker | vbox | `pos-docker-vbox` | Disposable Docker-based VMs (create/enter/start/stop/rm/ls) |
|
||||
| entertainment | config | `pos-entertainment-config` | Show or edit the entertainment config (ENABLED auto-trigger list, weather location) |
|
||||
| entertainment | disable | `pos-entertainment-disable` | Disable a plugin's auto-trigger (remove it from ENABLED) |
|
||||
@@ -581,7 +583,7 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
||||
| `features/autostart.sh` | 50 | Boot-time feature (moved from `bin/`, flag-gated service) |
|
||||
| `features/usb-automount.sh` | 138 | USB automount feature (udev rule + flag-gated service) |
|
||||
<!-- GEN:START filetable -->
|
||||
| `bin/pos` | 292 | CLI dispatcher with smart arg matching + logging + category help |
|
||||
| `bin/pos` | 294 | CLI dispatcher with smart arg matching + logging + category help |
|
||||
| `bin/pos-ai-gemini` | 311 | Chat with Google Gemini (ask, chat, models, sessions) |
|
||||
| `bin/pos-communication-matrix-listener` | 568 | Matrix listener: map /command → bash, run them on room messages |
|
||||
| `bin/pos-communication-matrix-sender` | 224 | Send messages to a Matrix room via the client-server API (send, test, login) |
|
||||
@@ -591,6 +593,7 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
||||
| `bin/pos-docker-compose` | 366 | Docker Compose service manager (ls/up/down/restart/logs/update/config) |
|
||||
| `bin/pos-docker-health` | 107 | One-glance container health dashboard (exits 1 if unhealthy) |
|
||||
| `bin/pos-docker-ps` | 125 | Enhanced container overview (health, IPs, ports, uptime) |
|
||||
| `bin/pos-docker-stack` | 101 | Containers grouped by compose stack (project); standalone group; -a/--all includes stopped |
|
||||
| `bin/pos-docker-vbox` | 158 | Disposable Docker-based VMs (create/enter/start/stop/rm/ls) |
|
||||
| `bin/pos-entertainment-config` | 143 | Show or edit the entertainment config (ENABLED auto-trigger list, weather location) |
|
||||
| `bin/pos-entertainment-disable` | 32 | Disable a plugin's auto-trigger (remove it from ENABLED) |
|
||||
@@ -617,7 +620,7 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
||||
| `bin/pos-system-schedule` | 81 | Scheduled jobs: run a command on a timer; notify on threshold/change/error/always or silently |
|
||||
| `bin/pos-config` | 80 | Interactive editor for the tools' runtime config (reads # POS_CONFIG: registry) |
|
||||
| `bin/pos-tree` | 112 | Show the pos CLI command tree: categories, commands, and subcommands |
|
||||
| `completions/pos.bash` | 293 | Dynamic bash completion |
|
||||
| `completions/pos.bash` | 294 | Dynamic bash completion |
|
||||
<!-- GEN:END filetable -->
|
||||
| `apps/install.sh` | 171 | App install/uninstall picker/orchestrator |
|
||||
|
||||
@@ -641,6 +644,7 @@ Use conventional prefixes: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`
|
||||
| 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 Docker stack (grouped container) view | Edit `bin/pos-docker-stack` |
|
||||
| Modify vbox (Docker VM) logic | Edit `bin/pos-docker-vbox` |
|
||||
| Modify USB forwarding logic | Edit `bin/pos-share-usb-server` |
|
||||
| Modify aria2 download daemon / queue logic | Edit `bin/pos-network-download` |
|
||||
|
||||
@@ -143,8 +143,11 @@ Runs a persistent `aria2c` JSON-RPC daemon (`localhost:6800`) as a **systemd use
|
||||
|---------|------|---------|---------------|
|
||||
| `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 stack [-a]` | `bin/pos-docker-stack` | Containers grouped by compose stack (project); `-a` includes stopped; non-compose containers under `Standalone` | None. Requires Docker |
|
||||
| `pos docker compose …` | `bin/pos-docker-compose` | ScaleTail service manager | See [Docker Compose / ScaleTail](#docker-compose--scaletail) below |
|
||||
|
||||
**`pos docker stack [-a|--all]`** — containers grouped by their Docker Compose project (stack). Each stack is a section (project name, sorted) whose lines show container name, status, and port mappings (`-` when none — e.g. ScaleTail sidecar containers). Containers with no compose project land in a `Standalone` section at the end. Default shows running containers; `-a|--all` includes stopped/exited (like `docker ps -a`). Status is colored when output goes to a terminal: `Up*` green, `Exited*`/`Dead*`/`Created*` red, `Paused*`/`Restarting*` yellow. Ends with a summary line (`Stacks: N containers: N standalone: N`); exit 0 even when no containers exist.
|
||||
|
||||
#### Docker Compose / ScaleTail
|
||||
|
||||
**`pos docker compose ls`** — list available ScaleTail service templates.
|
||||
|
||||
+27
-1
@@ -1,13 +1,14 @@
|
||||
# How-To: `pos docker`
|
||||
|
||||
Manage self-hosted services, watch containers, and spin up disposable VMs.
|
||||
Tools: `compose`, `ps`, `health`, `vbox`.
|
||||
Tools: `compose`, `ps`, `health`, `stack`, `vbox`.
|
||||
|
||||
| Tool | What it does |
|
||||
|------|--------------|
|
||||
| `pos docker compose` | ScaleTail service manager (deploy/stop/logs/update) |
|
||||
| `pos docker ps` | Enhanced container overview (health, IPs, ports, uptime) |
|
||||
| `pos docker health` | One-glance health dashboard (exits 1 if unhealthy) |
|
||||
| `pos docker stack` | Containers grouped by compose stack (`-a` includes stopped) |
|
||||
| `pos docker vbox` | Disposable Docker containers as lightweight VMs |
|
||||
|
||||
---
|
||||
@@ -100,6 +101,31 @@ not unhealthy.
|
||||
|
||||
---
|
||||
|
||||
## `pos docker stack` — containers by compose stack
|
||||
|
||||
```bash
|
||||
pos docker stack # running containers grouped by compose project
|
||||
pos docker stack -a # include stopped/exited containers
|
||||
```
|
||||
|
||||
Grouped view of your compose deployments: every stack is a section with its
|
||||
services (container name, status, ports); containers started outside compose
|
||||
land in a `Standalone` group at the end. `-a` behaves like `docker ps -a` and
|
||||
also shows exited services (e.g. one-shot migration jobs that `docker ps`
|
||||
hides).
|
||||
|
||||
**Recipe:** after `pos docker compose up <svc>`, confirm it joined the right
|
||||
stack:
|
||||
|
||||
```bash
|
||||
pos docker stack | grep -A5 <svc>
|
||||
```
|
||||
|
||||
**Recipe:** spot what's restarting across all stacks at a glance (yellow
|
||||
`Restarting` / red `Exited` statuses stand out on a terminal).
|
||||
|
||||
---
|
||||
|
||||
## `pos docker vbox` — disposable VMs
|
||||
|
||||
Docker containers with a bind-mounted host dir so files persist even after the
|
||||
|
||||
@@ -148,6 +148,8 @@ EXAMPLES
|
||||
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 docker stack Containers grouped by compose stack
|
||||
pos docker stack -a Include stopped/exited containers
|
||||
|
||||
pos media mp3 <url> Download audio as MP3
|
||||
pos media mp4 <url> Download video as MP4
|
||||
|
||||
Executable
+101
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
# POS: docker stack — Containers grouped by compose stack (project); standalone group; -a/--all includes stopped
|
||||
# POS_FLAGS: -a --all
|
||||
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: pos docker stack [-a|--all]
|
||||
|
||||
Show running containers grouped by their Docker Compose stack (project).
|
||||
Non-compose containers (no stack) are listed under a "Standalone" group
|
||||
at the end.
|
||||
|
||||
Flags:
|
||||
-a, --all Include stopped/exited containers too (like docker ps -a)
|
||||
-h, --help Show this help
|
||||
|
||||
Exit codes:
|
||||
0 Success (also when no containers are found)
|
||||
|
||||
Examples:
|
||||
pos docker stack Grouped view of running containers
|
||||
pos docker stack -a Include stopped/exited containers
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
command -v docker &>/dev/null || err "docker not found — run 'install.sh --apps' and install Docker first"
|
||||
|
||||
ALL=0
|
||||
case "${1:-}" in
|
||||
-h|--help) usage ;;
|
||||
-a|--all) ALL=1 ;;
|
||||
esac
|
||||
|
||||
ps_args=()
|
||||
[ "$ALL" -eq 1 ] && ps_args+=(-a)
|
||||
ps_args+=(--format '{{.Names}}{{"\u001f"}}{{.Label "com.docker.compose.project"}}{{"\u001f"}}{{.Status}}{{"\u001f"}}{{.Ports}}')
|
||||
|
||||
if ! data="$(docker ps "${ps_args[@]}" 2>/dev/null)"; then
|
||||
err "docker daemon not reachable — is it running?"
|
||||
fi
|
||||
|
||||
if [ -z "$data" ]; then
|
||||
echo "No containers."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Compose projects (stack names), sorted; empty project label = standalone.
|
||||
projects="$(printf '%s\n' "$data" | awk -F'\x1f' 'NF>=2 && $2!="" {print $2}' | LC_ALL=C sort -u)"
|
||||
standalone_rows="$(printf '%s\n' "$data" | awk -F'\x1f' 'NF>=2 && $2==""')"
|
||||
|
||||
# Column width for aligned output (global so all stacks line up).
|
||||
width=0
|
||||
while IFS=$'\x1f' read -r name _proj _status _ports; do
|
||||
[ "${#name}" -gt "$width" ] && width="${#name}"
|
||||
done <<< "$data"
|
||||
|
||||
show_rows() {
|
||||
local rows="$1"
|
||||
[ -z "$rows" ] && return 0
|
||||
while IFS=$'\x1f' read -r name _proj status ports; do
|
||||
[ -z "$name" ] && continue
|
||||
case "$status" in
|
||||
Up*) sdisp="${GREEN}${status}${RESET}" ;;
|
||||
Exited*|Dead*|Created*) sdisp="${RED}${status}${RESET}" ;;
|
||||
Paused*|Restarting*) sdisp="${YELLOW}${status}${RESET}" ;;
|
||||
*) sdisp="$status" ;;
|
||||
esac
|
||||
printf " %-*s %b %s\n" "$width" "$name" "$sdisp" "${ports:--}"
|
||||
done <<< "$rows"
|
||||
}
|
||||
|
||||
stack_header() {
|
||||
local label="$1"
|
||||
local pad=$((46 - ${#label}))
|
||||
[ "$pad" -lt 1 ] && pad=1
|
||||
printf -- "── %s%s%s %s\n" "$CYAN" "$label" "$RESET" "$(printf '%*s' "$pad" '' | sed 's/ /─/g')"
|
||||
}
|
||||
|
||||
stacks=0
|
||||
while IFS= read -r proj; do
|
||||
[ -z "$proj" ] && continue
|
||||
stacks=$((stacks + 1))
|
||||
rows="$(printf '%s\n' "$data" | awk -F'\x1f' -v p="$proj" '$2==p')"
|
||||
stack_header "$proj"
|
||||
show_rows "$rows"
|
||||
echo
|
||||
done <<< "$projects"
|
||||
|
||||
if [ -n "$standalone_rows" ]; then
|
||||
stack_header "Standalone"
|
||||
show_rows "$standalone_rows"
|
||||
echo
|
||||
fi
|
||||
|
||||
containers="$(printf '%s\n' "$data" | grep -c . || true)"
|
||||
standalone="$(printf '%s\n' "$standalone_rows" | grep -c . || true)"
|
||||
printf '%*s\n' 60 '' | sed 's/ /─/g'
|
||||
echo "Stacks: $stacks containers: $containers standalone: $standalone"
|
||||
@@ -7,6 +7,7 @@ _pos_flags[ai-gemini]="--model --session --system"
|
||||
_pos_flags[communication-matrix-listener]="--enable --disable --status --run"
|
||||
_pos_flags[communication-telegram-listener]="--enable --disable --status --sync-commands --run"
|
||||
_pos_flags[communication-telegram-sender]="--type --caption --parse-mode --no-preview --token --chat-id --markdown"
|
||||
_pos_flags[docker-stack]="-a --all"
|
||||
_pos_flags[entertainment-send]="--print --markdown"
|
||||
_pos_flags[media-mp3]="--output --no-playlist --cookies --by-artist --dry-run"
|
||||
_pos_flags[media-mp4]="--format --best --worst --output --no-playlist --cookies --dry-run"
|
||||
|
||||
Reference in New Issue
Block a user