From 73585213ba5075364a578aa3828ec451c446393a Mon Sep 17 00:00:00 2001 From: R0m1k3 Date: Thu, 16 Apr 2026 10:13:08 +0200 Subject: [PATCH] feat: add Unraid single-container deployment with docker-compose and comprehensive setup guide --- .claude/settings.local.json | 5 +- .claude/worktrees/upbeat-easley | 1 + .env.unraid | 55 ++++ Dockerfile.unraid | 154 +++++++++++ UNRAID_INSTALL.md | 458 ++++++++++++++++++++++++++++++++ docker-compose.unraid.yml | 81 ++++++ 6 files changed, 753 insertions(+), 1 deletion(-) create mode 160000 .claude/worktrees/upbeat-easley create mode 100644 .env.unraid create mode 100644 Dockerfile.unraid create mode 100644 UNRAID_INSTALL.md create mode 100644 docker-compose.unraid.yml diff --git a/.claude/settings.local.json b/.claude/settings.local.json index d059e0b..1bffd74 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -2,7 +2,10 @@ "permissions": { "allow": [ "Bash(rtk git:*)", - "Bash(rtk npm:*)" + "Bash(rtk npm:*)", + "Bash(git remote:*)", + "Bash(git branch:*)", + "Bash(git push:*)" ] } } diff --git a/.claude/worktrees/upbeat-easley b/.claude/worktrees/upbeat-easley new file mode 160000 index 0000000..9bb61cc --- /dev/null +++ b/.claude/worktrees/upbeat-easley @@ -0,0 +1 @@ +Subproject commit 9bb61cc8a748415a087657b7b428b332ac03ed29 diff --git a/.env.unraid b/.env.unraid new file mode 100644 index 0000000..8a8a12b --- /dev/null +++ b/.env.unraid @@ -0,0 +1,55 @@ +# ========================================== +# Podcastic - Unraid Configuration +# ========================================== +# All services in single container on port 3579 + +# Application Environment +NODE_ENV=production + +# ========================================== +# DATABASE & CACHE (Internal - Don't change) +# ========================================== +MONGODB_URI=mongodb://127.0.0.1:27017/podcastic +REDIS_URL=redis://127.0.0.1:6379 + +# ========================================== +# SECURITY - CHANGE THESE! +# ========================================== +# JWT Secret - Use strong random string +# Generate: openssl rand -base64 32 +JWT_SECRET=change-this-to-random-secret-key-in-production +JWT_EXPIRES_IN=7d +JWT_REFRESH_EXPIRES_IN=30d + +# ========================================== +# OPTIONAL: PodcastIndex API (Podcast Search) +# ========================================== +# Leave empty to disable (manual RSS subscription still works) +# Get free keys: https://podcastindex-api.com/ +PODCAST_INDEX_API_KEY= +PODCAST_INDEX_API_SECRET= + +# ========================================== +# PORTS (Single container) +# ========================================== +# Frontend: http://your-unraid-ip:3579 +# API: http://your-unraid-ip:3579/api +# Health: http://your-unraid-ip:3579/health + +PORT=5000 # Internal backend port (don't change) + +# ========================================== +# Frontend Configuration +# ========================================== +VITE_API_URL=http://your-unraid-ip:3579/api +# Or use relative path: +# VITE_API_URL=/api + +# ========================================== +# NOTES FOR UNRAID +# ========================================== +# 1. Change JWT_SECRET to a random string +# 2. If accessing from WAN, update VITE_API_URL to your domain +# 3. Optionally add PodcastIndex API keys for search feature +# 4. All data stored in /mnt/user/appdata/podcastic/ +# 5. Logs in /mnt/user/appdata/podcastic/logs/ diff --git a/Dockerfile.unraid b/Dockerfile.unraid new file mode 100644 index 0000000..bda077c --- /dev/null +++ b/Dockerfile.unraid @@ -0,0 +1,154 @@ +# Multi-stage build for Podcastic All-in-One Container +# Combines: Backend (Node.js), Frontend (React), Web Server (Nginx), Database (MongoDB), Cache (Redis) + +# ==================== STAGE 1: Build Backend ==================== +FROM node:20-alpine AS backend-builder + +WORKDIR /build/backend + +COPY backend/package*.json ./ +RUN npm ci + +COPY backend/tsconfig.json ./ +COPY backend/src ./src + +RUN npm run build + +# ==================== STAGE 2: Build Frontend ==================== +FROM node:20-alpine AS frontend-builder + +WORKDIR /build/frontend + +COPY frontend/package*.json ./ +RUN npm ci + +COPY frontend/tsconfig*.json ./ +COPY frontend/vite.config.ts ./ +COPY frontend/postcss.config.js ./ +COPY frontend/tailwind.config.js ./ +COPY frontend/index.html ./ +COPY frontend/src ./src + +RUN npm run build + +# ==================== STAGE 3: Final Image ==================== +FROM node:20-alpine + +# Install supervisor to manage multiple processes, mongodb, redis, nginx +RUN apk add --no-cache supervisor nginx mongodb redis bash curl + +WORKDIR /app + +# ==================== Setup Backend ==================== +COPY --from=backend-builder /build/backend/dist ./backend/dist +COPY --from=backend-builder /build/backend/package*.json ./backend/ +RUN cd backend && npm ci --only=production + +# ==================== Setup Frontend ==================== +RUN mkdir -p /usr/share/nginx/html +COPY --from=frontend-builder /build/frontend/dist/* /usr/share/nginx/html/ + +# ==================== Setup Nginx ==================== +RUN mkdir -p /etc/nginx/conf.d + +COPY <<'EOF' /etc/nginx/conf.d/default.conf +server { + listen 3579; + server_name _; + client_max_body_size 100M; + + # Frontend - Static files + location / { + root /usr/share/nginx/html; + try_files $uri $uri/ /index.html; + expires 1d; + add_header Cache-Control "public, immutable"; + } + + # Static assets with long cache + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + root /usr/share/nginx/html; + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # API - Proxy to backend + location /api { + proxy_pass http://127.0.0.1:5000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + 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 $scheme; + proxy_cache_bypass $http_upgrade; + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # Health check endpoint + location /health { + proxy_pass http://127.0.0.1:5000; + } +} +EOF + +# ==================== Setup Supervisor ==================== +RUN mkdir -p /var/log/supervisor + +COPY <<'EOF' /etc/supervisor/conf.d/supervisord.conf +[supervisord] +nodaemon=true +logfile=/var/log/supervisor/supervisord.log +pidfile=/var/run/supervisord.pid + +[program:mongodb] +command=/usr/bin/mongod --dbpath /data/db --bind_ip 127.0.0.1 +autostart=true +autorestart=true +stderr_logfile=/var/log/supervisor/mongodb.err.log +stdout_logfile=/var/log/supervisor/mongodb.out.log + +[program:redis] +command=/usr/bin/redis-server --bind 127.0.0.1 +autostart=true +autorestart=true +stderr_logfile=/var/log/supervisor/redis.err.log +stdout_logfile=/var/log/supervisor/redis.out.log + +[program:backend] +command=/bin/bash -c "cd /app/backend && node dist/index.js" +environment=NODE_ENV=production,MONGODB_URI=mongodb://127.0.0.1:27017/podcastic,REDIS_URL=redis://127.0.0.1:6379 +autostart=true +autorestart=true +stderr_logfile=/var/log/supervisor/backend.err.log +stdout_logfile=/var/log/supervisor/backend.out.log + +[program:nginx] +command=/usr/sbin/nginx -g "daemon off;" +autostart=true +autorestart=true +stderr_logfile=/var/log/supervisor/nginx.err.log +stdout_logfile=/var/log/supervisor/nginx.out.log +EOF + +# ==================== Create Data Directories ==================== +RUN mkdir -p /data/db /data/redis + +# ==================== Environment ==================== +ENV NODE_ENV=production +ENV MONGODB_URI=mongodb://127.0.0.1:27017/podcastic +ENV REDIS_URL=redis://127.0.0.1:6379 +ENV PORT=5000 + +# ==================== Health Check ==================== +HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ + CMD curl -f http://127.0.0.1:3579/health || exit 1 + +# ==================== Expose Port ==================== +EXPOSE 3579 + +# ==================== Entrypoint ==================== +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] diff --git a/UNRAID_INSTALL.md b/UNRAID_INSTALL.md new file mode 100644 index 0000000..c9ddb49 --- /dev/null +++ b/UNRAID_INSTALL.md @@ -0,0 +1,458 @@ +# 🐳 Podcastic - Unraid Installation Guide + +Complete guide to install Podcastic on Unraid as a **single all-in-one container**. + +## 📋 What's Included + +This single Docker container includes: +- ✅ **Backend**: Node.js + Express.js API +- ✅ **Frontend**: React web interface +- ✅ **Database**: MongoDB (embedded) +- ✅ **Cache**: Redis (embedded) +- ✅ **Web Server**: Nginx reverse proxy +- ✅ **Process Manager**: Supervisor (manages all 5 services) + +All in **ONE container** on port **3579** + +## Prerequisites + +- Unraid server with Docker support +- ~2GB RAM available +- ~20GB storage for database/media +- Internet connection + +## Installation Steps + +### Step 1: Clone Repository + +```bash +# Via terminal on Unraid +cd /mnt/user/appdata # or your preferred path +git clone https://github.com/R0m1k3/podcastic.git +cd podcastic +``` + +Or download as ZIP from GitHub and extract. + +### Step 2: Configure Environment + +```bash +# Copy template +cp .env.example .env + +# Edit with your settings +nano .env +``` + +**Key settings**: +```env +NODE_ENV=production + +# JWT Secret - CHANGE THIS! +JWT_SECRET=your-super-secret-key-change-me-in-production + +# Optional: PodcastIndex API for podcast search +PODCAST_INDEX_API_KEY= +PODCAST_INDEX_API_SECRET= +``` + +### Step 3: Build Docker Image + +Option A: Build locally (takes ~5-10 minutes) +```bash +docker-compose -f docker-compose.unraid.yml build --no-cache +``` + +Option B: Use pre-built image (faster) +```bash +# Will be available after first push to Docker Hub +# docker pull r0m1k3/podcastic:latest +``` + +### Step 4: Start Container + +```bash +# Start +docker-compose -f docker-compose.unraid.yml up -d + +# View logs +docker-compose -f docker-compose.unraid.yml logs -f podcastic + +# Check status +docker ps | grep podcastic +``` + +### Step 5: Access Application + +Open browser: +``` +http://your-unraid-ip:3579 +``` + +**First Time Setup**: +1. Click "Create Account" +2. Register with email/password +3. Log in +4. Go to "Discover" to search podcasts +5. Subscribe and listen! + +--- + +## 📊 Container Architecture + +``` +Single Container (podcastic) +├── Nginx (port 3579) +│ ├── Serves static frontend +│ └── Proxies /api to backend +├── Node.js Backend (internal port 5000) +│ ├── API routes +│ ├── Authentication +│ └── Database management +├── MongoDB (internal port 27017) +│ └── Podcast data storage +└── Redis (internal port 6379) + └── Cache & sessions +``` + +All services communicate **internally** - only port 3579 exposed. + +--- + +## 🛠️ Unraid Integration + +### Add to Unraid via WebUI + +1. **Settings → Docker** → Enable Docker +2. **Docker** tab +3. Add custom template: + ```xml + + + podcastic + r0m1k3/podcastic:latest + https://hub.docker.com/ + bridge + + sh + false + + + + + http://[IP]:[PORT:3579] + + + + + false + + + Podcastic - Multi-user podcast streaming application + + bridge + + + 3579 + 3579 + tcp + + + + + + /mnt/user/appdata/podcastic/db + /data/db + rw + + + /mnt/user/appdata/podcastic/redis + /data/redis + rw + + + /mnt/user/appdata/podcastic/logs + /var/log/supervisor + rw + + + + + JWT_SECRET + change-me-in-production + + + PODCAST_INDEX_API_KEY + + + + PODCAST_INDEX_API_SECRET + + + + + ``` + +Or use Docker Compose tab with `docker-compose.unraid.yml` + +--- + +## 📂 Data Persistence + +Data is saved to Unraid shares: + +``` +/mnt/user/appdata/podcastic/ +├── db/ # MongoDB data (podcasts, users, episodes) +├── redis/ # Redis cache data +└── logs/ # Application logs +``` + +### Backup + +```bash +# Backup database +docker exec podcastic mongodump --out /backup/mongodb + +# Copy to safe location +cp -r /mnt/user/appdata/podcastic /mnt/backup/podcastic-backup +``` + +### Restore + +```bash +docker exec podcastic mongorestore /backup/mongodb +``` + +--- + +## 🔧 Management Commands + +### View Logs + +```bash +# All logs +docker logs -f podcastic + +# Specific service +docker exec podcastic tail -f /var/log/supervisor/backend.out.log +docker exec podcastic tail -f /var/log/supervisor/mongodb.out.log +docker exec podcastic tail -f /var/log/supervisor/redis.out.log +docker exec podcastic tail -f /var/log/supervisor/nginx.out.log +``` + +### Restart Container + +```bash +docker-compose -f docker-compose.unraid.yml restart podcastic +``` + +### Stop Container + +```bash +docker-compose -f docker-compose.unraid.yml down + +# Keep volumes (data persists) +# Data in /mnt/user/appdata/podcastic/ is preserved +``` + +### Update to Latest + +```bash +# Pull latest code +cd /path/to/podcastic +git pull + +# Rebuild and restart +docker-compose -f docker-compose.unraid.yml build --no-cache +docker-compose -f docker-compose.unraid.yml up -d +``` + +--- + +## 🐛 Troubleshooting + +### Container won't start + +```bash +# Check logs +docker logs podcastic + +# Common issues: +# - Port 3579 in use: Change in docker-compose.unraid.yml +# - Insufficient RAM: Free up memory +# - Permission issues: Check volume permissions +``` + +### MongoDB not starting + +```bash +# Check mongo logs +docker exec podcastic tail -f /var/log/supervisor/mongodb.out.log + +# Repair database +docker exec podcastic mongod --repair --dbpath /data/db +``` + +### Can't access on port 3579 + +```bash +# Check if container is running +docker ps | grep podcastic + +# Check if port is open +netstat -tulpn | grep 3579 + +# Test connection +curl http://localhost:3579/health +``` + +### Out of disk space + +MongoDB can grow large. Check: +```bash +docker exec podcastic du -sh /data/db + +# Clean old data (backup first!) +``` + +--- + +## 📈 Performance Optimization + +### For Limited Resources + +Edit `docker-compose.unraid.yml`: +```yaml +# Limit memory usage +mem_limit: 1g +cpus: '0.5' +``` + +### For High Performance + +Increase limits: +```yaml +mem_limit: 4g +cpus: '2' +``` + +### Disable Unneeded Features + +If only streaming (no search): +```env +PODCAST_INDEX_API_KEY= # Leave empty +``` + +--- + +## 🔒 Security Notes + +### Change Default Secrets + +```env +# MUST change in production! +JWT_SECRET=your-unique-secret-key-here + +# Use strong password for first admin account +``` + +### Firewall Rules + +Allow only trusted IPs: +```bash +# On Unraid, set firewall rules in UI +# Or use nginx config to restrict access +``` + +### SSL/HTTPS Setup + +If exposing to internet, use reverse proxy with SSL: +- Nginx Proxy Manager (separate container) +- Traefik (with Let's Encrypt) +- Unraid built-in reverse proxy + +--- + +## 📊 Monitoring + +### Check Container Health + +```bash +# Real-time stats +docker stats podcastic + +# Service status +docker exec podcastic supervisorctl status + +# Expected output: +# mongodb RUNNING pid 123, uptime 0:00:45 +# redis RUNNING pid 124, uptime 0:00:45 +# backend RUNNING pid 125, uptime 0:00:45 +# nginx RUNNING pid 126, uptime 0:00:45 +``` + +### Resource Usage + +```bash +# Disk usage +du -sh /mnt/user/appdata/podcastic/ + +# Memory usage +docker exec podcastic free -h + +# CPU usage +docker stats --no-stream podcastic +``` + +--- + +## 🆘 Support + +If issues occur: + +1. **Check logs**: + ```bash + docker logs podcastic + ``` + +2. **Check service status**: + ```bash + docker exec podcastic supervisorctl status + ``` + +3. **Rebuild container**: + ```bash + docker-compose -f docker-compose.unraid.yml build --no-cache + docker-compose -f docker-compose.unraid.yml up -d + ``` + +4. **Factory reset** (loses all data!): + ```bash + docker-compose -f docker-compose.unraid.yml down -v + rm -rf /mnt/user/appdata/podcastic + # Start fresh + ``` + +--- + +## 🎯 Next Steps + +1. **First Run**: Create account, set JWT_SECRET properly +2. **Configure PodcastIndex**: Optional, enables podcast search +3. **Add Podcasts**: Via Discover or RSS URLs +4. **Enjoy**: Start listening! + +--- + +## 📚 Additional Resources + +- **Main README**: See README.md +- **Getting Started**: See GETTING_STARTED.md +- **PodcastIndex Setup**: See PODCAST_INDEX_SETUP.md +- **GitHub**: https://github.com/R0m1k3/podcastic + +--- + +**Last Updated**: 2026-04-16 +**Tested on**: Unraid 6.12+ diff --git a/docker-compose.unraid.yml b/docker-compose.unraid.yml new file mode 100644 index 0000000..0bf6e2e --- /dev/null +++ b/docker-compose.unraid.yml @@ -0,0 +1,81 @@ +version: '3.8' + +services: + podcastic: + build: + context: . + dockerfile: Dockerfile.unraid + container_name: podcastic + restart: unless-stopped + ports: + - "3579:3579" # Web Interface (Frontend + API) + environment: + # Backend Config + NODE_ENV: production + PORT: 5000 + + # Database + MONGODB_URI: mongodb://127.0.0.1:27017/podcastic + REDIS_URL: redis://127.0.0.1:6379 + + # JWT Config + JWT_SECRET: ${JWT_SECRET:-change_me_in_production} + JWT_EXPIRES_IN: 7d + JWT_REFRESH_EXPIRES_IN: 30d + + # PodcastIndex API (Optional - for podcast search) + PODCAST_INDEX_API_KEY: ${PODCAST_INDEX_API_KEY:-} + PODCAST_INDEX_API_SECRET: ${PODCAST_INDEX_API_SECRET:-} + + volumes: + # MongoDB data persistence + - podcastic_db:/data/db + # Redis data persistence (optional) + - podcastic_redis:/data/redis + # Application logs + - podcastic_logs:/var/log/supervisor + + networks: + - podcastic-network + + # Resource limits (adjust based on your server) + # Uncomment to set limits: + # mem_limit: 2g + # cpus: '1' + +volumes: + podcastic_db: + driver: local + podcastic_redis: + driver: local + podcastic_logs: + driver: local + +networks: + podcastic-network: + driver: bridge + +# ============================================ +# USAGE: +# ============================================ +# +# 1. Copy .env.example to .env and configure: +# cp .env.example .env +# +# 2. Build and start: +# docker-compose -f docker-compose.unraid.yml up -d +# +# 3. Access: +# http://your-server-ip:3579 +# +# 4. View logs: +# docker-compose -f docker-compose.unraid.yml logs -f podcastic +# +# 5. Stop: +# docker-compose -f docker-compose.unraid.yml down +# +# 6. Backup data: +# docker run --rm -v podcastic_db:/data/db -v $(pwd):/backup \ +# alpine tar czf /backup/podcastic-db.tar.gz -C /data db +# +# ============================================