# 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/.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// - .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.