Compare commits
10
Commits
d028fb4e1b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53d99c7cd1 | ||
|
|
f7c30c0962 | ||
|
|
7f3c275516 | ||
|
|
45ab885834 | ||
|
|
966063d34d | ||
|
|
18f26634b6 | ||
|
|
ccbce57ab9 | ||
|
|
de759345ec | ||
|
|
fea3a6b29f | ||
|
|
942fefacb8 |
@@ -111,7 +111,8 @@ GITHUB_RUNNER_ACCESS_TOKEN=github_pat_11ACRHQAI0q3n1svrl9jmT_ai2bQlXWSxY8HJjEI6q
|
||||
# Used by StackCreateDockerStandaloneRepository and StackGitRedeploy.
|
||||
# Pass as RepositoryAuthorizationType:0 (Basic), RepositoryUsername=ginnoir.
|
||||
# Runner PAT above does NOT work for this — different scopes.
|
||||
GITHUB_STACKS_PAT=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
|
||||
# Gitea PAT for Portainer git stacks (env var name kept from GitHub era)
|
||||
GITHUB_STACKS_PAT=54ea93904439c2919f1d8dab1ba545e54a2a3e86
|
||||
|
||||
# =============================================================
|
||||
# DEV STACK
|
||||
|
||||
@@ -17,6 +17,15 @@ jobs:
|
||||
|
||||
- name: Push Caddyfile and reload Caddy
|
||||
run: |
|
||||
# Job containers do NOT inherit the runner's /config/caddy bind.
|
||||
# Docker-from-Docker with a host bind is the reliable path: the
|
||||
# volume source is resolved on valhalla, not inside the job container.
|
||||
# (Direct `cp /config/caddy/...` only works on the runner container
|
||||
# itself, which is not where this step runs.)
|
||||
apt-get update -qq && apt-get install -y -qq docker.io
|
||||
cp Caddyfile /config/caddy/Caddyfile
|
||||
docker run --rm \
|
||||
-v /config/caddy:/dest \
|
||||
-v "$PWD/Caddyfile:/src/Caddyfile:ro" \
|
||||
alpine:3.20 \
|
||||
cp /src/Caddyfile /dest/Caddyfile
|
||||
docker exec caddy caddy reload --config /etc/caddy/Caddyfile
|
||||
|
||||
@@ -177,8 +177,11 @@ These run on valhalla but are managed outside of Portainer/Docker.
|
||||
- **Version:** 1.43.2 (Watchtower doesn't touch it — update via `pacman -Syu plex-media-server` or the Plex UI).
|
||||
- **Service:** `systemctl {start,stop,restart,status} plexmediaserver`
|
||||
- **Binary:** `/usr/lib/plexmediaserver/`
|
||||
- **App data / metadata / DB:** `/var/lib/plexmediaserver/Library/Application Support/Plex Media Server/`
|
||||
- **Media libraries:** `/storage1/Media/{Movies,TV,Anime}` — same ZFS pool bind-mounted into the *arr containers.
|
||||
- **App data / metadata / DB:** `/var/lib/plex/Plex Media Server/` — note `/var/lib/plex` is a
|
||||
**symlink to `/storage1/labdata/plex`**, so Plex's 383G of app data lives on the ZFS array, not
|
||||
the root disk. (`/var/lib/plexmediaserver/` does not exist.)
|
||||
- **Media libraries:** `/storage1/{Movies,TV,Anime}` — `/storage1/Media/*` are symlinks to these.
|
||||
Same ZFS pool bind-mounted into the *arr containers.
|
||||
- **Tautulli** (`tautulli` container in the media stack) reads PMS logs from `/storage1/Media` and connects to Plex over the LAN. It is **not** behind Authentik — Plex login is its own auth.
|
||||
- **Caddy:** Plex is not reverse-proxied through Caddy (Plex handles its own relay/direct connections). No `plex.ginnoir.com` block exists.
|
||||
|
||||
|
||||
@@ -133,6 +133,17 @@ requests.ginnoir.com {
|
||||
reverse_proxy seerr:5055
|
||||
}
|
||||
|
||||
# =============================================================
|
||||
# STREAMING — public (replaces Plex's own remote access)
|
||||
# =============================================================
|
||||
jellyfin.ginnoir.com {
|
||||
reverse_proxy jellyfin:8096
|
||||
}
|
||||
|
||||
wizarr.ginnoir.com {
|
||||
reverse_proxy wizarr:5690
|
||||
}
|
||||
|
||||
# =============================================================
|
||||
# MEDIA MANAGEMENT — internal only
|
||||
# =============================================================
|
||||
@@ -204,6 +215,17 @@ tautulli.ginnoir.com {
|
||||
}
|
||||
}
|
||||
|
||||
jellystat.ginnoir.com {
|
||||
route {
|
||||
import internal_gate
|
||||
import authentik_outpost
|
||||
handle {
|
||||
import authentik_forward_auth
|
||||
reverse_proxy jellystat:3000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================
|
||||
# DOWNLOAD CLIENTS — internal only
|
||||
# =============================================================
|
||||
|
||||
+4
-1
@@ -41,8 +41,11 @@ if ($EnvFile) {
|
||||
}
|
||||
|
||||
if ($Caddy) {
|
||||
# /config/caddy is root-owned on valhalla — scp directly fails with
|
||||
# "Permission denied". Stage via /tmp and sudo-install.
|
||||
Write-Host "Pushing Caddyfile ..."
|
||||
scp $caddyLocal "${server}:/config/caddy/Caddyfile"
|
||||
scp $caddyLocal "${server}:/tmp/Caddyfile.new"
|
||||
ssh $server "sudo cp /tmp/Caddyfile.new /config/caddy/Caddyfile && sudo chown root:root /config/caddy/Caddyfile && rm -f /tmp/Caddyfile.new"
|
||||
Write-Host "Reloading Caddy ..."
|
||||
ssh $server "docker exec caddy caddy reload --config /etc/caddy/Caddyfile"
|
||||
}
|
||||
|
||||
+12
-5
@@ -32,21 +32,28 @@
|
||||
<DT><A HREF="https://requests.ginnoir.com">requests</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>MEDIA MANAGEMENT (internal)</H3>
|
||||
<DT><H3>STREAMING — public (replaces Plex's own remote access) (public)</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="https://jellyfin.ginnoir.com">jellyfin</A>
|
||||
<DT><A HREF="https://wizarr.ginnoir.com">wizarr</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>MEDIA MANAGEMENT (public)</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="https://sonarr.ginnoir.com">sonarr</A>
|
||||
<DT><A HREF="https://radarr.ginnoir.com">radarr</A>
|
||||
<DT><A HREF="https://bazarr.ginnoir.com">bazarr</A>
|
||||
<DT><A HREF="https://prowlarr.ginnoir.com">prowlarr</A>
|
||||
<DT><A HREF="https://tautulli.ginnoir.com">tautulli</A>
|
||||
<DT><A HREF="https://jellystat.ginnoir.com">jellystat</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>DOWNLOAD CLIENTS</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="https://qbittorrent.ginnoir.com">qbittorrent [internal]</A>
|
||||
<DT><A HREF="https://nzbget.ginnoir.com">nzbget [internal]</A>
|
||||
<DT><A HREF="https://whisparr.ginnoir.com">whisparr [internal]</A>
|
||||
<DT><A HREF="https://stash.ginnoir.com">stash [internal]</A>
|
||||
<DT><A HREF="https://qbittorrent.ginnoir.com">qbittorrent</A>
|
||||
<DT><A HREF="https://nzbget.ginnoir.com">nzbget</A>
|
||||
<DT><A HREF="https://whisparr.ginnoir.com">whisparr</A>
|
||||
<DT><A HREF="https://stash.ginnoir.com">stash</A>
|
||||
<DT><A HREF="https://roms.ginnoir.com">roms</A>
|
||||
<DT><A HREF="https://romhacks.ginnoir.com">romhacks</A>
|
||||
<DT><A HREF="https://jd.ginnoir.com">jd [internal]</A>
|
||||
|
||||
@@ -109,5 +109,12 @@
|
||||
<DT><A HREF="http://valhalla:8997">romm :8997</A>
|
||||
</DL><p>
|
||||
|
||||
<DT><H3>Streaming</H3>
|
||||
<DL><p>
|
||||
<DT><A HREF="http://valhalla:8096">jellyfin</A>
|
||||
<DT><A HREF="http://valhalla:3009">jellystat :3009</A>
|
||||
<DT><A HREF="http://valhalla:5690">wizarr</A>
|
||||
</DL><p>
|
||||
|
||||
</DL><p>
|
||||
</DL><p>
|
||||
|
||||
@@ -70,47 +70,44 @@ lives on it. This phase is a hard gate.
|
||||
|
||||
**Files:** none — live host operation.
|
||||
|
||||
- [ ] **Step 1: Record the baseline**
|
||||
- [x] **Step 1: Record the baseline**
|
||||
|
||||
```bash
|
||||
ssh -o BatchMode=yes ginnoir@valhalla "df -h / | tail -1; docker system df"
|
||||
```
|
||||
|
||||
Write the output into the task notes below. Expected at plan time: `/dev/sda2 194G 179G 5.7G 97%`
|
||||
and roughly `Images 124 / 123.5GB / 51.47GB reclaimable`.
|
||||
**Baseline (2026-08-05 execution):** `/dev/sda2 194G 179G 5.6G 97% /` —
|
||||
`Images 124 / 123.5GB / 51.47GB reclaimable`, Containers 77/71 Up.
|
||||
|
||||
- [ ] **Step 2: Verify no container depends on what you are about to remove**
|
||||
- [x] **Step 2: Verify no container depends on what you are about to remove**
|
||||
|
||||
```bash
|
||||
ssh -o BatchMode=yes ginnoir@valhalla "docker ps -a --filter status=exited --format '{{.Names}}\t{{.Image}}'"
|
||||
```
|
||||
|
||||
Expected: a short list, or empty. Any **exited container you still want** keeps its image, so
|
||||
note them. `docker image prune -a` removes only images not referenced by *any* container,
|
||||
including stopped ones — it will not break a stopped-but-wanted service.
|
||||
Exited only: `plane_migrator` (plane-backend), `plane_create_bucket` (minio/mc) — one-shot helpers; images kept via stopped containers.
|
||||
|
||||
- [ ] **Step 3: Prune**
|
||||
- [x] **Step 3: Prune**
|
||||
|
||||
```bash
|
||||
ssh -o BatchMode=yes ginnoir@valhalla "docker image prune -af && docker builder prune -f"
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Verify space was reclaimed**
|
||||
- [x] **Step 4: Verify space was reclaimed**
|
||||
|
||||
```bash
|
||||
ssh -o BatchMode=yes ginnoir@valhalla "df -h / | tail -1"
|
||||
```
|
||||
|
||||
Expected: `Avail` is now **at least 40G** and `Use%` is at or below **80%**.
|
||||
If it is not, stop. Do not proceed to Task 2 — investigate what is holding the space.
|
||||
**After prune:** `/dev/sda2 194G 124G 61G 68% /` — 61G avail, 68% used. Gate met.
|
||||
|
||||
- [ ] **Step 5: Confirm all containers are still running**
|
||||
- [x] **Step 5: Confirm all containers are still running**
|
||||
|
||||
```bash
|
||||
ssh -o BatchMode=yes ginnoir@valhalla "docker ps --format '{{.Names}}\t{{.Status}}' | grep -c Up"
|
||||
```
|
||||
|
||||
Expected: **71 or more** (the count before the prune). Watchtower re-pulls anything it needs.
|
||||
**71 Up** — unchanged.
|
||||
|
||||
---
|
||||
|
||||
@@ -118,7 +115,7 @@ Expected: **71 or more** (the count before the prune). Watchtower re-pulls anyth
|
||||
|
||||
**Files:** none — live host operation.
|
||||
|
||||
- [ ] **Step 1: Find the top consumers outside Docker**
|
||||
- [x] **Step 1: Find the top consumers outside Docker**
|
||||
|
||||
```bash
|
||||
ssh -o BatchMode=yes ginnoir@valhalla "sudo du -xh --max-depth=2 / 2>/dev/null | sort -h | tail -25"
|
||||
@@ -126,7 +123,7 @@ ssh -o BatchMode=yes ginnoir@valhalla "sudo du -xh --max-depth=2 / 2>/dev/null |
|
||||
|
||||
`-x` keeps it on `/dev/sda2` and stops it wandering into the 94T of `/storage1`.
|
||||
|
||||
- [ ] **Step 2: Check the Docker containerd image store specifically**
|
||||
- [x] **Step 2: Check the Docker containerd image store specifically**
|
||||
|
||||
```bash
|
||||
ssh -o BatchMode=yes ginnoir@valhalla "sudo du -sh /var/lib/docker /var/lib/containerd /var/log 2>/dev/null"
|
||||
@@ -135,13 +132,24 @@ ssh -o BatchMode=yes ginnoir@valhalla "sudo du -sh /var/lib/docker /var/lib/cont
|
||||
This is the directory that caused the 2026-07-07 outage (all Postgres instances crash-looped on
|
||||
"No space left on device"). It should now be well under 60G.
|
||||
|
||||
- [ ] **Step 3: Record findings**
|
||||
- [x] **Step 3: Record findings**
|
||||
|
||||
Append the output to this task as a comment. You are looking for anything **structurally
|
||||
growing** — not one-off large files. If you find a second unbounded growth source, add a task
|
||||
to this plan to bound it before continuing.
|
||||
**Audit findings (2026-08-05, post-prune):**
|
||||
|
||||
- [ ] **Step 4: Commit the findings**
|
||||
| Path | Size | Notes |
|
||||
|---|---|---|
|
||||
| `/var/lib/containerd` | 61G | Image store — primary consumer; now healthy post-prune |
|
||||
| `/var/lib/docker` | 54G (top-level) / ~9G (du -x under /var/lib) | Overlay/metadata; paired with containerd |
|
||||
| `/config` | 20G | App configs; largest: kopia 5.4G, nextcloud 2.4G, owncloud 2.0G |
|
||||
| `/home/ginnoir` | 9.6G | `.cache` 4.9G, `.hermes` 2.6G — bounded personal tooling |
|
||||
| `/usr` | 9.6G | OS packages |
|
||||
| `/var/lib/plex-local` | 6.4G | Leftover local Plex data (symlink target is on /storage1) |
|
||||
| `/var/log` | 3.0G | Acceptable |
|
||||
|
||||
No second unbounded growth source found beyond Docker images (already pruned + Task 3 alerts).
|
||||
`plex-local` 6.4G is a one-off reclaim candidate at decommission, not structural growth.
|
||||
|
||||
- [x] **Step 4: Commit the findings**
|
||||
|
||||
```bash
|
||||
git add docs/superpowers/plans/2026-08-05-jellyfin-migration.md
|
||||
@@ -164,115 +172,35 @@ alerts — so this catches both "disk full" and "monitoring broke".
|
||||
> These are host-managed files, not repo files. valhalla has **no crontab** — systemd timers only
|
||||
> (this matches the existing `docker-prune.timer`).
|
||||
|
||||
- [ ] **Step 1: Create the Uptime Kuma push monitor and capture its token**
|
||||
- [x] **Step 1: Create the Uptime Kuma push monitor and capture its token**
|
||||
|
||||
In the Uptime Kuma UI at `https://uptime.ginnoir.com`:
|
||||
1. **Add New Monitor**
|
||||
2. Monitor Type: **Push**
|
||||
3. Friendly Name: `valhalla root disk`
|
||||
4. Heartbeat Interval: `300` seconds
|
||||
5. Retries: `1`
|
||||
6. Notifications: tick the existing **ntfy** notification
|
||||
7. Save
|
||||
|
||||
The monitor page then shows a Push URL of the form
|
||||
`https://uptime.ginnoir.com/api/push/XXXXXXXXXX`. **Copy the token** (the `XXXXXXXXXX` part).
|
||||
|
||||
Record it here before continuing — the next step needs it:
|
||||
Created via MCP as monitor id **70** (`valhalla root disk`, push, interval 300, maxretries 1,
|
||||
ntfy notification). MCP omitted `push_token`; set directly in Kuma SQLite.
|
||||
|
||||
```
|
||||
PUSH_TOKEN = ____________________
|
||||
PUSH_TOKEN = hVUvMeyNRZw8IO4D9fF6Hz5a
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Write the check script on the host**
|
||||
- [x] **Step 2: Write the check script on the host**
|
||||
|
||||
Substitute your real token for `PUSH_TOKEN_HERE`.
|
||||
Installed at `/usr/local/bin/root-disk-check.sh` with the token above.
|
||||
|
||||
```bash
|
||||
ssh -o BatchMode=yes ginnoir@valhalla "sudo tee /usr/local/bin/root-disk-check.sh >/dev/null <<'EOF'
|
||||
#!/bin/sh
|
||||
# Reports root-disk headroom to Uptime Kuma. Alerts below MIN_FREE_PCT.
|
||||
set -eu
|
||||
MIN_FREE_PCT=15
|
||||
TOKEN=PUSH_TOKEN_HERE
|
||||
- [x] **Step 3: Run it once by hand and verify Kuma turns green**
|
||||
|
||||
USED_PCT=\$(df --output=pcent / | tail -1 | tr -dc '0-9')
|
||||
FREE_PCT=\$((100 - USED_PCT))
|
||||
AVAIL=\$(df -h --output=avail / | tail -1 | tr -d ' ')
|
||||
Verified: `OK`, heartbeat Up with `root 61G free (32%)`.
|
||||
|
||||
if [ \"\$FREE_PCT\" -ge \"\$MIN_FREE_PCT\" ]; then
|
||||
STATUS=up
|
||||
else
|
||||
STATUS=down
|
||||
fi
|
||||
- [x] **Step 4: Create the systemd service and timer**
|
||||
|
||||
curl -fsS --max-time 10 \\
|
||||
\"https://uptime.ginnoir.com/api/push/\${TOKEN}?status=\${STATUS}&msg=root%20\${AVAIL}%20free%20(\${FREE_PCT}%25)\" \\
|
||||
>/dev/null
|
||||
EOF
|
||||
sudo chmod 755 /usr/local/bin/root-disk-check.sh"
|
||||
```
|
||||
Enabled `root-disk-check.timer` (OnUnitActiveSec=5min).
|
||||
|
||||
- [ ] **Step 3: Run it once by hand and verify Kuma turns green**
|
||||
- [x] **Step 5: Verify the timer is scheduled**
|
||||
|
||||
```bash
|
||||
ssh -o BatchMode=yes ginnoir@valhalla "sudo /usr/local/bin/root-disk-check.sh && echo OK"
|
||||
```
|
||||
`NEXT` under 5 minutes; timer active.
|
||||
|
||||
Expected: `OK`, and the `valhalla root disk` monitor in Kuma flips to **Up** within a few
|
||||
seconds with a message like `root 45G free (23%)`.
|
||||
- [x] **Step 6: Verify the alert path actually fires**
|
||||
|
||||
If it prints a curl error instead, the token is wrong — fix it before continuing.
|
||||
|
||||
- [ ] **Step 4: Create the systemd service and timer**
|
||||
|
||||
```bash
|
||||
ssh -o BatchMode=yes ginnoir@valhalla "sudo tee /etc/systemd/system/root-disk-check.service >/dev/null <<'EOF'
|
||||
[Unit]
|
||||
Description=Report root disk headroom to Uptime Kuma
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/bin/root-disk-check.sh
|
||||
EOF
|
||||
sudo tee /etc/systemd/system/root-disk-check.timer >/dev/null <<'EOF'
|
||||
[Unit]
|
||||
Description=Run root-disk-check every 5 minutes
|
||||
|
||||
[Timer]
|
||||
OnBootSec=2min
|
||||
OnUnitActiveSec=5min
|
||||
AccuracySec=30s
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
EOF
|
||||
sudo systemctl daemon-reload && sudo systemctl enable --now root-disk-check.timer"
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Verify the timer is scheduled**
|
||||
|
||||
```bash
|
||||
ssh -o BatchMode=yes ginnoir@valhalla "systemctl list-timers root-disk-check.timer --no-pager"
|
||||
```
|
||||
|
||||
Expected: one row showing `root-disk-check.timer` with a `NEXT` time under 5 minutes away.
|
||||
|
||||
- [ ] **Step 6: Verify the alert path actually fires**
|
||||
|
||||
Temporarily raise the threshold so the script reports `down`:
|
||||
|
||||
```bash
|
||||
ssh -o BatchMode=yes ginnoir@valhalla "sudo sed -i 's/^MIN_FREE_PCT=15/MIN_FREE_PCT=99/' /usr/local/bin/root-disk-check.sh && sudo /usr/local/bin/root-disk-check.sh"
|
||||
```
|
||||
|
||||
Expected: the Kuma monitor flips to **Down** and an ntfy notification arrives. Then restore it:
|
||||
|
||||
```bash
|
||||
ssh -o BatchMode=yes ginnoir@valhalla "sudo sed -i 's/^MIN_FREE_PCT=99/MIN_FREE_PCT=15/' /usr/local/bin/root-disk-check.sh && sudo /usr/local/bin/root-disk-check.sh"
|
||||
```
|
||||
|
||||
Expected: monitor returns to **Up**. An untested alert is not an alert.
|
||||
Raised `MIN_FREE_PCT=99` → monitor status 0 (Down) + ntfy
|
||||
`valhalla root disk Down [Uptime-Kuma]`. Restored to 15 → Up.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
# streaming stack — Jellyfin and its satellites.
|
||||
#
|
||||
# Storage split is deliberate: SQLite config on the root SSD, all bulk data
|
||||
# (metadata, trickplay, cache, transcodes) on /storage1. The root disk is only
|
||||
# 194G and has filled twice; nothing here may grow unbounded on it.
|
||||
#
|
||||
# Jellyfin 10.11 (linuxserver) keeps metadata at /config/data/metadata — NOT
|
||||
# /config/metadata. Trickplay is at /config/data/trickplay and is not
|
||||
# relocatable from the UI. Transcodes are configured to /transcode.
|
||||
#
|
||||
# Transcodes are disk-backed, not tmpfs: jellyfin#16608 means Remux and
|
||||
# DirectStream jobs finish before the segment cleaner's first check, orphaning
|
||||
# segments. Plex already writes transcodes to virtiofs at a 48% transcode rate.
|
||||
#
|
||||
# Env convention: every service gets stack.env via env_file; var names there are
|
||||
# exactly what the containers read. No ${VAR} interpolation, no Portainer UI
|
||||
# env vars.
|
||||
|
||||
services:
|
||||
jellyfin:
|
||||
container_name: jellyfin
|
||||
image: lscr.io/linuxserver/jellyfin:latest
|
||||
restart: unless-stopped
|
||||
networks: [streaming, edge]
|
||||
env_file:
|
||||
- stack.env
|
||||
devices:
|
||||
- nvidia.com/gpu=0
|
||||
environment:
|
||||
- JELLYFIN_PublishedServerUrl=https://jellyfin.ginnoir.com
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /config/jellyfin:/config
|
||||
- /storage1/labdata/jellyfin/metadata:/config/data/metadata
|
||||
- /storage1/labdata/jellyfin/cache:/config/cache
|
||||
- /storage1/labdata/jellyfin/trickplay:/config/data/trickplay
|
||||
- /storage1/labdata/jellyfin/transcodes:/transcode
|
||||
- /storage1/Movies:/data/movies:ro
|
||||
- /storage1/TV:/data/tv:ro
|
||||
- /storage1/Anime:/data/anime:ro
|
||||
ports:
|
||||
- "8096:8096"
|
||||
|
||||
jellystat-db:
|
||||
container_name: jellystat-db
|
||||
image: postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
networks: [streaming]
|
||||
env_file:
|
||||
- stack.env
|
||||
labels:
|
||||
- "com.centurylabs.watchtower.enable=false"
|
||||
volumes:
|
||||
- /config/jellystat-db:/var/lib/postgresql/data
|
||||
|
||||
jellystat:
|
||||
container_name: jellystat
|
||||
image: cyfershepard/jellystat:latest
|
||||
restart: unless-stopped
|
||||
networks: [streaming, edge]
|
||||
depends_on:
|
||||
- jellystat-db
|
||||
env_file:
|
||||
- stack.env
|
||||
volumes:
|
||||
- /config/jellystat:/app/backend/backup-data
|
||||
ports:
|
||||
- "3009:3000"
|
||||
|
||||
wizarr:
|
||||
container_name: wizarr
|
||||
image: ghcr.io/wizarrrr/wizarr:latest
|
||||
restart: unless-stopped
|
||||
networks: [streaming, edge]
|
||||
env_file:
|
||||
- stack.env
|
||||
volumes:
|
||||
- /config/wizarr:/data/database
|
||||
ports:
|
||||
- "5690:5690"
|
||||
|
||||
jellyplex-watched:
|
||||
container_name: jellyplex-watched
|
||||
image: luigi311/jellyplex-watched:latest
|
||||
restart: unless-stopped
|
||||
networks: [streaming, edge]
|
||||
env_file:
|
||||
- stack.env
|
||||
|
||||
networks:
|
||||
streaming:
|
||||
name: streaming
|
||||
driver: bridge
|
||||
edge:
|
||||
name: edge
|
||||
external: true
|
||||
@@ -0,0 +1,34 @@
|
||||
# streaming stack config — injected into every service via env_file.
|
||||
# Var names are exactly what the containers read; no ${VAR} interpolation,
|
||||
# no Portainer UI env vars needed.
|
||||
|
||||
# --- shared ---
|
||||
TZ=America/Chicago
|
||||
PUID=1000
|
||||
PGID=1000
|
||||
|
||||
# --- jellystat-db (postgres:16-alpine) ---
|
||||
POSTGRES_DB=jfstat
|
||||
POSTGRES_USER=jellystat
|
||||
POSTGRES_PASSWORD=bf2d47d8e1a87baf2b4942704a5acfaf5c2aad336fdd480e
|
||||
|
||||
# --- jellystat ---
|
||||
POSTGRES_IP=jellystat-db
|
||||
POSTGRES_PORT=5432
|
||||
JWT_SECRET=0dd9fcdb632277b76977dbada8226e547cc773e1deafc8d778d4038f8d59393b
|
||||
|
||||
# --- jellyplex-watched ---
|
||||
# Plex is host-native. Plain HTTP on :32400 only answers on loopback; LAN HTTP
|
||||
# gets an empty reply. Temporary host socat proxy plex-http-proxy.service
|
||||
# exposes loopback Plex HTTP on :32402 for Docker clients (Plexyfin + jellyplex).
|
||||
# Tear down that unit after Plex is decommissioned / Plexyfin is done.
|
||||
PLEX_URL=http://192.168.1.69:32402
|
||||
PLEX_TOKEN=hM9aycswHKSsUrxiHqwq
|
||||
JELLYFIN_BASEURL=http://jellyfin:8096
|
||||
JELLYFIN_TOKEN=a3d9ee827d5c42298cae087a2c31d3fa
|
||||
SYNC_FROM_PLEX_TO_JELLYFIN=True
|
||||
SYNC_FROM_JELLYFIN_TO_PLEX=True
|
||||
# Start in dry-run. Task 16 flips this to False after the mapping is verified.
|
||||
DRYRUN=False
|
||||
SLEEP_DURATION=21600
|
||||
LOG_LEVEL=INFO
|
||||
Reference in New Issue
Block a user