# Project: PeerTube YouTube Archive Rebuild **Goal:** Rebuild PeerTube at `stream.echo6.co` with Authentik SSO, 18TB NFS storage, and a bulk import pipeline for 250 YouTube channels (~136K videos). **Status:** Phase 1 — Complete (2026-02-13). CT 110 on media, 192.168.1.170, TS 100.64.0.23, PeerTube v8.0.2 --- ## Architecture ``` ┌─────────────────────────────────┐ │ utility node │ Internet ──── DNS ──────▶│ Caddy LXC (CT 101) │ │ stream.echo6.co → PT LXC:80 │ └──────────────┬──────────────────┘ │ ┌──────────────▼──────────────────┐ │ media node │ │ PeerTube LXC (CT 100) │ │ ├── nginx (port 80) │ │ ├── PeerTube (port 9000) │ │ ├── PostgreSQL 16 │ │ ├── Redis │ │ └── /var/www/peertube/storage │ │ └── NFS mount (18TB) │ └──────────────────────────────────┘ Authentik ◄──── OIDC ────► PeerTube Phase 2: cortex (VM 150 on TOC, has GPU) = remote transcoding runner ``` **Key decisions:** - LXC on media node (not VM, not Docker) — CT 100 - Privileged container (NFS bind-mount uid mapping is hell otherwise) - Native PeerTube install (Node.js + PostgreSQL + Redis + nginx, no Docker) - PeerTube v8.0.2 — config and nginx template may differ from v6.x docs; verify during install - Node.js 20 (v8 requirement, not 18) - Caddy on utility handles TLS, nginx inside LXC handles WebSocket/static files - Caddy proxies to local IP (192.168.1.x) since PeerTube has OIDC - Transcoding: 480p + 720p only (storage budget) - Built-in channel sync DISABLED — bulk pipeline handles imports - Signup disabled — Authentik SSO only - NFS storage from pi-nas `/export/peertube` (separate from arr) --- ## Phase 1: PeerTube Up and Secure ### 1.1 Provision LXC on media **Run:** `runbooks/ct-runbook.md` with these inputs: | Variable | Value | |----------|-------| | Host | media (192.168.1.243) | | CTID | 100 | | Hostname | peertube | | Template | Debian 12 (not Ubuntu — PeerTube docs target Debian) | | Memory | 4096 MB | | Cores | 4 | | Disk | 50 GB root | | Privileged | YES (override ct-runbook default) | | Network | DHCP initially | **Deviations from ct-runbook:** - Use **Debian 12** template instead of Ubuntu 24.04 - Use **privileged** container (`--unprivileged 0`) for NFS compatibility - Skip Docker install — PeerTube runs native - Still do: base packages, zvx user, SSH, Tailscale ### 1.2 Mount NFS storage On the **media host** (not inside LXC): ```bash # Mount NFS on host mkdir -p /mnt/peertube-storage mount -t nfs 192.168.1.245:/export/peertube /mnt/peertube-storage # Persist echo "192.168.1.245:/export/peertube /mnt/peertube-storage nfs defaults,_netdev 0 0" >> /etc/fstab # Bind-mount into LXC echo "mp0: /mnt/peertube-storage,mp=/var/www/peertube/storage" >> /etc/pve/lxc/.conf # Restart LXC to pick up mount pct stop && pct start ``` **Verify inside LXC:** ```bash df -h /var/www/peertube/storage # Should show ~18TB touch /var/www/peertube/storage/test && rm /var/www/peertube/storage/test ``` **NFS details:** - Server: pi-nas (192.168.1.245 / 100.64.0.21) - Export: `/export/peertube` - Access: Already configured in OMV for 100.64.0.0/10 and 192.168.1.0/24 ### 1.3 Install PeerTube dependencies Inside the LXC: ```bash # PostgreSQL 16 sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg apt update && apt install -y postgresql-16 postgresql-contrib-16 sudo -u postgres psql << 'SQL' CREATE USER peertube WITH PASSWORD ''; CREATE DATABASE peertube_prod OWNER peertube; \c peertube_prod CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE EXTENSION IF NOT EXISTS unaccent; SQL # Redis apt install -y redis-server sed -i 's/^# requirepass .*/requirepass /' /etc/redis/redis.conf sed -i 's/^bind .*/bind 127.0.0.1 -::1/' /etc/redis/redis.conf systemctl restart redis-server && systemctl enable redis-server # Node.js 20 (PeerTube v8 requires Node.js 20+) curl -fsSL https://deb.nodesource.com/setup_20.x | bash - apt install -y nodejs npm install -g yarn # ffmpeg (for transcoding) apt install -y ffmpeg ``` ### 1.4 Install PeerTube ```bash # Create peertube user adduser --system --group --home /var/www/peertube --shell /bin/bash peertube chown -R peertube:peertube /var/www/peertube/storage # Get latest version (joinpeertube.org API is dead, use GitHub) PEERTUBE_VERSION=$(curl -s https://api.github.com/repos/Chocobozzz/PeerTube/releases/latest | grep -oP '"tag_name": "v\K[^"]+' || echo "8.0.2") # Download and install cd /var/www/peertube sudo -u peertube mkdir -p config sudo -u peertube mkdir -p storage/{avatars,caches,captions,logs,plugins,previews,redundancy,streaming-playlists,thumbnails,tmp,torrents,videos,bin,storyboards,web-videos,original-video-files} sudo -u peertube wget -q "https://github.com/Chocobozzz/PeerTube/releases/download/v${PEERTUBE_VERSION}/peertube-v${PEERTUBE_VERSION}.tar.xz" sudo -u peertube tar xf peertube-v${PEERTUBE_VERSION}.tar.xz sudo -u peertube ln -s peertube-v${PEERTUBE_VERSION} peertube-latest cd peertube-latest sudo -u peertube yarn install --production --pure-lockfile ``` ### 1.5 Configure PeerTube Create `/var/www/peertube/config/local-production.json`: ```json { "listen": { "hostname": "0.0.0.0", "port": 9000 }, "webserver": { "https": true, "hostname": "stream.echo6.co", "port": 443 }, "database": { "hostname": "localhost", "port": 5432, "name": "peertube_prod", "username": "peertube", "password": "" }, "redis": { "hostname": "localhost", "port": 6379, "auth": "" }, "storage": { "avatars": "/var/www/peertube/storage/avatars/", "caches": "/var/www/peertube/storage/caches/", "captions": "/var/www/peertube/storage/captions/", "logs": "/var/www/peertube/storage/logs/", "plugins": "/var/www/peertube/storage/plugins/", "previews": "/var/www/peertube/storage/previews/", "redundancy": "/var/www/peertube/storage/redundancy/", "streaming_playlists": "/var/www/peertube/storage/streaming-playlists/", "thumbnails": "/var/www/peertube/storage/thumbnails/", "tmp": "/var/www/peertube/storage/tmp/", "torrents": "/var/www/peertube/storage/torrents/", "videos": "/var/www/peertube/storage/videos/", "bin": "/var/www/peertube/storage/bin/", "storyboards": "/var/www/peertube/storage/storyboards/", "web_videos": "/var/www/peertube/storage/web-videos/", "original_video_files": "/var/www/peertube/storage/original-video-files/" }, "admin": { "email": "admin@echo6.co" }, "signup": { "enabled": false }, "import": { "videos": { "concurrency": 10, "http": { "enabled": true }, "torrent": { "enabled": false } }, "video_channel_synchronization": { "enabled": false } }, "transcoding": { "enabled": true, "threads": 2, "concurrency": 2, "allow_additional_extensions": true, "allow_audio_files": true, "resolutions": { "0p": false, "144p": false, "240p": false, "360p": false, "480p": true, "720p": true, "1080p": false, "1440p": false, "2160p": false }, "hls": { "enabled": true }, "web_videos": { "enabled": true } } } ``` ```bash chown peertube:peertube /var/www/peertube/config/local-production.json chmod 600 /var/www/peertube/config/local-production.json ``` ### 1.6 nginx (inside LXC) ```bash apt install -y nginx rm -f /etc/nginx/sites-enabled/default cat > /etc/nginx/sites-available/peertube << 'NGINXCONF' server { listen 80; server_name stream.echo6.co; add_header X-Frame-Options "SAMEORIGIN"; add_header X-Content-Type-Options "nosniff"; client_max_body_size 20G; proxy_connect_timeout 600; proxy_send_timeout 600; proxy_read_timeout 600; send_timeout 600; location ~ ^/client/(.*\.(js|css|woff2|otf|ttf|woff|eot|svg|png|jpg|gif|ico|webp))$ { add_header Cache-Control "public, max-age=31536000, immutable"; alias /var/www/peertube/peertube-latest/client/dist/$1; } location ~ ^(/static/(webseed|web-videos|streaming-playlists|redundancy)/.+)$ { set $upstream_peertube http://127.0.0.1:9000; try_files /var/www/peertube/storage$1 @api; root /; add_header Cache-Control "public, max-age=7200"; } location @api { proxy_pass http://127.0.0.1:9000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } location / { proxy_pass http://127.0.0.1:9000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; client_max_body_size 20G; } } NGINXCONF ln -s /etc/nginx/sites-available/peertube /etc/nginx/sites-enabled/peertube nginx -t && systemctl restart nginx && systemctl enable nginx ``` ### 1.7 systemd service ```bash cat > /etc/systemd/system/peertube.service << 'EOF' [Unit] Description=PeerTube daemon After=network.target postgresql.service redis-server.service [Service] Type=simple User=peertube Group=peertube Environment=NODE_ENV=production Environment=NODE_CONFIG_DIR=/var/www/peertube/config WorkingDirectory=/var/www/peertube/peertube-latest ExecStart=/usr/bin/node dist/server Restart=always RestartSec=10 StandardOutput=journal StandardError=journal SyslogIdentifier=peertube TimeoutStartSec=60 [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable peertube systemctl start peertube # Grab auto-generated root password journalctl -u peertube | grep -i "password" ``` ### 1.8 Expose via Caddy **Run:** `runbooks/expose-service-home.md` with these inputs: | Variable | Value | |----------|-------| | Service | stream | | Domain | stream.echo6.co | | Backend IP | PeerTube LXC local IP (192.168.1.x — has OIDC, use local IP pattern) | | Backend port | 80 | | Has OIDC | YES (use local IP, not Tailscale) | DNS record for `stream.echo6.co` does NOT exist in GoDaddy yet — create it pointing to `199.6.36.163`. Also update dnsmasq split DNS on Contabo — entry exists pointing to old IP `100.64.0.7`, update to `100.64.0.8` (utility Caddy, same pattern as jellyfin/requests). ### 1.9 Authentik OIDC **Run:** `runbooks/authentik-oidc-application.md` with these inputs: | Variable | Value | |----------|-------| | SERVICE_NAME | PeerTube | | SERVICE_SLUG | peertube | | SERVICE_URL | https://stream.echo6.co | | OIDC_CALLBACK_PATH | Check PeerTube OIDC plugin docs — likely `/plugins/auth-openid-connect/router/code-cb` | | NEEDS_OFFLINE_ACCESS | yes | | CLIENT_TYPE | confidential | **Note:** No existing PeerTube provider in Authentik — create from scratch using the runbook. Then install the plugin inside PeerTube: ```bash cd /var/www/peertube/peertube-latest sudo -u peertube NODE_ENV=production NODE_CONFIG_DIR=/var/www/peertube/config \ node dist/server/tools/peertube-plugins.js install \ --npm-name peertube-plugin-auth-openid-connect systemctl restart peertube ``` Configure via Admin UI → Plugins → OpenID Connect: - Discover URL: `https://auth.echo6.co/application/o/peertube/.well-known/openid-configuration` - Client ID/Secret from Authentik - Scope: `openid email profile` - Username property: `preferred_username` - Display name property: `name` ### 1.10 First login and lockdown 1. Log in as `root` with the auto-generated password 2. Change root password immediately 3. Test Authentik SSO login 4. Promote your Authentik user to admin 5. Admin → Configuration: instance name "Echo6 Archive", signup disabled, HTTP import enabled ### Phase 1 checklist ``` [x] LXC on media — CT 110, privileged, Debian 12, 4C/4GB/50GB [x] NFS 22TB mounted and writable (/export/peertube from 192.168.1.245) [x] PostgreSQL 16 + Redis installed [x] Node.js 22 + pnpm + ffmpeg installed (v8.0.2 requires Node 22, pnpm not yarn) [x] PeerTube v8.0.2 installed and configured [x] nginx configured (port 80, WebSocket, static files) [x] systemd service running [x] Tailscale registered (100.64.0.23) [x] Caddy on utility proxying stream.echo6.co → 192.168.1.170:80 [x] DNS verified (GoDaddy + dnsmasq split DNS → 100.64.0.8) [x] Authentik OIDC working (provider pk:12, app slug: peertube) [ ] Root password changed, your user promoted to admin (manual step) ``` **Update after Phase 1:** - `docs/hardware/environment.md` — add PeerTube LXC - `docs/services/services.md` — add PeerTube entry - `docs/software/caddy.md` — add stream.echo6.co site block --- ## Phase 2: Import Pipeline ### 2.1 Create PeerTube channels Script to create all 250 channels from the master spreadsheet via PeerTube API. One channel per YouTube channel, matching names. ### 2.2 Bulk downloader - yt-dlp with cookies + PO tokens - `--match-filter "duration > 61"` to exclude Shorts - Round-robin across channels (5-10 videos per channel, rotate) - Download to NFS staging area - Track downloaded video IDs in archive file (prevent re-downloads) ### 2.3 Import pipeline - Watch staging area for new downloads - Import to correct PeerTube channel via API - Move source file after successful import (or delete if transcoded) - Rate limit to avoid overwhelming PeerTube ### 2.4 GPU transcoding on cortex - PeerTube remote runner protocol - cortex already has RTX A4000 + nvidia-container-toolkit - NVENC encoding for 480p + 720p HLS - PeerTube delegates transcoding jobs to cortex runner ### Phase 2 checklist ``` [x] 100 channels created in PeerTube (99 planned + extras, channel-map.json at /opt/bulk-import/config/) [x] yt-dlp configured (cookies, Shorts filter) [x] Bulk downloader script with round-robin (pt-downloader service on CT 110) [x] Import pipeline (pt-importer service on CT 110, resumable chunked upload) [x] Archive tracking (downloaded.txt, downloader-state.json) [x] GPU transcoding runner on cortex (pt-transcoder service, H.265 NVENC) [x] PeerTube remote runner on cortex (Whisper auto-captioning, medium model, smart GPU/CPU routing) [x] Test: full cycle — download → transcode → import → playable [ ] VPN/IP rotation (NordVPN token pending from Matt) ``` --- ## Phase 3: Monitoring ### 3.1 WATCHTOWER dashboard - Import queue depth and throughput - Per-channel video counts vs YouTube totals - Storage usage and growth rate - Transcoding queue status ### 3.2 Alerts - Storage threshold warnings (80%, 90%, 95%) - Stalled imports (no progress for N hours) - Failed downloads (rate limiting, auth issues) ### Phase 3 checklist ``` [ ] Dashboard showing import progress [ ] Per-channel completion tracking [ ] Storage alerts configured [ ] Stall detection working ``` --- ## Reference - **Master channel list:** `youtube_archive_master.xlsx` (250 channels, 19 categories) - **PeerTube LXC:** CT 110 on media (192.168.1.243) - **NFS:** pi-nas (192.168.1.245) export `/export/peertube` - **Previous PeerTube Tailscale IP:** 100.64.0.7 (do not reuse — assign fresh) - **Previous bulk import map:** `final-channel-map.json` (lost with crash) - **Previous download archive:** `downloaded.txt` (21,714 video IDs, lost with crash) - **Runbooks used:** ct-runbook.md, expose-service-home.md, authentik-oidc-application.md - **Docs to update after Phase 1:** environment.md, services.md, caddy.md, dns.md (dnsmasq entry)