Sanitized mirror from private repository - 2026-04-24 08:27:14 UTC
This commit is contained in:
2409
docs/superpowers/plans/2026-04-04-homelab-dashboard.md
Normal file
2409
docs/superpowers/plans/2026-04-04-homelab-dashboard.md
Normal file
File diff suppressed because it is too large
Load Diff
410
docs/superpowers/plans/2026-04-24-pinchflat.md
Normal file
410
docs/superpowers/plans/2026-04-24-pinchflat.md
Normal file
@@ -0,0 +1,410 @@
|
||||
# Pinchflat Test Deployment Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Deploy Pinchflat (YouTube auto-archiver) on Atlantis via a hand-run docker compose on a feature branch, so the user can evaluate it before committing to production promotion.
|
||||
|
||||
**Architecture:** Single `ghcr.io/kieraneglin/pinchflat:latest` container, port-published on `8945` on Atlantis's LAN IP. Config on Atlantis NVMe (`/volume2/metadata/docker2/pinchflat/config`), downloads on SATA array (`/volume1/data/media/youtube`). No SSO, no reverse proxy, no Kuma monitor, no Portainer stack — purely a branch-based hand-run test.
|
||||
|
||||
**Tech Stack:** Docker Compose, Synology DSM (Atlantis NAS), SSH to `vish@atlantis`, git.
|
||||
|
||||
**Reference spec:** `docs/superpowers/specs/2026-04-24-pinchflat-design.md`
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
Files added to the repo on branch `feat/pinchflat`:
|
||||
|
||||
- `hosts/synology/atlantis/pinchflat/docker-compose.yml` — the single-service compose definition
|
||||
- `docs/services/individual/pinchflat.md` — brief stub (purpose, URL, test status), following repo convention at `docs/services/individual/<service>.md`
|
||||
|
||||
Non-tracked host state created on Atlantis:
|
||||
|
||||
- `/volume2/metadata/docker2/pinchflat/config/` — persistent SQLite + YAML config
|
||||
- `/volume1/data/media/youtube/` — download target folder
|
||||
- `/volume1/homes/vish/pinchflat-test/` — throwaway working copy of the branch
|
||||
|
||||
No modifications to existing files. No Portainer stack registration.
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Pre-flight checks on Atlantis
|
||||
|
||||
**Files:** None (verification only)
|
||||
|
||||
- [ ] **Step 1: Confirm port 8945 is free on Atlantis**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
ssh vish@atlantis 'ss -tlnp 2>/dev/null | grep -E ":8945\b" || echo "port 8945 free"'
|
||||
```
|
||||
|
||||
Expected: `port 8945 free`
|
||||
|
||||
If a process is listening on 8945, stop and re-plan — pick a different host port (e.g. 8946) and update the compose file in Task 2 accordingly.
|
||||
|
||||
- [ ] **Step 2: Confirm media root exists and ownership convention**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
ssh vish@atlantis 'ls -ld /volume1/data/media /volume2/metadata/docker2'
|
||||
```
|
||||
|
||||
Expected output includes two directories owned by a user account, with `/volume1/data/media` containing existing media subfolders (movies, tv, anime, etc.).
|
||||
|
||||
- [ ] **Step 3: Confirm Docker daemon is running on Atlantis**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
ssh vish@atlantis 'docker ps --format "table {{.Names}}\t{{.Status}}" | head -5'
|
||||
```
|
||||
|
||||
Expected: a table listing at least a handful of running containers (plex, sonarr, etc.). If Docker isn't responding, stop and investigate before proceeding.
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Create feature branch and compose file
|
||||
|
||||
**Files:**
|
||||
- Create: `hosts/synology/atlantis/pinchflat/docker-compose.yml`
|
||||
|
||||
- [ ] **Step 1: Create branch off main**
|
||||
|
||||
Run from the repo root `/home/homelab/organized/repos/homelab`:
|
||||
```bash
|
||||
git checkout main && git pull --ff-only && git checkout -b feat/pinchflat
|
||||
```
|
||||
|
||||
Expected: branch `feat/pinchflat` checked out, working tree clean except for the pre-existing untracked items (`.secrets.baseline` modifications, `data/expenses.csv`, `.superpowers/`, `backups/`).
|
||||
|
||||
- [ ] **Step 2: Create the compose directory**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
mkdir -p hosts/synology/atlantis/pinchflat
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Write `hosts/synology/atlantis/pinchflat/docker-compose.yml`**
|
||||
|
||||
Full file contents:
|
||||
|
||||
```yaml
|
||||
# Pinchflat - YouTube auto-archiver (test deployment)
|
||||
# Port: 8945
|
||||
# Docs: https://github.com/kieraneglin/pinchflat
|
||||
# Scope: lightweight evaluation on Atlantis. No SSO, no reverse proxy, no Kuma.
|
||||
# See: docs/superpowers/specs/2026-04-24-pinchflat-design.md
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
pinchflat:
|
||||
image: ghcr.io/kieraneglin/pinchflat:latest
|
||||
container_name: pinchflat
|
||||
|
||||
environment:
|
||||
- PUID=1029
|
||||
- PGID=100
|
||||
- TZ=America/Los_Angeles
|
||||
- UMASK=022
|
||||
|
||||
ports:
|
||||
- "8945:8945"
|
||||
|
||||
volumes:
|
||||
- /volume2/metadata/docker2/pinchflat/config:/config
|
||||
- /volume1/data/media/youtube:/downloads
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -qO /dev/null http://127.0.0.1:8945/ 2>/dev/null || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Validate the YAML**
|
||||
|
||||
Run from the repo root:
|
||||
```bash
|
||||
python3 -c "import yaml; yaml.safe_load(open('hosts/synology/atlantis/pinchflat/docker-compose.yml'))" && echo "YAML OK"
|
||||
```
|
||||
|
||||
Expected: `YAML OK`
|
||||
|
||||
If parsing fails, fix the indentation/syntax before proceeding. Do not use `sed` to edit — re-run the Write step with the full corrected file.
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Write the docs stub
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/services/individual/pinchflat.md`
|
||||
|
||||
- [ ] **Step 1: Write the doc stub**
|
||||
|
||||
Full file contents:
|
||||
|
||||
````markdown
|
||||
# Pinchflat
|
||||
|
||||
YouTube channel auto-archiver. Subscribes to channels, polls for new uploads, downloads via yt-dlp, stores locally with metadata/subtitles/chapters.
|
||||
|
||||
## Status
|
||||
|
||||
**Test deployment as of 2026-04-24.** Running on Atlantis via hand-run `docker compose` on branch `feat/pinchflat`. Not yet registered in Portainer, not yet behind Authentik / NPM / Kuma.
|
||||
|
||||
See `docs/superpowers/specs/2026-04-24-pinchflat-design.md` for design rationale and promotion path.
|
||||
|
||||
## Access
|
||||
|
||||
- **Web UI:** http://192.168.0.200:8945 (LAN only)
|
||||
- **Host:** Atlantis
|
||||
- **Image:** `ghcr.io/kieraneglin/pinchflat:latest`
|
||||
|
||||
## Paths on Atlantis
|
||||
|
||||
- **Compose:** `/volume1/homes/vish/pinchflat-test/hosts/synology/atlantis/pinchflat/docker-compose.yml` (during test)
|
||||
- **Config:** `/volume2/metadata/docker2/pinchflat/config`
|
||||
- **Downloads:** `/volume1/data/media/youtube/<Channel>/<YYYY-MM-DD> - <Title>.mkv`
|
||||
|
||||
## Runtime defaults (configure in web UI on first launch)
|
||||
|
||||
- Output template: `/downloads/{{ source_custom_name }}/{{ upload_yyyy_mm_dd }} - {{ title }}.{{ ext }}`
|
||||
- Resolution cap: 2160p (4K)
|
||||
- Container format: MKV (required for VP9/AV1 4K streams)
|
||||
- Thumbnails: on
|
||||
- Subtitles: on
|
||||
- Chapters: on
|
||||
- NFO files: off
|
||||
|
||||
## Operations
|
||||
|
||||
### Start / stop (during test phase)
|
||||
|
||||
```bash
|
||||
ssh vish@atlantis
|
||||
cd /volume1/homes/vish/pinchflat-test
|
||||
docker compose up -d # start
|
||||
docker compose logs -f # tail logs
|
||||
docker compose down # stop (data preserved)
|
||||
```
|
||||
|
||||
### Full teardown (abandon test)
|
||||
|
||||
```bash
|
||||
ssh vish@atlantis
|
||||
cd /volume1/homes/vish/pinchflat-test
|
||||
docker compose down -v
|
||||
sudo rm -rf /volume2/metadata/docker2/pinchflat /volume1/data/media/youtube
|
||||
cd ~ && rm -rf /volume1/homes/vish/pinchflat-test
|
||||
```
|
||||
|
||||
Then on the workstation:
|
||||
```bash
|
||||
git branch -D feat/pinchflat
|
||||
git push origin --delete feat/pinchflat # if pushed
|
||||
```
|
||||
|
||||
## Promotion to production (if keeping)
|
||||
|
||||
1. Merge `feat/pinchflat` → `main`.
|
||||
2. Register new Portainer GitOps stack pointing at `hosts/synology/atlantis/pinchflat/docker-compose.yml`.
|
||||
3. Stop the hand-run container, re-up via Portainer.
|
||||
4. Add NPM proxy host `pinchflat.vish.gg` + Authentik proxy provider.
|
||||
5. Add Kuma HTTP monitor against `http://192.168.0.200:8945`.
|
||||
6. Pin the image to a specific digest instead of `:latest`.
|
||||
7. Expand this doc with operational runbook (channel subscription process, troubleshooting, log locations).
|
||||
````
|
||||
|
||||
- [ ] **Step 2: Verify the doc renders as expected Markdown**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
head -5 docs/services/individual/pinchflat.md && echo "---" && wc -l docs/services/individual/pinchflat.md
|
||||
```
|
||||
|
||||
Expected: the first line is `# Pinchflat`, line count is around 60–70.
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Commit and push the branch
|
||||
|
||||
**Files:** Both files from Task 2 and Task 3.
|
||||
|
||||
- [ ] **Step 1: Stage the two new files only**
|
||||
|
||||
Explicitly avoid staging the pre-existing dirty working-tree items (`.secrets.baseline`, `data/expenses.csv`, `.superpowers/`, `backups/`).
|
||||
|
||||
Run:
|
||||
```bash
|
||||
git add hosts/synology/atlantis/pinchflat/docker-compose.yml docs/services/individual/pinchflat.md
|
||||
git status --short
|
||||
```
|
||||
|
||||
Expected: only the two new files appear in the "to be committed" section. Other items remain in the unstaged/untracked section untouched.
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
git commit -m "feat(pinchflat): add test deployment compose + docs stub
|
||||
|
||||
Hand-run docker compose on Atlantis port 8945 for evaluating Pinchflat as a
|
||||
YouTube auto-archiver. No SSO/NPM/Kuma/Portainer during the test phase.
|
||||
|
||||
See docs/superpowers/specs/2026-04-24-pinchflat-design.md for design and
|
||||
promotion path."
|
||||
```
|
||||
|
||||
Expected: a commit hook chain runs (trim whitespace, check yaml, yamllint, docker-compose syntax check, detect-secrets). All should pass. Per CLAUDE.md, never add `Co-Authored-By` lines.
|
||||
|
||||
If yamllint fails on the new compose file, read the error, fix the compose file inline with Edit (not sed), re-validate per Task 2 Step 4, then `git add` + `git commit --amend --no-edit` is acceptable here since the commit has not been pushed yet.
|
||||
|
||||
- [ ] **Step 3: Push the branch**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
git push -u origin feat/pinchflat
|
||||
```
|
||||
|
||||
Expected: branch published to Gitea. Output shows tracking established.
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Deploy on Atlantis
|
||||
|
||||
**Files:** None in the repo; creates host directories and launches the container.
|
||||
|
||||
- [ ] **Step 1: Create the config and download directories with correct ownership**
|
||||
|
||||
Pinchflat runs as `PUID=1029 PGID=100` (Synology `dockerlimited:users`). Pre-create the dirs so there's no first-boot permission confusion.
|
||||
|
||||
Run:
|
||||
```bash
|
||||
ssh vish@atlantis 'sudo mkdir -p /volume2/metadata/docker2/pinchflat/config /volume1/data/media/youtube && sudo chown -R 1029:100 /volume2/metadata/docker2/pinchflat /volume1/data/media/youtube && ls -ld /volume2/metadata/docker2/pinchflat /volume1/data/media/youtube'
|
||||
```
|
||||
|
||||
Expected: both directories exist and are owned by `dockerlimited:users` (which may display as `1029:users` depending on DSM's /etc/passwd).
|
||||
|
||||
- [ ] **Step 2: Clone the branch to a throwaway working copy on Atlantis**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
ssh vish@atlantis 'mkdir -p /volume1/homes/vish/pinchflat-test && cd /volume1/homes/vish/pinchflat-test && git clone --branch feat/pinchflat --depth 1 https://git.vish.gg/vish/homelab.git . && ls hosts/synology/atlantis/pinchflat/'
|
||||
```
|
||||
|
||||
Expected: a single `docker-compose.yml` listed under the pinchflat directory. If the git clone fails (auth or network), fall back to `scp` of the single compose file:
|
||||
```bash
|
||||
scp hosts/synology/atlantis/pinchflat/docker-compose.yml vish@atlantis:/volume1/homes/vish/pinchflat-test/docker-compose.yml
|
||||
```
|
||||
|
||||
(in which case the `docker compose` commands in later steps should be run directly from `/volume1/homes/vish/pinchflat-test/` rather than the nested path)
|
||||
|
||||
- [ ] **Step 3: Pull the image**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
ssh vish@atlantis 'cd /volume1/homes/vish/pinchflat-test/hosts/synology/atlantis/pinchflat && docker compose pull'
|
||||
```
|
||||
|
||||
Expected: image `ghcr.io/kieraneglin/pinchflat:latest` pulls successfully. If pull fails on auth (ghcr.io rate-limit), add `docker login ghcr.io` with a PAT and retry.
|
||||
|
||||
- [ ] **Step 4: Start the container**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
ssh vish@atlantis 'cd /volume1/homes/vish/pinchflat-test/hosts/synology/atlantis/pinchflat && docker compose up -d'
|
||||
```
|
||||
|
||||
Expected: `Container pinchflat Started`. No errors.
|
||||
|
||||
- [ ] **Step 5: Verify container status**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
ssh vish@atlantis 'docker ps --filter name=pinchflat --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"'
|
||||
```
|
||||
|
||||
Expected: single row showing `pinchflat Up N seconds (health: starting) 0.0.0.0:8945->8945/tcp, :::8945->8945/tcp`. Wait ~60 seconds after the first `up` so the healthcheck can transition from `starting` to `healthy`.
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Smoke test
|
||||
|
||||
**Files:** None (evaluation only)
|
||||
|
||||
- [ ] **Step 1: Confirm the web UI responds**
|
||||
|
||||
Run from the workstation:
|
||||
```bash
|
||||
curl -s -o /dev/null -w "%{http_code}\n" http://192.168.0.200:8945/
|
||||
```
|
||||
|
||||
Expected: `200` (Pinchflat serves its dashboard at `/`). If you get `000` (connection refused), wait 30 seconds and retry — the app takes a moment to bind after container start.
|
||||
|
||||
- [ ] **Step 2: Tail logs for any obvious errors**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
ssh vish@atlantis 'docker logs pinchflat --tail 50 2>&1 | grep -iE "error|fatal|panic" | head -20 || echo "no errors in recent logs"'
|
||||
```
|
||||
|
||||
Expected: `no errors in recent logs`, or if any errors appear, assess whether they're benign (e.g. migration notices) vs blocking.
|
||||
|
||||
- [ ] **Step 3: Check container healthcheck transitioned to healthy**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
ssh vish@atlantis 'docker inspect pinchflat --format "{{.State.Health.Status}}"'
|
||||
```
|
||||
|
||||
Expected: `healthy`. If still `starting` after 2 minutes, check logs for why the web server isn't responding. If `unhealthy`, something is wrong — stop and debug before handing off.
|
||||
|
||||
- [ ] **Step 4: Confirm config and download volumes wrote correctly**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
ssh vish@atlantis 'ls -la /volume2/metadata/docker2/pinchflat/config/ | head -10 && echo "---" && ls -la /volume1/data/media/youtube/'
|
||||
```
|
||||
|
||||
Expected: the config dir now contains Pinchflat's initial files (e.g. database file, YAML config). The downloads dir may still be empty — that's fine, it only populates after the user subscribes to a channel.
|
||||
|
||||
- [ ] **Step 5: Hand off to user for UI walk-through**
|
||||
|
||||
Report to the user:
|
||||
- URL: `http://192.168.0.200:8945`
|
||||
- Container state: running + healthy
|
||||
- Next step is user-driven: open the URL, walk through first-run setup, apply the defaults from §4 of the spec (output template, 4K cap, MKV container, thumbnails/subtitles/chapters on, NFO off), subscribe to 2–5 test channels.
|
||||
|
||||
Do NOT mark the plan as complete at this step. The user drives the evaluation window; the plan is complete when the user makes the keep/drop decision.
|
||||
|
||||
---
|
||||
|
||||
## Task 7 (deferred): Keep-or-drop decision
|
||||
|
||||
**Trigger:** User signals "keep" or "drop" after evaluation window.
|
||||
|
||||
**If KEEP:**
|
||||
- Open follow-up plan for promotion (register Portainer GitOps stack, add NPM + Authentik + Kuma, pin image digest, expand docs).
|
||||
- Out of scope for this plan.
|
||||
|
||||
**If DROP:**
|
||||
- [ ] Run the teardown block from `docs/services/individual/pinchflat.md` (Full teardown section).
|
||||
- [ ] Delete the remote branch: `git push origin --delete feat/pinchflat`.
|
||||
- [ ] Delete the local branch: `git branch -D feat/pinchflat`.
|
||||
- [ ] Optionally delete the two repo files with a follow-up commit on main, or leave the branch deleted and nothing ever landed on main — either is fine.
|
||||
|
||||
---
|
||||
|
||||
## Self-review notes
|
||||
|
||||
- **Spec coverage:** Every spec section (architecture, components, storage, runtime defaults, deployment workflow, error handling, testing, rollback) maps to a task or is explicitly deferred. ✓
|
||||
- **Placeholder scan:** No TBDs. No "similar to Task N" shortcuts. Every compose field and command is literal. ✓
|
||||
- **Type consistency:** Paths match across tasks (`/volume1/data/media/youtube`, `/volume2/metadata/docker2/pinchflat/config`, `/volume1/homes/vish/pinchflat-test`, port `8945`). Ownership `1029:100` consistent. ✓
|
||||
- **Known fuzziness:** "60-70 lines" for the doc stub is approximate; the exact line count depends on final whitespace. Not a blocker.
|
||||
Reference in New Issue
Block a user