7 docs in /testing/ — institutional memory after 6+ regressions in
24-48h on the v6 theme. Read before any edit.
README.md — index + quickstart
THEMING.md — safe-edit checklist + layer/specificity tables
ERROR-PATTERNS.md — 12 cataloged patterns (Symptom/Cause/Diag/Fix/Prev)
HEADLESS-PROBE.md — 11 playwright recipes (md5 chain, darkPct,
ancestor bg sample, dropdown listItem probe)
ROLLBACK.md — 8 emergency revert recipes (overlay, branding,
encoding, full-from-repo, dev-clone-prod,
git-revert, pw-reset, bind-mount inode-swap)
SMOKE-TEST.md — manual + headless verify checklist
DEPLOY.md — dev → prod promotion workflow with backup +
chown root + restart inode-swap
Empty subdirs: snipUSER-Es/, recipes/, incidents/ (post-mortems land here).
Goal: stop reinventing the same fixes. Catalog every error class,
codify the recovery, build a skills folder for future ARRFLIX work.
74 lines
2.8 KiB
Markdown
74 lines
2.8 KiB
Markdown
# DEPLOY — dev → prod promotion workflow
|
|
|
|
> Strict order. Skip a step → break prod.
|
|
|
|
## Pre-flight
|
|
|
|
- [ ] testing/SMOKE-TEST.md passed on dev
|
|
- [ ] You can name the change in one sentence
|
|
- [ ] You have a rollback target (the current prod md5 — capture before deploy)
|
|
|
|
## Step 1 — capture current prod md5 (rollback anchor)
|
|
|
|
```bash
|
|
PROD_BEFORE=$(ssh user@nullstone 'md5sum /opt/docker/jellyfin/web-overrides/index.html' | awk '{print $1}')
|
|
echo "rollback anchor: $PROD_BEFORE"
|
|
```
|
|
|
|
If anything goes wrong: `cp /opt/docker/jellyfin/web-overrides/index.html.bak.<latest> /opt/docker/jellyfin/web-overrides/index.html` + restart.
|
|
|
|
## Step 2 — backup prod overlay + branding
|
|
|
|
```bash
|
|
ssh user@nullstone 'set -e
|
|
TS=$(date +%s)
|
|
docker run --rm --userns=host -v /opt/docker/jellyfin/web-overrides:/d:rw alpine cp /d/index.html /d/index.html.bak.deploy.$TS
|
|
docker run --rm --userns=host -v /home/docker/jellyfin/config/config:/d:rw alpine cp /d/branding.xml /d/branding.xml.bak.deploy.$TS
|
|
'
|
|
```
|
|
|
|
## Step 3 — copy dev overlay to prod via docker shim (root-owned dest)
|
|
|
|
```bash
|
|
ssh user@nullstone 'docker run --rm --userns=host -v /opt/docker/jellyfin-dev/web-overrides:/dev:ro -v /opt/docker/jellyfin/web-overrides:/prod:rw alpine sh -c "cp /dev/index-dev.html /prod/index.html && chown root:root /prod/index.html && md5sum /prod/index.html"'
|
|
```
|
|
|
|
## Step 4 — bind-mount inode swap requires restart
|
|
|
|
```bash
|
|
ssh user@nullstone 'docker restart jellyfin && sleep 14'
|
|
```
|
|
|
|
## Step 5 — verify
|
|
|
|
```bash
|
|
ssh user@nullstone 'docker exec jellyfin md5sum /jellyfin/jellyfin-web/index.html' # should match dev's md5
|
|
ssh user@nullstone 'docker exec jellyfin curl -s http://127.0.0.1:8096/web/index.html | grep -c ARRFLIX-MIDDLE-THEME-BEGIN' # = 1
|
|
ssh user@nullstone 'docker exec jellyfin curl -s http://127.0.0.1:8096/Branding/Css.css | wc -c' # ~36000
|
|
ssh user@nullstone 'docker ps --format "{{.Names}} {{.Status}}" | grep "^jellyfin "' # healthy
|
|
```
|
|
|
|
## Step 6 — manual smoke on prod
|
|
|
|
Open arrflix.s8n.ru in incognito. Run testing/SMOKE-TEST.md manual checklist.
|
|
|
|
## Step 7 — commit + push to repo
|
|
|
|
```bash
|
|
cd /tmp/arrflix-recon
|
|
cp web-overrides/index.html snapshots/2026-05-09-v6-stable/index.html # update snapshot
|
|
git add bin/inject-middle-theme.py web-overrides/index.html snapshots/2026-05-09-v6-stable/index.html docs/ # whatever changed
|
|
git -c user.name=s8n -c user.email=admin@s8n.ru commit -m "<one-line summary>"
|
|
git push origin main
|
|
```
|
|
|
|
## If verify fails
|
|
|
|
Run `testing/ROLLBACK.md` immediately. Don't try to fix forward on prod.
|
|
|
|
## Common deploy gotchas
|
|
|
|
- Forget `docker restart jellyfin` after `cp` → bind-mount inode swap → container serves stale
|
|
- Forget `chown root:root` → user can't write but root needs to own per host config
|
|
- Forget snapshot bump → next "what's deployed" question gets wrong answer
|
|
- Forget commit → repo drifts from prod (per doc 26 INC1 root cause)
|