Files
homelab-optimized/docs/superpowers/plans/2026-04-24-pinchflat.md
Gitea Mirror Bot 48bfce60e7
Some checks failed
Documentation / Build Docusaurus (push) Failing after 5m7s
Documentation / Deploy to GitHub Pages (push) Has been skipped
Sanitized mirror from private repository - 2026-04-24 08:27:14 UTC
2026-04-24 08:27:14 +00:00

411 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 6070.
---
## 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 25 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.