15 KiB
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 definitiondocs/services/individual/pinchflat.md— brief stub (purpose, URL, test status), following repo convention atdocs/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:
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:
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:
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:
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:
mkdir -p hosts/synology/atlantis/pinchflat
- Step 3: Write
hosts/synology/atlantis/pinchflat/docker-compose.yml
Full file contents:
# 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:
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:
# 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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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, port8945). Ownership1029:100consistent. ✓ - Known fuzziness: "60-70 lines" for the doc stub is approximate; the exact line count depends on final whitespace. Not a blocker.