Compare commits
15 Commits
2ea24a98cd
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae3b82862d | ||
|
|
8a89df3486 | ||
|
|
9c0a45afab | ||
|
|
49393d9a73 | ||
|
|
d235c8e057 | ||
|
|
52e910346b | ||
|
|
f2470e27ec | ||
|
|
0e242eb0b3 | ||
|
|
c4e43ce69b | ||
|
|
cf44843418 | ||
|
|
a4262da7c9 | ||
|
|
024430754c | ||
|
|
d17ed79e29 | ||
|
|
21eb63659a | ||
|
|
bb293b6a81 |
4
.dockerignore
Executable file → Normal file
@@ -52,6 +52,4 @@ PLAYER_AUTH.md
|
||||
PROGRESS.md
|
||||
README.md
|
||||
|
||||
# Config templates
|
||||
player_config_template.ini
|
||||
player_auth_module.py
|
||||
|
||||
|
||||
62
.env.example
Normal file
@@ -0,0 +1,62 @@
|
||||
# DigiServer v2 Production Environment Configuration
|
||||
# Copy to .env and update with your production values
|
||||
# IMPORTANT: Never commit this file to git
|
||||
|
||||
# Flask Configuration
|
||||
FLASK_ENV=production
|
||||
FLASK_APP=app.app:create_app
|
||||
|
||||
# Security - MUST BE SET IN PRODUCTION
|
||||
# Generate with: python -c "import secrets; print(secrets.token_urlsafe(32))"
|
||||
SECRET_KEY=change-me-to-a-strong-random-secret-key-at-least-32-characters
|
||||
|
||||
# Admin User Configuration
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=change-me-to-a-strong-password
|
||||
ADMIN_EMAIL=admin@your-domain.com
|
||||
|
||||
# Database Configuration (optional - defaults to SQLite)
|
||||
# For PostgreSQL: postgresql://user:pass@host:5432/database
|
||||
# For SQLite: sqlite:////data/instance/dashboard.db
|
||||
# DATABASE_URL=
|
||||
|
||||
# Server Configuration
|
||||
# Set BEFORE deployment if host will have static IP after restart
|
||||
# This IP/domain will be used for SSL certificates and nginx configuration
|
||||
DOMAIN=your-domain.com
|
||||
HOST_IP=192.168.0.121
|
||||
EMAIL=admin@your-domain.com
|
||||
PREFERRED_URL_SCHEME=https
|
||||
|
||||
# SSL/HTTPS (configured in nginx.conf by default)
|
||||
SSL_CERT_PATH=/etc/nginx/ssl/cert.pem
|
||||
SSL_KEY_PATH=/etc/nginx/ssl/key.pem
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=INFO
|
||||
|
||||
# Security Headers (configured in nginx.conf)
|
||||
HSTS_MAX_AGE=31536000
|
||||
HSTS_INCLUDE_SUBDOMAINS=true
|
||||
|
||||
# Features (optional)
|
||||
ENABLE_LIBREOFFICE=true
|
||||
MAX_UPLOAD_SIZE=500000000 # 500MB
|
||||
|
||||
# Cache Configuration (optional)
|
||||
CACHE_TYPE=simple
|
||||
CACHE_DEFAULT_TIMEOUT=300
|
||||
|
||||
# Session Configuration
|
||||
SESSION_COOKIE_SECURE=true
|
||||
SESSION_COOKIE_HTTPONLY=true
|
||||
SESSION_COOKIE_SAMESITE=Lax
|
||||
|
||||
# Proxy Configuration (configured in app.py)
|
||||
# IMPORTANT: Set this to your actual network range or specific proxy IP
|
||||
# Examples:
|
||||
# - 192.168.0.0/24 (local network with /24 subnet)
|
||||
# - 10.0.0.0/8 (AWS or similar cloud)
|
||||
# - 172.16.0.0/12 (Docker networks)
|
||||
# For multiple IPs: 192.168.0.121,10.0.1.50
|
||||
TRUSTED_PROXIES=192.168.0.0/24
|
||||
0
.gitignore
vendored
Executable file → Normal file
73
Caddyfile
@@ -1,73 +0,0 @@
|
||||
{
|
||||
# Global options
|
||||
email admin@example.com
|
||||
# Admin API for configuration management (listen on all interfaces)
|
||||
admin 0.0.0.0:2019
|
||||
}
|
||||
|
||||
# Shared reverse proxy configuration
|
||||
(reverse_proxy_config) {
|
||||
reverse_proxy digiserver-app:5000 {
|
||||
header_up Host {host}
|
||||
header_up X-Real-IP {remote_host}
|
||||
header_up X-Forwarded-Proto {scheme}
|
||||
|
||||
# Timeouts for large uploads
|
||||
transport http {
|
||||
read_timeout 300s
|
||||
write_timeout 300s
|
||||
}
|
||||
}
|
||||
|
||||
# File upload size limit (2GB)
|
||||
request_body {
|
||||
max_size 2GB
|
||||
}
|
||||
|
||||
# Security headers
|
||||
header {
|
||||
X-Frame-Options "SAMEORIGIN"
|
||||
X-Content-Type-Options "nosniff"
|
||||
X-XSS-Protection "1; mode=block"
|
||||
}
|
||||
|
||||
# Logging
|
||||
log {
|
||||
output file /var/log/caddy/access.log
|
||||
}
|
||||
}
|
||||
|
||||
# Localhost (development/local access - HTTP only for local dev)
|
||||
http://localhost {
|
||||
import reverse_proxy_config
|
||||
}
|
||||
|
||||
# Main HTTPS entry point with multiple hostnames and IP
|
||||
https://digiserver, https://10.76.152.164, https://digiserver.sibiusb.harting.intra {
|
||||
import reverse_proxy_config
|
||||
tls internal
|
||||
}
|
||||
|
||||
# HTTP redirects to HTTPS for each hostname
|
||||
http://digiserver {
|
||||
redir https://{host}{uri}
|
||||
}
|
||||
|
||||
http://10.76.152.164 {
|
||||
redir https://{host}{uri}
|
||||
}
|
||||
|
||||
http://digiserver.sibiusb.harting.intra {
|
||||
redir https://{host}{uri}
|
||||
}
|
||||
|
||||
# Catch-all for any other HTTP requests
|
||||
http://* {
|
||||
import reverse_proxy_config
|
||||
}
|
||||
|
||||
# Catch-all for any other HTTPS requests (fallback)
|
||||
https://* {
|
||||
import reverse_proxy_config
|
||||
tls internal
|
||||
}
|
||||
4
Dockerfile
Executable file → Normal file
@@ -24,7 +24,9 @@ COPY requirements.txt .
|
||||
# Install Python dependencies
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy application code
|
||||
# Copy entire application code into container
|
||||
# This includes: app/, migrations/, configs, and all scripts
|
||||
# Code is immutable in the image - only data folders are mounted as volumes
|
||||
COPY . .
|
||||
|
||||
# Copy and set permissions for entrypoint script
|
||||
|
||||
413
HTTPS_STATUS.txt
@@ -1,413 +0,0 @@
|
||||
╔═══════════════════════════════════════════════════════════════════════════════╗
|
||||
║ HTTPS MANAGEMENT SYSTEM IMPLEMENTATION ║
|
||||
║ ✅ COMPLETE ║
|
||||
╚═══════════════════════════════════════════════════════════════════════════════╝
|
||||
|
||||
📦 DELIVERABLES
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
✅ CREATED FILES (9 new files)
|
||||
───────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
1. 🗄️ DATABASE MODEL
|
||||
└─ app/models/https_config.py
|
||||
• HTTPSConfig database model
|
||||
• Fields: hostname, domain, ip_address, port, status, audit trail
|
||||
• Methods: get_config(), create_or_update(), to_dict()
|
||||
• Auto timestamps for created/updated dates
|
||||
|
||||
2. 🛣️ ADMIN ROUTES
|
||||
└─ app/blueprints/admin.py (UPDATED)
|
||||
• GET /admin/https-config - Configuration page
|
||||
• POST /admin/https-config/update - Update settings
|
||||
• GET /admin/https-config/status - JSON status endpoint
|
||||
• Full validation and error handling
|
||||
• Admin-only access control
|
||||
|
||||
3. 🎨 ADMIN TEMPLATE
|
||||
└─ app/templates/admin/https_config.html
|
||||
• Beautiful, user-friendly configuration interface
|
||||
• Status display section
|
||||
• Configuration form with toggle switch
|
||||
• Input validation feedback
|
||||
• Real-time preview of access points
|
||||
• Comprehensive help sections
|
||||
• Responsive mobile design
|
||||
|
||||
4. 📊 ADMIN DASHBOARD
|
||||
└─ app/templates/admin/admin.html (UPDATED)
|
||||
• New card: "🔒 HTTPS Configuration"
|
||||
• Links to HTTPS configuration page
|
||||
• Gradient design with lock icon
|
||||
|
||||
5. 🔄 DATABASE MIGRATION
|
||||
└─ migrations/add_https_config_table.py
|
||||
• Creates https_config table
|
||||
• Sets up indexes and constraints
|
||||
• Audit trail fields
|
||||
|
||||
6. 🖥️ CLI UTILITY
|
||||
└─ https_manager.py
|
||||
• Command-line interface
|
||||
• Commands: status, enable, disable, show
|
||||
• Useful for automation and scripting
|
||||
|
||||
7. 🚀 SETUP SCRIPT
|
||||
└─ setup_https.sh
|
||||
• Automated setup script
|
||||
• Runs database migration
|
||||
• Displays step-by-step instructions
|
||||
|
||||
8. 📚 DOCUMENTATION
|
||||
├─ HTTPS_CONFIGURATION.md (Comprehensive guide)
|
||||
├─ HTTPS_IMPLEMENTATION_SUMMARY.md (Architecture & details)
|
||||
└─ HTTPS_QUICK_REFERENCE.md (Admin quick start)
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
✅ UPDATED FILES (3 modified files)
|
||||
───────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
1. ✏️ app/models/__init__.py
|
||||
• Added HTTPSConfig import
|
||||
• Exported in __all__ list
|
||||
|
||||
2. ✏️ app/blueprints/admin.py
|
||||
• Imported HTTPSConfig model
|
||||
• Added three new routes for HTTPS management
|
||||
• 160+ lines of new admin functionality
|
||||
|
||||
3. ✏️ app/templates/admin/admin.html
|
||||
• Added HTTPS Configuration card to dashboard
|
||||
• Purple gradient with lock icon
|
||||
|
||||
4. ✏️ Caddyfile
|
||||
• Updated to use domain: digiserver.sibiusb.harting.intra
|
||||
• IP fallback: 10.76.152.164
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
🎯 KEY FEATURES
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
✅ WEB INTERFACE
|
||||
• Enable/Disable HTTPS with toggle switch
|
||||
• Configure hostname, domain, IP address, port
|
||||
• Status display with current settings
|
||||
• Real-time preview of access URLs
|
||||
• User-friendly form with validations
|
||||
• Responsive design for all devices
|
||||
|
||||
✅ CONFIGURATION OPTIONS
|
||||
• Hostname: Short server name
|
||||
• Domain: Full domain name (e.g., digiserver.sibiusb.harting.intra)
|
||||
• IP Address: Server IP (e.g., 10.76.152.164)
|
||||
• Port: HTTPS port (default 443)
|
||||
• Enable/Disable toggle
|
||||
|
||||
✅ SECURITY
|
||||
• Admin-only access with permission checks
|
||||
• Input validation (domain, IP, port)
|
||||
• Admin audit trail (who changed what, when)
|
||||
• Server-side validation
|
||||
• Logged in system logs
|
||||
|
||||
✅ VALIDATION
|
||||
• Domain format validation
|
||||
• IPv4 address validation (0-255 range)
|
||||
• Port range validation (1-65535)
|
||||
• Required field checks
|
||||
• User-friendly error messages
|
||||
|
||||
✅ LOGGING
|
||||
• All configuration changes logged
|
||||
• Admin username recorded
|
||||
• Timestamps for all changes
|
||||
• Searchable in admin dashboard
|
||||
|
||||
✅ INTEGRATION
|
||||
• Works with existing Caddy reverse proxy
|
||||
• Automatic Let's Encrypt SSL certificates
|
||||
• No manual certificate management
|
||||
• Automatic certificate renewal
|
||||
• HTTP/HTTPS dual access
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
🚀 QUICK START (5 Minutes)
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
1️⃣ RUN DATABASE MIGRATION
|
||||
┌─ Option A: Automated
|
||||
│ bash setup_https.sh
|
||||
│
|
||||
└─ Option B: Manual
|
||||
python /app/migrations/add_https_config_table.py
|
||||
|
||||
2️⃣ START APPLICATION
|
||||
docker-compose up -d
|
||||
|
||||
3️⃣ LOG IN AS ADMIN
|
||||
• Navigate to admin panel
|
||||
• Use admin credentials
|
||||
|
||||
4️⃣ CONFIGURE HTTPS
|
||||
• Go to: Admin Panel → 🔒 HTTPS Configuration
|
||||
• Toggle: Enable HTTPS ✅
|
||||
• Fill in:
|
||||
- Hostname: digiserver
|
||||
- Domain: digiserver.sibiusb.harting.intra
|
||||
- IP: 10.76.152.164
|
||||
- Port: 443
|
||||
• Click: Save HTTPS Configuration
|
||||
|
||||
5️⃣ VERIFY
|
||||
• Check status shows "✅ HTTPS ENABLED"
|
||||
• Access via: https://digiserver.sibiusb.harting.intra
|
||||
• Fallback: http://10.76.152.164
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
📋 DATABASE SCHEMA
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
TABLE: https_config
|
||||
┌─────────────────┬──────────────┬──────────────────────────────────────┐
|
||||
│ Column │ Type │ Purpose │
|
||||
├─────────────────┼──────────────┼──────────────────────────────────────┤
|
||||
│ id │ INTEGER (PK) │ Primary key │
|
||||
│ https_enabled │ BOOLEAN │ Enable/disable HTTPS │
|
||||
│ hostname │ STRING(255) │ Server hostname (e.g., digiserver) │
|
||||
│ domain │ STRING(255) │ Domain (e.g., domain.local) │
|
||||
│ ip_address │ STRING(45) │ IP address (IPv4/IPv6) │
|
||||
│ port │ INTEGER │ HTTPS port (default 443) │
|
||||
│ created_at │ DATETIME │ Creation timestamp │
|
||||
│ updated_at │ DATETIME │ Last update timestamp │
|
||||
│ updated_by │ STRING(255) │ Admin who made change │
|
||||
└─────────────────┴──────────────┴──────────────────────────────────────┘
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
🔐 SECURITY FEATURES
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
✅ Access Control
|
||||
• Admin-only routes with @admin_required decorator
|
||||
• Permission checks on all endpoints
|
||||
• Login required for configuration access
|
||||
|
||||
✅ Input Validation
|
||||
• Domain format validation
|
||||
• IP address validation (IPv4/IPv6)
|
||||
• Port range validation (1-65535)
|
||||
• Required field validation
|
||||
• Error messages for invalid inputs
|
||||
|
||||
✅ SSL/TLS Management
|
||||
• Automatic Let's Encrypt certificates
|
||||
• Automatic renewal before expiration
|
||||
• Security headers (HSTS, X-Frame-Options, etc.)
|
||||
• HTTP/2 and HTTP/3 support via Caddy
|
||||
|
||||
✅ Audit Trail
|
||||
• All changes logged with timestamp
|
||||
• Admin username recorded
|
||||
• Enable/disable events tracked
|
||||
• Searchable in server logs
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
🛠️ ADMIN COMMANDS
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
CLI UTILITY: https_manager.py
|
||||
───────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Show Status:
|
||||
python https_manager.py status
|
||||
|
||||
Enable HTTPS:
|
||||
python https_manager.py enable digiserver digiserver.sibiusb.harting.intra 10.76.152.164 443
|
||||
|
||||
Disable HTTPS:
|
||||
python https_manager.py disable
|
||||
|
||||
Show Configuration:
|
||||
python https_manager.py show
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
📊 ACCESS POINTS
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
AFTER CONFIGURATION:
|
||||
|
||||
┌─ HTTPS (Recommended) ────────────────────────────────────────────┐
|
||||
│ URL: https://digiserver.sibiusb.harting.intra │
|
||||
│ Protocol: HTTPS (SSL/TLS) │
|
||||
│ Port: 443 │
|
||||
│ Certificate: Let's Encrypt (auto-renewed) │
|
||||
│ Use: All secure connections, recommended for everyone │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─ HTTP (Fallback) ────────────────────────────────────────────────┐
|
||||
│ URL: http://10.76.152.164 │
|
||||
│ Protocol: HTTP (plain text) │
|
||||
│ Port: 80 │
|
||||
│ Use: Troubleshooting, direct IP access, local network │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
📚 DOCUMENTATION FILES
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
1. HTTPS_QUICK_REFERENCE.md
|
||||
• Quick setup guide (5 minutes)
|
||||
• Admin checklist
|
||||
• Common tasks
|
||||
• Troubleshooting basics
|
||||
• STATUS: ⭐ START HERE!
|
||||
|
||||
2. HTTPS_CONFIGURATION.md
|
||||
• Comprehensive feature guide
|
||||
• Step-by-step workflow
|
||||
• Configuration details
|
||||
• Prerequisites and requirements
|
||||
• Integration overview
|
||||
• Troubleshooting guide
|
||||
• STATUS: For detailed reference
|
||||
|
||||
3. HTTPS_IMPLEMENTATION_SUMMARY.md
|
||||
• Architecture and design
|
||||
• Files created/modified
|
||||
• Database schema
|
||||
• Integration details
|
||||
• Implementation checklist
|
||||
• STATUS: For developers
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
✅ WORKFLOW
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
INITIAL STATE (HTTP ONLY)
|
||||
┌─────────────────────┐
|
||||
│ App on Port 80 │
|
||||
│ HTTP only │
|
||||
└────────┬────────────┘
|
||||
│
|
||||
└─ Accessible at: http://10.76.152.164
|
||||
|
||||
USER CONFIGURES HTTPS
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Admin Sets: │
|
||||
│ • Hostname: digiserver │
|
||||
│ • Domain: digiserver.sibiusb.harting.intra │
|
||||
│ • IP: 10.76.152.164 │
|
||||
│ • Port: 443 │
|
||||
└────────┬────────────────────────────────────┘
|
||||
│
|
||||
↓
|
||||
CONFIGURATION SAVED
|
||||
┌──────────────────────────────────────────────┐
|
||||
│ • Settings stored in database │
|
||||
│ • Change logged with admin name & timestamp │
|
||||
│ • Status updated in admin panel │
|
||||
└────────┬─────────────────────────────────────┘
|
||||
│
|
||||
↓
|
||||
SYSTEM OPERATIONAL
|
||||
├─ HTTPS Active (Port 443)
|
||||
│ URL: https://digiserver.sibiusb.harting.intra
|
||||
│ Certificate: Auto-managed by Let's Encrypt
|
||||
│
|
||||
└─ HTTP Fallback (Port 80)
|
||||
URL: http://10.76.152.164
|
||||
For troubleshooting and backup access
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
✨ HIGHLIGHTS
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
🎯 USER EXPERIENCE
|
||||
• No manual configuration needed
|
||||
• Simple toggle to enable/disable
|
||||
• Real-time preview of settings
|
||||
• Status display shows current state
|
||||
• Mobile-responsive interface
|
||||
|
||||
🔒 SECURITY
|
||||
• Admin-only access
|
||||
• Input validation on all fields
|
||||
• Audit trail of all changes
|
||||
• Automatic SSL certificates
|
||||
• No sensitive data stored in plain text
|
||||
|
||||
⚙️ FLEXIBILITY
|
||||
• Configurable hostname, domain, IP
|
||||
• Custom port support
|
||||
• Enable/disable without data loss
|
||||
• CLI and web interface both available
|
||||
• Works with existing Caddy setup
|
||||
|
||||
📊 MONITORING
|
||||
• Status endpoint for integration
|
||||
• Logged changes in server logs
|
||||
• Admin dashboard status display
|
||||
• CLI status command
|
||||
|
||||
🚀 AUTOMATION
|
||||
• CLI interface for scripting
|
||||
• Can be automated via setup scripts
|
||||
• Supports headless configuration
|
||||
• REST API endpoint for status
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
📋 CHECKLIST
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
IMPLEMENTATION
|
||||
✅ Database model created (https_config.py)
|
||||
✅ Admin routes added (3 new endpoints)
|
||||
✅ Admin template created (https_config.html)
|
||||
✅ Dashboard card added
|
||||
✅ Database migration created
|
||||
✅ CLI utility implemented
|
||||
✅ Setup script created
|
||||
✅ Documentation completed (3 guides)
|
||||
✅ Code integrated with existing system
|
||||
✅ Admin-only access enforced
|
||||
✅ Input validation implemented
|
||||
✅ Logging implemented
|
||||
✅ Error handling added
|
||||
|
||||
DEPLOYMENT
|
||||
⏳ Run database migration: python migrations/add_https_config_table.py
|
||||
⏳ Start application: docker-compose up -d
|
||||
⏳ Configure via admin panel
|
||||
⏳ Verify access points
|
||||
⏳ Check status display
|
||||
⏳ Review logs for changes
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
🎉 SYSTEM READY
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
All files have been created and integrated.
|
||||
The HTTPS configuration management system is complete and ready to use.
|
||||
|
||||
NEXT STEPS:
|
||||
1. Run database migration
|
||||
2. Restart application
|
||||
3. Access admin panel
|
||||
4. Navigate to HTTPS Configuration
|
||||
5. Enable and configure HTTPS settings
|
||||
6. Verify access points
|
||||
|
||||
For detailed instructions, see: HTTPS_QUICK_REFERENCE.md
|
||||
|
||||
═══════════════════════════════════════════════════════════════════════════════
|
||||
564
QUICK_DEPLOYMENT.md
Normal file
@@ -0,0 +1,564 @@
|
||||
# DigiServer v2 - Quick Deployment Guide
|
||||
|
||||
## 📋 Overview
|
||||
|
||||
DigiServer is deployed using Docker Compose with the following architecture:
|
||||
|
||||
```
|
||||
Internet (User)
|
||||
↓
|
||||
Nginx Reverse Proxy (Port 80/443)
|
||||
↓
|
||||
Internal Docker Network
|
||||
↓
|
||||
Flask App (Gunicorn on Port 5000)
|
||||
↓
|
||||
SQLite Database
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Complete Deployment Workflow
|
||||
|
||||
### **1️⃣ Clone & Setup**
|
||||
```bash
|
||||
# Copy the app folder from repository
|
||||
git clone <repository>
|
||||
cd digiserver-v2
|
||||
|
||||
# Copy environment file and modify as needed
|
||||
cp .env.example .env
|
||||
|
||||
# Edit .env with your configuration:
|
||||
nano .env
|
||||
```
|
||||
|
||||
**Configure in .env:**
|
||||
```env
|
||||
SECRET_KEY=your-secret-key-change-this
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=your-secure-password
|
||||
DOMAIN=your-domain.com
|
||||
EMAIL=admin@your-domain.com
|
||||
IP_ADDRESS=192.168.0.111
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **2️⃣ Deploy via Script**
|
||||
```bash
|
||||
# Run the deployment script
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
This automatically:
|
||||
1. ✅ Creates `data/` directories (instance, uploads, nginx-ssl, etc.)
|
||||
2. ✅ Copies nginx configs from repo root to `data/`
|
||||
3. ✅ Starts Docker containers
|
||||
4. ✅ Initializes database
|
||||
5. ✅ Runs all migrations
|
||||
6. ✅ Configures HTTPS with SSL certificates
|
||||
7. ✅ Displays access information
|
||||
|
||||
**Output shows:**
|
||||
- Access URLs (HTTP/HTTPS)
|
||||
- Default credentials
|
||||
- Next steps for configuration
|
||||
|
||||
---
|
||||
|
||||
### **3️⃣ Network Migration (When Network Changes)**
|
||||
|
||||
When moving the server to a different network with a new IP:
|
||||
|
||||
```bash
|
||||
# Migrate to the new network IP
|
||||
./migrate_network.sh 10.55.150.160
|
||||
|
||||
# Optional: with custom hostname
|
||||
./migrate_network.sh 10.55.150.160 digiserver-secured
|
||||
```
|
||||
|
||||
This automatically:
|
||||
1. ✅ Regenerates SSL certificates for new IP
|
||||
2. ✅ Updates database HTTPS configuration
|
||||
3. ✅ Restarts nginx and app containers
|
||||
4. ✅ Verifies HTTPS connectivity
|
||||
|
||||
---
|
||||
|
||||
### **4️⃣ Normal Operations**
|
||||
|
||||
**Restart containers:**
|
||||
```bash
|
||||
docker compose restart
|
||||
```
|
||||
|
||||
**Stop containers:**
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
|
||||
**View logs:**
|
||||
```bash
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
**View container status:**
|
||||
```bash
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Container Architecture
|
||||
|
||||
### **Container 1: digiserver-app (Flask)**
|
||||
- **Image**: Built from Dockerfile (Python 3.13)
|
||||
- **Port**: 5000 (internal only)
|
||||
- **Volumes**:
|
||||
- `./data:/app` - Persistent application data
|
||||
- `./data/instance:/app/instance` - Database & configuration
|
||||
- `./data/uploads:/app/app/static/uploads` - User uploads
|
||||
- **Startup**: Automatically initializes database on first run
|
||||
- **Health Check**: Every 30 seconds
|
||||
|
||||
### **Container 2: nginx (Reverse Proxy)**
|
||||
- **Image**: nginx:alpine
|
||||
- **Ports**: 80 & 443 (exposed to internet)
|
||||
- **Volumes**:
|
||||
- `nginx.conf` - Main configuration
|
||||
- `./data/nginx-ssl/` - SSL certificates
|
||||
- `./data/nginx-logs/` - Access/error logs
|
||||
- `./data/certbot/` - Let's Encrypt challenges
|
||||
- **Startup**: Waits for Flask app to start
|
||||
- **Health Check**: Every 30 seconds
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Deployment Steps Explained
|
||||
|
||||
### **Step 1: Start Containers**
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
- Builds Flask app image (if needed)
|
||||
- Starts both containers
|
||||
- Waits for containers to be healthy
|
||||
|
||||
### **Step 2: Database Initialization** (docker-entrypoint.sh)
|
||||
When Flask container starts:
|
||||
1. Create required directories
|
||||
2. Check if database exists
|
||||
3. If NOT exists:
|
||||
- Initialize SQLite database (dashboard.db)
|
||||
- Create admin user from environment variables
|
||||
4. Start Gunicorn server (4 workers, 120s timeout)
|
||||
|
||||
### **Step 3: Run Migrations**
|
||||
```bash
|
||||
docker-compose exec -T digiserver-app python /app/migrations/[migration_name].py
|
||||
```
|
||||
Applied migrations:
|
||||
- `add_https_config_table.py` - HTTPS settings
|
||||
- `add_player_user_table.py` - Player user management
|
||||
- `add_email_to_https_config.py` - Email configuration
|
||||
- `migrate_player_user_global.py` - Global settings
|
||||
|
||||
### **Step 4: Configure HTTPS**
|
||||
- SSL certificates stored in `./data/nginx-ssl/`
|
||||
- Pre-generated self-signed certs for development
|
||||
- Ready for Let's Encrypt integration
|
||||
|
||||
### **Step 5: Reverse Proxy Routing** (nginx.conf)
|
||||
```
|
||||
HTTP (80):
|
||||
• Redirect all traffic to HTTPS
|
||||
• Allow ACME challenges for Let's Encrypt
|
||||
|
||||
HTTPS (443):
|
||||
• TLS 1.2+, HTTP/2 enabled
|
||||
• Proxy all requests to Flask app
|
||||
• Security headers added
|
||||
• Gzip compression enabled
|
||||
• Max upload size: 2GB
|
||||
• Proxy timeout: 300s
|
||||
```
|
||||
|
||||
### **Step 6: ProxyFix Middleware** (app/app.py)
|
||||
Extracts real client information from Nginx headers:
|
||||
- `X-Forwarded-For` → Real client IP
|
||||
- `X-Forwarded-Proto` → Protocol (http/https)
|
||||
- `X-Forwarded-Host` → Original hostname
|
||||
- `X-Forwarded-Port` → Original port
|
||||
|
||||
---
|
||||
|
||||
## 📂 Directory Structure & Persistence
|
||||
|
||||
```
|
||||
/srv/digiserver-v2/
|
||||
├── app/ (Flask application code)
|
||||
├── data/ (PERSISTENT - mounted as Docker volume)
|
||||
│ ├── app/ (Copy of app/ for container)
|
||||
│ ├── instance/ (dashboard.db - SQLite database)
|
||||
│ ├── uploads/ (User uploaded files)
|
||||
│ ├── nginx-ssl/ (SSL certificates)
|
||||
│ ├── nginx-logs/ (Nginx logs)
|
||||
│ └── certbot/ (Let's Encrypt challenges)
|
||||
├── migrations/ (Database schema updates)
|
||||
├── docker-compose.yml (Container orchestration)
|
||||
├── Dockerfile (Flask app image definition)
|
||||
├── nginx.conf (Reverse proxy configuration)
|
||||
└── docker-entrypoint.sh (Container startup script)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Default Credentials
|
||||
|
||||
```
|
||||
Username: admin
|
||||
Password: admin123
|
||||
```
|
||||
|
||||
⚠️ **CHANGE IMMEDIATELY IN PRODUCTION!**
|
||||
|
||||
---
|
||||
|
||||
## 🌐 Access Points
|
||||
|
||||
After deployment, access the app at:
|
||||
- `https://localhost` (if deployed locally)
|
||||
- `https://192.168.0.121` (if deployed on server)
|
||||
- `https://<DOMAIN>` (if DNS configured)
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Environment Variables (Optional)
|
||||
|
||||
Create `.env` file in project root:
|
||||
|
||||
```bash
|
||||
# Network Configuration
|
||||
HOSTNAME=digiserver
|
||||
DOMAIN=digiserver.example.com
|
||||
IP_ADDRESS=192.168.0.121
|
||||
|
||||
# SSL/HTTPS
|
||||
EMAIL=admin@example.com
|
||||
|
||||
# Flask Configuration
|
||||
SECRET_KEY=your-secret-key-here
|
||||
FLASK_ENV=production
|
||||
|
||||
# Admin User
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=admin123
|
||||
```
|
||||
|
||||
Then start with:
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Common Commands
|
||||
|
||||
### **Start/Stop Containers**
|
||||
```bash
|
||||
# Start containers
|
||||
docker-compose up -d
|
||||
|
||||
# Stop containers
|
||||
docker-compose down
|
||||
|
||||
# Restart containers
|
||||
docker-compose restart
|
||||
|
||||
# Restart specific container
|
||||
docker-compose restart digiserver-app
|
||||
docker-compose restart nginx
|
||||
```
|
||||
|
||||
### **View Logs**
|
||||
```bash
|
||||
# All containers
|
||||
docker-compose logs
|
||||
|
||||
# Follow logs (real-time)
|
||||
docker-compose logs -f
|
||||
|
||||
# Specific container
|
||||
docker-compose logs -f digiserver-app
|
||||
docker-compose logs -f nginx
|
||||
|
||||
# Show last 50 lines
|
||||
docker-compose logs --tail=50 digiserver-app
|
||||
```
|
||||
|
||||
### **Container Status**
|
||||
```bash
|
||||
# Show running containers
|
||||
docker-compose ps
|
||||
|
||||
# Show container details
|
||||
docker-compose ps -a
|
||||
|
||||
# Check container health
|
||||
docker ps --format="table {{.Names}}\t{{.Status}}"
|
||||
```
|
||||
|
||||
### **Database Operations**
|
||||
```bash
|
||||
# Access database shell
|
||||
docker-compose exec digiserver-app sqlite3 /app/instance/dashboard.db
|
||||
|
||||
# Backup database
|
||||
docker-compose exec digiserver-app cp /app/instance/dashboard.db /app/instance/dashboard.db.backup
|
||||
|
||||
# Restore database
|
||||
docker-compose exec digiserver-app cp /app/instance/dashboard.db.backup /app/instance/dashboard.db
|
||||
```
|
||||
|
||||
### **Nginx Operations**
|
||||
```bash
|
||||
# Validate Nginx configuration
|
||||
docker exec digiserver-nginx nginx -t
|
||||
|
||||
# Reload Nginx (without restart)
|
||||
docker exec digiserver-nginx nginx -s reload
|
||||
|
||||
# View Nginx logs
|
||||
docker-compose logs -f nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 SSL Certificate Management
|
||||
|
||||
### **Generate New Self-Signed Certificate**
|
||||
```bash
|
||||
bash generate_nginx_certs.sh 192.168.0.121 365
|
||||
docker-compose restart nginx
|
||||
```
|
||||
|
||||
Parameters:
|
||||
- `192.168.0.121` - Domain/IP for certificate
|
||||
- `365` - Certificate validity in days
|
||||
|
||||
### **Set Up Let's Encrypt (Production)**
|
||||
1. Update `DOMAIN` and `EMAIL` in environment
|
||||
2. Modify `nginx.conf` to enable certbot challenges
|
||||
3. Run certbot:
|
||||
```bash
|
||||
docker run --rm -v $(pwd)/data/certbot:/etc/letsencrypt \
|
||||
-v $(pwd)/data/nginx-logs:/var/log/letsencrypt \
|
||||
certbot/certbot certonly --webroot \
|
||||
-w /var/www/certbot \
|
||||
-d yourdomain.com \
|
||||
-m your-email@example.com \
|
||||
--agree-tos
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### **Containers not starting?**
|
||||
```bash
|
||||
# Check docker-compose logs
|
||||
docker-compose logs
|
||||
|
||||
# Check system resources
|
||||
docker stats
|
||||
|
||||
# Restart Docker daemon
|
||||
sudo systemctl restart docker
|
||||
```
|
||||
|
||||
### **Application not responding?**
|
||||
```bash
|
||||
# Check app container health
|
||||
docker-compose ps
|
||||
|
||||
# View app logs
|
||||
docker-compose logs -f digiserver-app
|
||||
|
||||
# Test Flask directly
|
||||
docker-compose exec digiserver-app curl http://localhost:5000/
|
||||
```
|
||||
|
||||
### **HTTPS not working?**
|
||||
```bash
|
||||
# Verify Nginx config
|
||||
docker exec digiserver-nginx nginx -t
|
||||
|
||||
# Check SSL certificates exist
|
||||
ls -la ./data/nginx-ssl/
|
||||
|
||||
# View Nginx error logs
|
||||
docker-compose logs nginx
|
||||
```
|
||||
|
||||
### **Database issues?**
|
||||
```bash
|
||||
# Check database file exists
|
||||
ls -la ./data/instance/dashboard.db
|
||||
|
||||
# Verify database permissions
|
||||
docker-compose exec digiserver-app ls -la /app/instance/
|
||||
|
||||
# Check database tables
|
||||
docker-compose exec digiserver-app sqlite3 /app/instance/dashboard.db ".tables"
|
||||
```
|
||||
|
||||
### **Port already in use?**
|
||||
```bash
|
||||
# Find process using port 80
|
||||
sudo lsof -i :80
|
||||
|
||||
# Find process using port 443
|
||||
sudo lsof -i :443
|
||||
|
||||
# Kill process (if needed)
|
||||
sudo kill -9 <PID>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Health Checks
|
||||
|
||||
Both containers have health checks:
|
||||
|
||||
**Flask App**: Pings `http://localhost:5000/` every 30 seconds
|
||||
**Nginx**: Pings `http://localhost:80/` every 30 seconds
|
||||
|
||||
Check health status:
|
||||
```bash
|
||||
docker-compose ps
|
||||
# Look for "Up (healthy)" status
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Database Backup & Restore
|
||||
|
||||
### **Backup**
|
||||
```bash
|
||||
# Create backup
|
||||
docker-compose exec digiserver-app cp /app/instance/dashboard.db /app/instance/dashboard.backup.db
|
||||
|
||||
# Download to local machine
|
||||
cp ./data/instance/dashboard.backup.db ./backup/
|
||||
```
|
||||
|
||||
### **Restore**
|
||||
```bash
|
||||
# Stop containers
|
||||
docker-compose stop
|
||||
|
||||
# Restore database
|
||||
cp ./backup/dashboard.backup.db ./data/instance/dashboard.db
|
||||
|
||||
# Start containers
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 Performance Tuning
|
||||
|
||||
### **Gunicorn Workers** (docker-entrypoint.sh)
|
||||
```bash
|
||||
# Default: 4 workers
|
||||
# Formula: (2 × CPU_count) + 1
|
||||
# For 4-core CPU: 9 workers
|
||||
|
||||
# Modify docker-entrypoint.sh:
|
||||
gunicorn --workers 9 ...
|
||||
```
|
||||
|
||||
### **Nginx Worker Processes** (nginx.conf)
|
||||
```nginx
|
||||
# Default: auto (CPU count)
|
||||
worker_processes auto;
|
||||
|
||||
# Or specify manually:
|
||||
worker_processes 4;
|
||||
```
|
||||
|
||||
### **Upload Timeout** (nginx.conf)
|
||||
```nginx
|
||||
# Default: 300s
|
||||
proxy_connect_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_read_timeout 300s;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Security Checklist
|
||||
|
||||
- [ ] Change admin password immediately
|
||||
- [ ] Set strong `SECRET_KEY` environment variable
|
||||
- [ ] Enable firewall rules (allow only ports 80, 443)
|
||||
- [ ] Set up HTTPS with Let's Encrypt
|
||||
- [ ] Configure regular database backups
|
||||
- [ ] Review Nginx security headers
|
||||
- [ ] Update Flask dependencies regularly
|
||||
- [ ] Monitor container logs for errors
|
||||
- [ ] Restrict admin panel access (IP whitelist optional)
|
||||
- [ ] Enable Flask debug mode only in development
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support & Documentation
|
||||
|
||||
- **Nginx Setup**: See `NGINX_SETUP_QUICK.md`
|
||||
- **ProxyFix Configuration**: See `PROXY_FIX_SETUP.md`
|
||||
- **Deployment Commands**: See `DEPLOYMENT_COMMANDS.md`
|
||||
- **Issue Troubleshooting**: Check `old_code_documentation/`
|
||||
|
||||
---
|
||||
|
||||
## ✅ Deployment Checklist
|
||||
|
||||
- [ ] Docker & Docker Compose installed
|
||||
- [ ] Running from `/srv/digiserver-v2` directory
|
||||
- [ ] Environment variables configured (optional)
|
||||
- [ ] Port 80/443 available
|
||||
- [ ] Sufficient disk space (min 5GB)
|
||||
- [ ] Sufficient RAM (min 2GB free)
|
||||
- [ ] Network connectivity verified
|
||||
- [ ] SSL certificates generated or obtained
|
||||
- [ ] Admin credentials changed (production)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Next Steps After Deployment
|
||||
|
||||
1. **Access Web Interface**
|
||||
- Login with admin credentials
|
||||
- Change password immediately
|
||||
|
||||
2. **Configure Application**
|
||||
- Set up players
|
||||
- Upload content
|
||||
- Configure groups & permissions
|
||||
|
||||
3. **Production Hardening**
|
||||
- Enable Let's Encrypt HTTPS
|
||||
- Configure firewall rules
|
||||
- Set up database backups
|
||||
- Monitor logs
|
||||
|
||||
4. **Optional Enhancements**
|
||||
- Set up custom domain
|
||||
- Configure email notifications
|
||||
- Install optional dependencies (LibreOffice, etc.)
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: January 15, 2026
|
||||
120
QUICK_START.md
@@ -1,120 +0,0 @@
|
||||
# 🚀 DigiServer Deployment - Quick Reference Card
|
||||
|
||||
## Instant Deployment
|
||||
|
||||
```bash
|
||||
cd /path/to/digiserver-v2
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
That's it! ✅
|
||||
|
||||
---
|
||||
|
||||
## 📖 Documentation Files
|
||||
|
||||
| File | Purpose | Size |
|
||||
|------|---------|------|
|
||||
| [DEPLOYMENT_INDEX.md](DEPLOYMENT_INDEX.md) | Navigation guide | 8.1 KB |
|
||||
| [DEPLOYMENT_README.md](DEPLOYMENT_README.md) | Complete guide | 9.4 KB |
|
||||
| [DOCKER_EXEC_COMMANDS.md](DOCKER_EXEC_COMMANDS.md) | Command reference | 7.6 KB |
|
||||
| [DEPLOYMENT_COMMANDS.md](DEPLOYMENT_COMMANDS.md) | Detailed guide | 6.8 KB |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Executable Scripts
|
||||
|
||||
| Script | Purpose | Time |
|
||||
|--------|---------|------|
|
||||
| [deploy.sh](deploy.sh) | Fully automated | 2-3 min |
|
||||
| [setup_https.sh](setup_https.sh) | Semi-automated | 3-5 min |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Common Commands
|
||||
|
||||
### Check Status
|
||||
```bash
|
||||
docker-compose ps
|
||||
```
|
||||
|
||||
### View Logs
|
||||
```bash
|
||||
docker-compose logs -f digiserver-app
|
||||
```
|
||||
|
||||
### Verify HTTPS Configuration
|
||||
```bash
|
||||
docker-compose exec -T digiserver-app python /app/https_manager.py status
|
||||
```
|
||||
|
||||
### Access the Application
|
||||
```
|
||||
https://digiserver.sibiusb.harting.intra
|
||||
https://10.76.152.164
|
||||
https://digiserver
|
||||
```
|
||||
|
||||
### Default Login
|
||||
```
|
||||
Username: admin
|
||||
Password: admin123
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Troubleshooting
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Containers won't start | `docker-compose logs` |
|
||||
| Migration fails | Check DB connection, see docs |
|
||||
| HTTPS errors | Clear Caddy cache: `docker volume rm digiserver-v2_caddy-*` |
|
||||
| Port conflict | `lsof -i :443` or change in docker-compose.yml |
|
||||
|
||||
See [DEPLOYMENT_README.md#-troubleshooting](DEPLOYMENT_README.md#-troubleshooting) for full guide.
|
||||
|
||||
---
|
||||
|
||||
## 🌍 Deploy on Different PC
|
||||
|
||||
1. Copy project files
|
||||
2. Install Docker & Docker Compose
|
||||
3. Run `./deploy.sh`
|
||||
|
||||
Done! 🎉
|
||||
|
||||
---
|
||||
|
||||
## 🔑 Customize Deployment
|
||||
|
||||
```bash
|
||||
HOSTNAME=myserver \
|
||||
DOMAIN=myserver.internal \
|
||||
IP_ADDRESS=192.168.1.100 \
|
||||
EMAIL=admin@example.com \
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Need More Help?
|
||||
|
||||
- **First time?** → [DEPLOYMENT_README.md](DEPLOYMENT_README.md)
|
||||
- **Need a command?** → [DOCKER_EXEC_COMMANDS.md](DOCKER_EXEC_COMMANDS.md)
|
||||
- **Lost?** → [DEPLOYMENT_INDEX.md](DEPLOYMENT_INDEX.md)
|
||||
- **Troubleshooting?** → [DEPLOYMENT_README.md#-troubleshooting](DEPLOYMENT_README.md#-troubleshooting)
|
||||
|
||||
---
|
||||
|
||||
## ✨ What You Get
|
||||
|
||||
✅ Web application with admin dashboard
|
||||
✅ HTTPS with self-signed certificates
|
||||
✅ User management system
|
||||
✅ Player & content management
|
||||
✅ Fully configured & ready to use
|
||||
|
||||
---
|
||||
|
||||
**Ready to deploy?** `./deploy.sh` 🚀
|
||||
19
app/app.py
Executable file → Normal file
@@ -4,6 +4,7 @@ Modern Flask application with blueprint architecture
|
||||
"""
|
||||
import os
|
||||
from flask import Flask, render_template
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from app.config import DevelopmentConfig, ProductionConfig, TestingConfig
|
||||
@@ -37,6 +38,10 @@ def create_app(config_name=None):
|
||||
|
||||
app.config.from_object(config)
|
||||
|
||||
# Apply ProxyFix middleware for reverse proxy (Nginx/Caddy)
|
||||
# This ensures proper handling of X-Forwarded-* headers
|
||||
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1)
|
||||
|
||||
# Initialize extensions
|
||||
db.init_app(app)
|
||||
bcrypt.init_app(app)
|
||||
@@ -47,6 +52,18 @@ def create_app(config_name=None):
|
||||
# Configure Flask-Login
|
||||
configure_login_manager(app)
|
||||
|
||||
# Initialize CORS for player API access
|
||||
from app.extensions import cors
|
||||
cors.init_app(app, resources={
|
||||
r"/api/*": {
|
||||
"origins": ["*"],
|
||||
"methods": ["GET", "POST", "OPTIONS", "PUT", "DELETE"],
|
||||
"allow_headers": ["Content-Type", "Authorization"],
|
||||
"supports_credentials": True,
|
||||
"max_age": 3600
|
||||
}
|
||||
})
|
||||
|
||||
# Register components
|
||||
register_blueprints(app)
|
||||
register_error_handlers(app)
|
||||
@@ -63,7 +80,6 @@ def register_blueprints(app):
|
||||
from app.blueprints.auth import auth_bp
|
||||
from app.blueprints.admin import admin_bp
|
||||
from app.blueprints.players import players_bp
|
||||
from app.blueprints.groups import groups_bp
|
||||
from app.blueprints.content import content_bp
|
||||
from app.blueprints.playlist import playlist_bp
|
||||
from app.blueprints.api import api_bp
|
||||
@@ -73,7 +89,6 @@ def register_blueprints(app):
|
||||
app.register_blueprint(auth_bp)
|
||||
app.register_blueprint(admin_bp)
|
||||
app.register_blueprint(players_bp)
|
||||
app.register_blueprint(groups_bp)
|
||||
app.register_blueprint(content_bp)
|
||||
app.register_blueprint(playlist_bp)
|
||||
app.register_blueprint(api_bp)
|
||||
|
||||
0
app/blueprints/__init__.py
Executable file → Normal file
@@ -8,9 +8,10 @@ from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from app.extensions import db, bcrypt
|
||||
from app.models import User, Player, Group, Content, ServerLog, Playlist, HTTPSConfig
|
||||
from app.models import User, Player, Content, ServerLog, Playlist, HTTPSConfig
|
||||
from app.utils.logger import log_action
|
||||
from app.utils.caddy_manager import CaddyConfigGenerator
|
||||
from app.utils.nginx_config_reader import get_nginx_status
|
||||
|
||||
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
|
||||
|
||||
@@ -870,10 +871,14 @@ def https_config():
|
||||
db.session.commit()
|
||||
log_action('info', f'HTTPS status auto-corrected to enabled (detected from request)')
|
||||
|
||||
# Get Nginx configuration status
|
||||
nginx_status = get_nginx_status()
|
||||
|
||||
return render_template('admin/https_config.html',
|
||||
config=config,
|
||||
is_https_active=is_https_active,
|
||||
current_host=current_host)
|
||||
current_host=current_host,
|
||||
nginx_status=nginx_status)
|
||||
except Exception as e:
|
||||
log_action('error', f'Error loading HTTPS config page: {str(e)}')
|
||||
flash('Error loading HTTPS configuration page.', 'danger')
|
||||
|
||||
63
app/blueprints/api.py
Executable file → Normal file
@@ -7,7 +7,7 @@ import bcrypt
|
||||
from typing import Optional, Dict, List
|
||||
|
||||
from app.extensions import db, cache
|
||||
from app.models import Player, Group, Content, PlayerFeedback, ServerLog
|
||||
from app.models import Player, Content, PlayerFeedback, ServerLog
|
||||
from app.utils.logger import log_action
|
||||
|
||||
api_bp = Blueprint('api', __name__, url_prefix='/api')
|
||||
@@ -95,6 +95,12 @@ def health_check():
|
||||
})
|
||||
|
||||
|
||||
@api_bp.route('/certificate', methods=['GET'])
|
||||
def get_server_certificate():
|
||||
"""Get server SSL certificate."""
|
||||
return jsonify({'test': 'certificate_endpoint_works'}), 200
|
||||
|
||||
|
||||
@api_bp.route('/auth/player', methods=['POST'])
|
||||
@rate_limit(max_requests=120, window=60)
|
||||
def authenticate_player():
|
||||
@@ -593,31 +599,33 @@ def system_info():
|
||||
return jsonify({'error': 'Internal server error'}), 500
|
||||
|
||||
|
||||
@api_bp.route('/groups', methods=['GET'])
|
||||
@rate_limit(max_requests=60, window=60)
|
||||
def list_groups():
|
||||
"""List all groups with basic information."""
|
||||
try:
|
||||
groups = Group.query.order_by(Group.name).all()
|
||||
|
||||
groups_data = []
|
||||
for group in groups:
|
||||
groups_data.append({
|
||||
'id': group.id,
|
||||
'name': group.name,
|
||||
'description': group.description,
|
||||
'player_count': group.players.count(),
|
||||
'content_count': group.contents.count()
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'groups': groups_data,
|
||||
'count': len(groups_data)
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
log_action('error', f'Error listing groups: {str(e)}')
|
||||
return jsonify({'error': 'Internal server error'}), 500
|
||||
|
||||
# DEPRECATED: Groups functionality has been archived
|
||||
# @api_bp.route('/groups', methods=['GET'])
|
||||
# @rate_limit(max_requests=60, window=60)
|
||||
# def list_groups():
|
||||
# """List all groups with basic information."""
|
||||
# try:
|
||||
# groups = Group.query.order_by(Group.name).all()
|
||||
#
|
||||
# groups_data = []
|
||||
# for group in groups:
|
||||
# groups_data.append({
|
||||
# 'id': group.id,
|
||||
# 'name': group.name,
|
||||
# 'description': group.description,
|
||||
# 'player_count': group.players.count(),
|
||||
# 'content_count': group.contents.count()
|
||||
# })
|
||||
#
|
||||
# return jsonify({
|
||||
# 'groups': groups_data,
|
||||
# 'count': len(groups_data)
|
||||
# })
|
||||
#
|
||||
# except Exception as e:
|
||||
# log_action('error', f'Error listing groups: {str(e)}')
|
||||
# return jsonify({'error': 'Internal server error'}), 500
|
||||
|
||||
|
||||
@api_bp.route('/content', methods=['GET'])
|
||||
@@ -819,6 +827,7 @@ def receive_edited_media():
|
||||
db.session.add(edit_record)
|
||||
|
||||
# Update playlist version to force player refresh
|
||||
playlist = None
|
||||
if player.playlist_id:
|
||||
from app.models.playlist import Playlist
|
||||
playlist = db.session.get(Playlist, player.playlist_id)
|
||||
@@ -839,7 +848,7 @@ def receive_edited_media():
|
||||
'version': version,
|
||||
'old_filename': old_filename,
|
||||
'new_filename': new_filename,
|
||||
'new_playlist_version': playlist.version if player.playlist_id and playlist else None
|
||||
'new_playlist_version': playlist.version if playlist else None
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
|
||||
0
app/blueprints/auth.py
Executable file → Normal file
50
app/blueprints/content.py
Executable file → Normal file
@@ -458,6 +458,56 @@ def update_playlist_content_edit_enabled(playlist_id: int, content_id: int):
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@content_bp.route('/playlist/<int:playlist_id>/update-duration/<int:content_id>', methods=['POST'])
|
||||
@login_required
|
||||
def update_playlist_content_duration(playlist_id: int, content_id: int):
|
||||
"""Update content duration in playlist."""
|
||||
playlist = Playlist.query.get_or_404(playlist_id)
|
||||
|
||||
try:
|
||||
content = Content.query.get_or_404(content_id)
|
||||
|
||||
# Get duration from request
|
||||
try:
|
||||
duration = int(request.form.get('duration', 10))
|
||||
except (ValueError, TypeError):
|
||||
return jsonify({'success': False, 'message': 'Invalid duration value'}), 400
|
||||
|
||||
# Validate duration (minimum 1 second)
|
||||
if duration < 1:
|
||||
return jsonify({'success': False, 'message': 'Duration must be at least 1 second'}), 400
|
||||
|
||||
from app.models.playlist import playlist_content
|
||||
from sqlalchemy import update
|
||||
|
||||
# Update duration in association table
|
||||
stmt = update(playlist_content).where(
|
||||
(playlist_content.c.playlist_id == playlist_id) &
|
||||
(playlist_content.c.content_id == content_id)
|
||||
).values(duration=duration)
|
||||
db.session.execute(stmt)
|
||||
|
||||
# Increment playlist version
|
||||
playlist.increment_version()
|
||||
|
||||
db.session.commit()
|
||||
cache.clear()
|
||||
|
||||
log_action('info', f'Updated duration={duration}s for "{content.filename}" in playlist "{playlist.name}"')
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': 'Duration updated',
|
||||
'duration': duration,
|
||||
'version': playlist.version
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
log_action('error', f'Error updating duration: {str(e)}')
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@content_bp.route('/upload-media-page')
|
||||
@login_required
|
||||
def upload_media_page():
|
||||
|
||||
0
app/blueprints/content_old.py
Executable file → Normal file
0
app/blueprints/main.py
Executable file → Normal file
0
app/blueprints/players.py
Executable file → Normal file
23
app/blueprints/playlist.py
Executable file → Normal file
@@ -16,25 +16,16 @@ playlist_bp = Blueprint('playlist', __name__, url_prefix='/playlist')
|
||||
@playlist_bp.route('/<int:player_id>')
|
||||
@login_required
|
||||
def manage_playlist(player_id: int):
|
||||
"""Manage playlist for a specific player."""
|
||||
"""Legacy route - redirect to new content management area."""
|
||||
player = Player.query.get_or_404(player_id)
|
||||
|
||||
# Get content from player's assigned playlist
|
||||
playlist_items = []
|
||||
if player.playlist_id:
|
||||
playlist = Playlist.query.get(player.playlist_id)
|
||||
if playlist:
|
||||
playlist_items = playlist.get_content_ordered()
|
||||
|
||||
# Get available content (all content not in current playlist)
|
||||
all_content = Content.query.all()
|
||||
playlist_content_ids = {item.id for item in playlist_items}
|
||||
available_content = [c for c in all_content if c.id not in playlist_content_ids]
|
||||
|
||||
return render_template('playlist/manage_playlist.html',
|
||||
player=player,
|
||||
playlist_content=playlist_items,
|
||||
available_content=available_content)
|
||||
# Redirect to the new content management interface
|
||||
return redirect(url_for('content.manage_playlist_content', playlist_id=player.playlist_id))
|
||||
else:
|
||||
# Player has no playlist assigned
|
||||
flash('This player has no playlist assigned.', 'warning')
|
||||
return redirect(url_for('players.manage_player', player_id=player_id))
|
||||
|
||||
|
||||
@playlist_bp.route('/<int:player_id>/add', methods=['POST'])
|
||||
|
||||
6
app/config.py
Executable file → Normal file
@@ -29,6 +29,11 @@ class Config:
|
||||
SESSION_COOKIE_HTTPONLY = True
|
||||
SESSION_COOKIE_SAMESITE = 'Lax'
|
||||
|
||||
# Reverse proxy trust (for Nginx/Caddy with ProxyFix middleware)
|
||||
# These are set by werkzeug.middleware.proxy_fix
|
||||
TRUSTED_PROXIES = os.getenv('TRUSTED_PROXIES', '127.0.0.1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16')
|
||||
PREFERRED_URL_SCHEME = os.getenv('PREFERRED_URL_SCHEME', 'https')
|
||||
|
||||
# Cache
|
||||
SEND_FILE_MAX_AGE_DEFAULT = 300 # 5 minutes for static files
|
||||
|
||||
@@ -86,6 +91,7 @@ class ProductionConfig(Config):
|
||||
|
||||
# Security
|
||||
SESSION_COOKIE_SECURE = True
|
||||
SESSION_COOKIE_SAMESITE = 'Lax'
|
||||
WTF_CSRF_ENABLED = True
|
||||
|
||||
|
||||
|
||||
2
app/extensions.py
Executable file → Normal file
@@ -7,6 +7,7 @@ from flask_bcrypt import Bcrypt
|
||||
from flask_login import LoginManager
|
||||
from flask_migrate import Migrate
|
||||
from flask_caching import Cache
|
||||
from flask_cors import CORS
|
||||
|
||||
# Initialize extensions (will be bound to app in create_app)
|
||||
db = SQLAlchemy()
|
||||
@@ -14,6 +15,7 @@ bcrypt = Bcrypt()
|
||||
login_manager = LoginManager()
|
||||
migrate = Migrate()
|
||||
cache = Cache()
|
||||
cors = CORS()
|
||||
|
||||
# Configure login manager
|
||||
login_manager.login_view = 'auth.login'
|
||||
|
||||
0
app/models/__init__.py
Executable file → Normal file
0
app/models/content.py
Executable file → Normal file
0
app/models/group.py
Executable file → Normal file
0
app/models/player.py
Executable file → Normal file
0
app/models/player_edit.py
Executable file → Normal file
0
app/models/player_feedback.py
Executable file → Normal file
0
app/models/player_user.py
Executable file → Normal file
0
app/models/playlist.py
Executable file → Normal file
0
app/models/server_log.py
Executable file → Normal file
0
app/models/user.py
Executable file → Normal file
0
app/static/icons/edit.svg
Executable file → Normal file
|
Before Width: | Height: | Size: 294 B After Width: | Height: | Size: 294 B |
0
app/static/icons/home.svg
Executable file → Normal file
|
Before Width: | Height: | Size: 311 B After Width: | Height: | Size: 311 B |
0
app/static/icons/info.svg
Executable file → Normal file
|
Before Width: | Height: | Size: 329 B After Width: | Height: | Size: 329 B |
0
app/static/icons/monitor.svg
Executable file → Normal file
|
Before Width: | Height: | Size: 301 B After Width: | Height: | Size: 301 B |
0
app/static/icons/moon.svg
Executable file → Normal file
|
Before Width: | Height: | Size: 257 B After Width: | Height: | Size: 257 B |
0
app/static/icons/playlist.svg
Executable file → Normal file
|
Before Width: | Height: | Size: 259 B After Width: | Height: | Size: 259 B |
0
app/static/icons/sun.svg
Executable file → Normal file
|
Before Width: | Height: | Size: 651 B After Width: | Height: | Size: 651 B |
0
app/static/icons/trash.svg
Executable file → Normal file
|
Before Width: | Height: | Size: 334 B After Width: | Height: | Size: 334 B |
0
app/static/icons/upload.svg
Executable file → Normal file
|
Before Width: | Height: | Size: 345 B After Width: | Height: | Size: 345 B |
0
app/static/icons/warning.svg
Executable file → Normal file
|
Before Width: | Height: | Size: 396 B After Width: | Height: | Size: 396 B |
0
app/static/uploads/.gitkeep
Executable file → Normal file
0
app/templates/admin/admin.html
Executable file → Normal file
0
app/templates/admin/customize_logos.html
Executable file → Normal file
0
app/templates/admin/dependencies.html
Executable file → Normal file
0
app/templates/admin/editing_users.html
Executable file → Normal file
@@ -160,6 +160,95 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Nginx Status Card -->
|
||||
<div class="card nginx-status-card">
|
||||
<h2>🔧 Nginx Reverse Proxy Status</h2>
|
||||
{% if nginx_status.available %}
|
||||
<div class="nginx-status-content">
|
||||
<div class="status-item">
|
||||
<strong>Status:</strong>
|
||||
<span class="badge badge-success">✅ Nginx Configured</span>
|
||||
</div>
|
||||
|
||||
<div class="status-item">
|
||||
<strong>Configuration Path:</strong>
|
||||
<code>{{ nginx_status.path }}</code>
|
||||
</div>
|
||||
|
||||
{% if nginx_status.ssl_enabled %}
|
||||
<div class="status-item">
|
||||
<strong>SSL/TLS:</strong>
|
||||
<span class="badge badge-success">🔒 Enabled</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="status-item">
|
||||
<strong>SSL/TLS:</strong>
|
||||
<span class="badge badge-warning">⚠️ Not Configured</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nginx_status.http_ports %}
|
||||
<div class="status-item">
|
||||
<strong>HTTP Ports:</strong>
|
||||
<code>{{ nginx_status.http_ports|join(', ') }}</code>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nginx_status.https_ports %}
|
||||
<div class="status-item">
|
||||
<strong>HTTPS Ports:</strong>
|
||||
<code>{{ nginx_status.https_ports|join(', ') }}</code>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nginx_status.server_names %}
|
||||
<div class="status-item">
|
||||
<strong>Server Names:</strong>
|
||||
{% for name in nginx_status.server_names %}
|
||||
<code>{{ name }}</code>{% if not loop.last %}<br>{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nginx_status.upstream_servers %}
|
||||
<div class="status-item">
|
||||
<strong>Upstream Servers:</strong>
|
||||
{% for server in nginx_status.upstream_servers %}
|
||||
<code>{{ server }}</code>{% if not loop.last %}<br>{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nginx_status.ssl_protocols %}
|
||||
<div class="status-item">
|
||||
<strong>SSL Protocols:</strong>
|
||||
<code>{{ nginx_status.ssl_protocols|join(', ') }}</code>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nginx_status.client_max_body_size %}
|
||||
<div class="status-item">
|
||||
<strong>Max Body Size:</strong>
|
||||
<code>{{ nginx_status.client_max_body_size }}</code>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if nginx_status.gzip_enabled %}
|
||||
<div class="status-item">
|
||||
<strong>Gzip Compression:</strong>
|
||||
<span class="badge badge-success">✅ Enabled</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="status-disabled">
|
||||
<p>⚠️ <strong>Nginx configuration not accessible</strong></p>
|
||||
<p>Error: {{ nginx_status.error|default('Unknown error') }}</p>
|
||||
<p style="font-size: 12px; color: #666;">Path checked: {{ nginx_status.path }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Information Section -->
|
||||
<div class="card info-card">
|
||||
<h2>ℹ️ Important Information</h2>
|
||||
@@ -466,6 +555,45 @@
|
||||
color: #0066cc;
|
||||
}
|
||||
|
||||
.nginx-status-card {
|
||||
background: linear-gradient(135deg, #f0f7ff 0%, #e7f3ff 100%);
|
||||
border-left: 5px solid #0066cc;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.nginx-status-card h2 {
|
||||
color: #0066cc;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.nginx-status-content {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
padding: 12px;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
border-left: 3px solid #0066cc;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status-item strong {
|
||||
display: inline-block;
|
||||
min-width: 150px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.status-item code {
|
||||
background: #f0f7ff;
|
||||
padding: 4px 8px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #0066cc;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.info-sections {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
|
||||
0
app/templates/admin/leftover_media.html
Executable file → Normal file
0
app/templates/admin/user_management.html
Executable file → Normal file
0
app/templates/auth/change_password.html
Executable file → Normal file
0
app/templates/auth/login.html
Executable file → Normal file
0
app/templates/auth/register.html
Executable file → Normal file
0
app/templates/base.html
Executable file → Normal file
0
app/templates/content/content_list.html
Executable file → Normal file
0
app/templates/content/content_list_new.html
Executable file → Normal file
0
app/templates/content/edit_content.html
Executable file → Normal file
165
app/templates/content/manage_playlist_content.html
Executable file → Normal file
@@ -97,6 +97,67 @@
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Duration spinner control */
|
||||
.duration-spinner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.duration-display {
|
||||
min-width: 60px;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
padding: 6px 12px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.duration-spinner button {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
padding: 0;
|
||||
border: 1px solid #ddd;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.duration-spinner button:hover {
|
||||
background: #f0f0f0;
|
||||
border-color: #999;
|
||||
}
|
||||
|
||||
.duration-spinner button:active {
|
||||
background: #e0e0e0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.duration-spinner button.btn-increase {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.duration-spinner button.btn-decrease {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.audio-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.audio-checkbox {
|
||||
display: none;
|
||||
}
|
||||
@@ -154,6 +215,36 @@
|
||||
body.dark-mode .available-content {
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
/* Dark mode for duration spinner */
|
||||
body.dark-mode .duration-display {
|
||||
background: #2d3748;
|
||||
border-color: #4a5568;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
body.dark-mode .duration-spinner button {
|
||||
background: #2d3748;
|
||||
border-color: #4a5568;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
body.dark-mode .duration-spinner button:hover {
|
||||
background: #4a5568;
|
||||
border-color: #718096;
|
||||
}
|
||||
|
||||
body.dark-mode .duration-spinner button:active {
|
||||
background: #5a6a78;
|
||||
}
|
||||
|
||||
body.dark-mode .duration-spinner button.btn-increase {
|
||||
color: #48bb78;
|
||||
}
|
||||
|
||||
body.dark-mode .duration-spinner button.btn-decrease {
|
||||
color: #f56565;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container" style="max-width: 1400px;">
|
||||
@@ -230,7 +321,27 @@
|
||||
{% elif content.content_type == 'pdf' %}📄 PDF
|
||||
{% else %}📁 Other{% endif %}
|
||||
</td>
|
||||
<td>{{ content._playlist_duration or content.duration }}s</td>
|
||||
<td>
|
||||
<div class="duration-spinner">
|
||||
<button type="button"
|
||||
class="btn-decrease"
|
||||
onclick="event.stopPropagation(); changeDuration({{ content.id }}, -1)"
|
||||
onmousedown="event.stopPropagation()"
|
||||
title="Decrease duration by 1 second">
|
||||
⬇️
|
||||
</button>
|
||||
<div class="duration-display" id="duration-display-{{ content.id }}">
|
||||
{{ content._playlist_duration or content.duration }}s
|
||||
</div>
|
||||
<button type="button"
|
||||
class="btn-increase"
|
||||
onclick="event.stopPropagation(); changeDuration({{ content.id }}, 1)"
|
||||
onmousedown="event.stopPropagation()"
|
||||
title="Increase duration by 1 second">
|
||||
⬆️
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{% if content.content_type == 'video' %}
|
||||
<label class="audio-toggle">
|
||||
@@ -413,6 +524,58 @@ function saveOrder() {
|
||||
});
|
||||
}
|
||||
|
||||
// Change duration with spinner buttons
|
||||
function changeDuration(contentId, change) {
|
||||
const displayElement = document.getElementById(`duration-display-${contentId}`);
|
||||
const currentText = displayElement.textContent;
|
||||
const currentDuration = parseInt(currentText);
|
||||
const newDuration = currentDuration + change;
|
||||
|
||||
// Validate duration (minimum 1 second)
|
||||
if (newDuration < 1) {
|
||||
alert('Duration must be at least 1 second');
|
||||
return;
|
||||
}
|
||||
|
||||
// Update display immediately for visual feedback
|
||||
displayElement.style.opacity = '0.7';
|
||||
displayElement.textContent = newDuration + 's';
|
||||
|
||||
// Save to server
|
||||
const playlistId = {{ playlist.id }};
|
||||
const url = `/content/playlist/${playlistId}/update-duration/${contentId}`;
|
||||
const formData = new FormData();
|
||||
formData.append('duration', newDuration);
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
console.log('Duration updated successfully');
|
||||
displayElement.style.opacity = '1';
|
||||
displayElement.style.color = '#28a745';
|
||||
setTimeout(() => {
|
||||
displayElement.style.color = '';
|
||||
}, 1000);
|
||||
} else {
|
||||
// Revert on error
|
||||
displayElement.textContent = currentDuration + 's';
|
||||
displayElement.style.opacity = '1';
|
||||
alert('Error updating duration: ' + (data.message || 'Unknown error'));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// Revert on error
|
||||
displayElement.textContent = currentDuration + 's';
|
||||
displayElement.style.opacity = '1';
|
||||
console.error('Error:', error);
|
||||
alert('Error updating duration');
|
||||
});
|
||||
}
|
||||
|
||||
function toggleAudio(contentId, enabled) {
|
||||
const muted = !enabled; // Checkbox is "enabled audio", but backend stores "muted"
|
||||
const playlistId = {{ playlist.id }};
|
||||
|
||||
0
app/templates/content/media_library.html
Executable file → Normal file
0
app/templates/content/upload_content.html
Executable file → Normal file
0
app/templates/content/upload_media.html
Executable file → Normal file
0
app/templates/dashboard.html
Executable file → Normal file
0
app/templates/errors/403.html
Executable file → Normal file
0
app/templates/errors/404.html
Executable file → Normal file
0
app/templates/errors/500.html
Executable file → Normal file
0
app/templates/players/add_player.html
Executable file → Normal file
0
app/templates/players/edit_player.html
Executable file → Normal file
0
app/templates/players/edited_media.html
Executable file → Normal file
0
app/templates/players/manage_player.html
Executable file → Normal file
0
app/templates/players/player_fullscreen.html
Executable file → Normal file
0
app/templates/players/player_page.html
Executable file → Normal file
0
app/templates/players/players_list.html
Executable file → Normal file
0
app/utils/__init__.py
Executable file → Normal file
0
app/utils/group_player_management.py
Executable file → Normal file
0
app/utils/logger.py
Executable file → Normal file
120
app/utils/nginx_config_reader.py
Normal file
@@ -0,0 +1,120 @@
|
||||
"""Nginx configuration reader utility."""
|
||||
import os
|
||||
import re
|
||||
from typing import Dict, List, Optional, Any
|
||||
|
||||
|
||||
class NginxConfigReader:
|
||||
"""Read and parse Nginx configuration files."""
|
||||
|
||||
def __init__(self, config_path: str = '/etc/nginx/nginx.conf'):
|
||||
"""Initialize Nginx config reader."""
|
||||
self.config_path = config_path
|
||||
self.config_content = None
|
||||
self.is_available = os.path.exists(config_path)
|
||||
|
||||
if self.is_available:
|
||||
try:
|
||||
with open(config_path, 'r') as f:
|
||||
self.config_content = f.read()
|
||||
except Exception as e:
|
||||
self.is_available = False
|
||||
self.error = str(e)
|
||||
|
||||
def get_status(self) -> Dict[str, Any]:
|
||||
"""Get Nginx configuration status."""
|
||||
if not self.is_available:
|
||||
return {
|
||||
'available': False,
|
||||
'error': 'Nginx configuration not found',
|
||||
'path': self.config_path
|
||||
}
|
||||
|
||||
return {
|
||||
'available': True,
|
||||
'path': self.config_path,
|
||||
'file_exists': os.path.exists(self.config_path),
|
||||
'ssl_enabled': self._check_ssl_enabled(),
|
||||
'http_ports': self._extract_http_ports(),
|
||||
'https_ports': self._extract_https_ports(),
|
||||
'upstream_servers': self._extract_upstream_servers(),
|
||||
'server_names': self._extract_server_names(),
|
||||
'ssl_protocols': self._extract_ssl_protocols(),
|
||||
'client_max_body_size': self._extract_client_max_body_size(),
|
||||
'gzip_enabled': self._check_gzip_enabled(),
|
||||
}
|
||||
|
||||
def _check_ssl_enabled(self) -> bool:
|
||||
"""Check if SSL is enabled."""
|
||||
if not self.config_content:
|
||||
return False
|
||||
return 'ssl_certificate' in self.config_content
|
||||
|
||||
def _extract_http_ports(self) -> List[int]:
|
||||
"""Extract HTTP listening ports."""
|
||||
if not self.config_content:
|
||||
return []
|
||||
pattern = r'listen\s+(\d+)'
|
||||
matches = re.findall(pattern, self.config_content)
|
||||
return sorted(list(set(int(p) for p in matches if int(p) < 1000)))
|
||||
|
||||
def _extract_https_ports(self) -> List[int]:
|
||||
"""Extract HTTPS listening ports."""
|
||||
if not self.config_content:
|
||||
return []
|
||||
pattern = r'listen\s+(\d+).*ssl'
|
||||
matches = re.findall(pattern, self.config_content)
|
||||
return sorted(list(set(int(p) for p in matches)))
|
||||
|
||||
def _extract_upstream_servers(self) -> List[str]:
|
||||
"""Extract upstream servers."""
|
||||
if not self.config_content:
|
||||
return []
|
||||
upstream_match = re.search(r'upstream\s+\w+\s*{([^}]+)}', self.config_content)
|
||||
if upstream_match:
|
||||
upstream_content = upstream_match.group(1)
|
||||
servers = re.findall(r'server\s+([^\s;]+)', upstream_content)
|
||||
return servers
|
||||
return []
|
||||
|
||||
def _extract_server_names(self) -> List[str]:
|
||||
"""Extract server names."""
|
||||
if not self.config_content:
|
||||
return []
|
||||
pattern = r'server_name\s+([^;]+);'
|
||||
matches = re.findall(pattern, self.config_content)
|
||||
result = []
|
||||
for match in matches:
|
||||
names = match.strip().split()
|
||||
result.extend(names)
|
||||
return result
|
||||
|
||||
def _extract_ssl_protocols(self) -> List[str]:
|
||||
"""Extract SSL protocols."""
|
||||
if not self.config_content:
|
||||
return []
|
||||
pattern = r'ssl_protocols\s+([^;]+);'
|
||||
match = re.search(pattern, self.config_content)
|
||||
if match:
|
||||
return match.group(1).strip().split()
|
||||
return []
|
||||
|
||||
def _extract_client_max_body_size(self) -> Optional[str]:
|
||||
"""Extract client max body size."""
|
||||
if not self.config_content:
|
||||
return None
|
||||
pattern = r'client_max_body_size\s+([^;]+);'
|
||||
match = re.search(pattern, self.config_content)
|
||||
return match.group(1).strip() if match else None
|
||||
|
||||
def _check_gzip_enabled(self) -> bool:
|
||||
"""Check if gzip is enabled."""
|
||||
if not self.config_content:
|
||||
return False
|
||||
return bool(re.search(r'gzip\s+on\s*;', self.config_content))
|
||||
|
||||
|
||||
def get_nginx_status() -> Dict[str, Any]:
|
||||
"""Get Nginx configuration status."""
|
||||
reader = NginxConfigReader()
|
||||
return reader.get_status()
|
||||
0
app/utils/pptx_converter.py
Executable file → Normal file
0
app/utils/uploads.py
Executable file → Normal file
62
deploy.sh
@@ -16,10 +16,10 @@ echo -e "${BLUE}║ DigiServer Automated Deployment
|
||||
echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
# Check if docker-compose is available
|
||||
if ! command -v docker-compose &> /dev/null; then
|
||||
echo -e "${RED}❌ docker-compose not found!${NC}"
|
||||
echo "Please install docker-compose first"
|
||||
# Check if docker compose is available
|
||||
if ! docker compose version &> /dev/null; then
|
||||
echo -e "${RED}❌ docker compose not found!${NC}"
|
||||
echo "Please install docker compose first"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -30,6 +30,38 @@ if [ ! -f "docker-compose.yml" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# INITIALIZATION: Create data directories and copy nginx configs
|
||||
# ============================================================================
|
||||
echo -e "${YELLOW}📁 Initializing data directories...${NC}"
|
||||
|
||||
# Create necessary data directories
|
||||
mkdir -p data/instance
|
||||
mkdir -p data/uploads
|
||||
mkdir -p data/nginx-ssl
|
||||
mkdir -p data/nginx-logs
|
||||
mkdir -p data/certbot
|
||||
|
||||
# Copy nginx configuration files from repo root to data folder
|
||||
if [ -f "nginx.conf" ]; then
|
||||
cp nginx.conf data/nginx.conf
|
||||
echo -e " ${GREEN}✓${NC} nginx.conf copied to data/"
|
||||
else
|
||||
echo -e " ${RED}❌ nginx.conf not found in repo root!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "nginx-custom-domains.conf" ]; then
|
||||
cp nginx-custom-domains.conf data/nginx-custom-domains.conf
|
||||
echo -e " ${GREEN}✓${NC} nginx-custom-domains.conf copied to data/"
|
||||
else
|
||||
echo -e " ${RED}❌ nginx-custom-domains.conf not found in repo root!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✅ Data directories initialized${NC}"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# CONFIGURATION VARIABLES
|
||||
# ============================================================================
|
||||
@@ -51,15 +83,15 @@ echo ""
|
||||
# STEP 1: Start containers
|
||||
# ============================================================================
|
||||
echo -e "${YELLOW}📦 [1/6] Starting containers...${NC}"
|
||||
docker-compose up -d
|
||||
docker compose up -d
|
||||
|
||||
echo -e "${YELLOW}⏳ Waiting for containers to be healthy...${NC}"
|
||||
sleep 10
|
||||
|
||||
# Verify containers are running
|
||||
if ! docker-compose ps | grep -q "Up"; then
|
||||
if ! docker compose ps | grep -q "Up"; then
|
||||
echo -e "${RED}❌ Containers failed to start!${NC}"
|
||||
docker-compose logs
|
||||
docker compose logs
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}✅ Containers started successfully${NC}"
|
||||
@@ -71,13 +103,13 @@ echo ""
|
||||
echo -e "${YELLOW}📊 [2/6] Running database migrations...${NC}"
|
||||
|
||||
echo -e " • Creating https_config table..."
|
||||
docker-compose exec -T digiserver-app python /app/migrations/add_https_config_table.py
|
||||
docker compose exec -T digiserver-app python /app/migrations/add_https_config_table.py
|
||||
echo -e " • Creating player_user table..."
|
||||
docker-compose exec -T digiserver-app python /app/migrations/add_player_user_table.py
|
||||
docker compose exec -T digiserver-app python /app/migrations/add_player_user_table.py
|
||||
echo -e " • Adding email to https_config..."
|
||||
docker-compose exec -T digiserver-app python /app/migrations/add_email_to_https_config.py
|
||||
docker compose exec -T digiserver-app python /app/migrations/add_email_to_https_config.py
|
||||
echo -e " • Migrating player_user global settings..."
|
||||
docker-compose exec -T digiserver-app python /app/migrations/migrate_player_user_global.py
|
||||
docker compose exec -T digiserver-app python /app/migrations/migrate_player_user_global.py
|
||||
|
||||
echo -e "${GREEN}✅ All database migrations completed${NC}"
|
||||
echo ""
|
||||
@@ -87,7 +119,7 @@ echo ""
|
||||
# ============================================================================
|
||||
echo -e "${YELLOW}🔒 [3/6] Configuring HTTPS...${NC}"
|
||||
|
||||
docker-compose exec -T digiserver-app python /app/https_manager.py enable \
|
||||
docker compose exec -T digiserver-app python /app/https_manager.py enable \
|
||||
"$HOSTNAME" \
|
||||
"$DOMAIN" \
|
||||
"$EMAIL" \
|
||||
@@ -102,7 +134,7 @@ echo ""
|
||||
# ============================================================================
|
||||
echo -e "${YELLOW}🔍 [4/6] Verifying database setup...${NC}"
|
||||
|
||||
docker-compose exec -T digiserver-app python -c "
|
||||
docker compose exec -T digiserver-app python -c "
|
||||
from app.app import create_app
|
||||
from sqlalchemy import inspect
|
||||
|
||||
@@ -123,7 +155,7 @@ echo ""
|
||||
# ============================================================================
|
||||
echo -e "${YELLOW}🔧 [5/6] Verifying Caddy configuration...${NC}"
|
||||
|
||||
docker-compose exec -T caddy caddy validate --config /etc/caddy/Caddyfile >/dev/null 2>&1
|
||||
docker compose exec -T caddy caddy validate --config /etc/caddy/Caddyfile >/dev/null 2>&1
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e " ${GREEN}✅ Caddy configuration is valid${NC}"
|
||||
else
|
||||
@@ -137,7 +169,7 @@ echo ""
|
||||
echo -e "${YELLOW}📋 [6/6] Displaying configuration summary...${NC}"
|
||||
echo ""
|
||||
|
||||
docker-compose exec -T digiserver-app python /app/https_manager.py status
|
||||
docker compose exec -T digiserver-app python /app/https_manager.py status
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}╔════════════════════════════════════════════════════════════════╗${NC}"
|
||||
|
||||
246
deployment-commands-reference.sh
Normal file
@@ -0,0 +1,246 @@
|
||||
#!/bin/bash
|
||||
|
||||
# DigiServer v2 Production Deployment Commands Reference
|
||||
# Use this file as a reference for all deployment-related operations
|
||||
|
||||
echo "📋 DigiServer v2 Production Deployment Reference"
|
||||
echo "================================================="
|
||||
echo ""
|
||||
echo "QUICK START:"
|
||||
echo " 1. Set environment variables"
|
||||
echo " 2. Create .env file"
|
||||
echo " 3. Run: docker-compose up -d"
|
||||
echo ""
|
||||
echo "Available commands below (copy/paste as needed):"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# SECTION: INITIAL SETUP
|
||||
# ============================================================================
|
||||
|
||||
echo "=== SECTION 1: INITIAL SETUP ==="
|
||||
echo ""
|
||||
echo "Generate Secret Key:"
|
||||
echo ' python -c "import secrets; print(secrets.token_urlsafe(32))"'
|
||||
echo ""
|
||||
echo "Create environment file from template:"
|
||||
echo " cp .env.example .env"
|
||||
echo " nano .env # Edit with your values"
|
||||
echo ""
|
||||
echo "Required .env variables:"
|
||||
echo " SECRET_KEY=<generated-32-char-key>"
|
||||
echo " ADMIN_USERNAME=admin"
|
||||
echo " ADMIN_PASSWORD=<strong-password>"
|
||||
echo " ADMIN_EMAIL=admin@company.com"
|
||||
echo " DOMAIN=your-domain.com"
|
||||
echo " EMAIL=admin@your-domain.com"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# SECTION: DOCKER OPERATIONS
|
||||
# ============================================================================
|
||||
|
||||
echo "=== SECTION 2: DOCKER OPERATIONS ==="
|
||||
echo ""
|
||||
echo "Build images:"
|
||||
echo " docker-compose build"
|
||||
echo ""
|
||||
echo "Start services:"
|
||||
echo " docker-compose up -d"
|
||||
echo ""
|
||||
echo "Stop services:"
|
||||
echo " docker-compose down"
|
||||
echo ""
|
||||
echo "Restart services:"
|
||||
echo " docker-compose restart"
|
||||
echo ""
|
||||
echo "View container status:"
|
||||
echo " docker-compose ps"
|
||||
echo ""
|
||||
echo "View logs (live):"
|
||||
echo " docker-compose logs -f digiserver-app"
|
||||
echo ""
|
||||
echo "View logs (last 100 lines):"
|
||||
echo " docker-compose logs --tail=100 digiserver-app"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# SECTION: DATABASE OPERATIONS
|
||||
# ============================================================================
|
||||
|
||||
echo "=== SECTION 3: DATABASE OPERATIONS ==="
|
||||
echo ""
|
||||
echo "Initialize database (first deployment only):"
|
||||
echo " docker-compose exec digiserver-app flask db upgrade"
|
||||
echo ""
|
||||
echo "Run database migrations:"
|
||||
echo " docker-compose exec digiserver-app flask db upgrade head"
|
||||
echo ""
|
||||
echo "Create new migration (after model changes):"
|
||||
echo " docker-compose exec digiserver-app flask db migrate -m 'description'"
|
||||
echo ""
|
||||
echo "Backup database:"
|
||||
echo " docker-compose exec digiserver-app cp instance/dashboard.db /backup/dashboard.db.bak"
|
||||
echo ""
|
||||
echo "Restore database:"
|
||||
echo " docker-compose exec digiserver-app cp /backup/dashboard.db.bak instance/dashboard.db"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# SECTION: VERIFICATION & TESTING
|
||||
# ============================================================================
|
||||
|
||||
echo "=== SECTION 4: VERIFICATION & TESTING ==="
|
||||
echo ""
|
||||
echo "Health check:"
|
||||
echo " curl -k https://your-domain.com/api/health"
|
||||
echo ""
|
||||
echo "Check CORS headers (should see Access-Control-Allow-*):"
|
||||
echo " curl -i -k https://your-domain.com/api/playlists"
|
||||
echo ""
|
||||
echo "Check HTTPS only (should redirect):"
|
||||
echo " curl -i http://your-domain.com/"
|
||||
echo ""
|
||||
echo "Test certificate:"
|
||||
echo " openssl s_client -connect your-domain.com:443 -showcerts"
|
||||
echo ""
|
||||
echo "Check SSL certificate expiry:"
|
||||
echo " openssl x509 -enddate -noout -in data/nginx-ssl/cert.pem"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# SECTION: TROUBLESHOOTING
|
||||
# ============================================================================
|
||||
|
||||
echo "=== SECTION 5: TROUBLESHOOTING ==="
|
||||
echo ""
|
||||
echo "View full container logs:"
|
||||
echo " docker-compose logs digiserver-app"
|
||||
echo ""
|
||||
echo "Execute command in container:"
|
||||
echo " docker-compose exec digiserver-app bash"
|
||||
echo ""
|
||||
echo "Check container resources:"
|
||||
echo " docker stats"
|
||||
echo ""
|
||||
echo "Remove and rebuild from scratch:"
|
||||
echo " docker-compose down -v"
|
||||
echo " docker-compose build --no-cache"
|
||||
echo " docker-compose up -d"
|
||||
echo ""
|
||||
echo "Check disk space:"
|
||||
echo " du -sh data/"
|
||||
echo ""
|
||||
echo "View network configuration:"
|
||||
echo " docker-compose exec digiserver-app netstat -tuln"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# SECTION: MAINTENANCE
|
||||
# ============================================================================
|
||||
|
||||
echo "=== SECTION 6: MAINTENANCE ==="
|
||||
echo ""
|
||||
echo "Clean up unused Docker resources:"
|
||||
echo " docker system prune -a"
|
||||
echo ""
|
||||
echo "Backup entire application:"
|
||||
echo " tar -czf digiserver-backup-\$(date +%Y%m%d).tar.gz ."
|
||||
echo ""
|
||||
echo "Update Docker images:"
|
||||
echo " docker-compose pull"
|
||||
echo " docker-compose up -d"
|
||||
echo ""
|
||||
echo "Rebuild and redeploy:"
|
||||
echo " docker-compose down"
|
||||
echo " docker-compose build --no-cache"
|
||||
echo " docker-compose up -d"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# SECTION: MONITORING
|
||||
# ============================================================================
|
||||
|
||||
echo "=== SECTION 7: MONITORING ==="
|
||||
echo ""
|
||||
echo "Monitor containers in real-time:"
|
||||
echo " watch -n 1 docker-compose ps"
|
||||
echo ""
|
||||
echo "Monitor resource usage:"
|
||||
echo " docker stats --no-stream"
|
||||
echo ""
|
||||
echo "Check application errors:"
|
||||
echo " docker-compose logs --since 10m digiserver-app | grep ERROR"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# SECTION: GIT OPERATIONS
|
||||
# ============================================================================
|
||||
|
||||
echo "=== SECTION 8: GIT OPERATIONS ==="
|
||||
echo ""
|
||||
echo "Check deployment status:"
|
||||
echo " git status"
|
||||
echo ""
|
||||
echo "View deployment history:"
|
||||
echo " git log --oneline -5"
|
||||
echo ""
|
||||
echo "Commit deployment changes:"
|
||||
echo " git add ."
|
||||
echo " git commit -m 'Deployment configuration'"
|
||||
echo ""
|
||||
echo "Tag release:"
|
||||
echo " git tag -a v2.0.0 -m 'Production release'"
|
||||
echo " git push --tags"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# SECTION: EMERGENCY PROCEDURES
|
||||
# ============================================================================
|
||||
|
||||
echo "=== SECTION 9: EMERGENCY PROCEDURES ==="
|
||||
echo ""
|
||||
echo "Kill stuck container:"
|
||||
echo " docker-compose kill digiserver-app"
|
||||
echo ""
|
||||
echo "Restore from backup:"
|
||||
echo " docker-compose down"
|
||||
echo " cp /backup/dashboard.db.bak data/instance/dashboard.db"
|
||||
echo " docker-compose up -d"
|
||||
echo ""
|
||||
echo "Rollback to previous version:"
|
||||
echo " git checkout v1.9.0"
|
||||
echo " docker-compose down"
|
||||
echo " docker-compose build"
|
||||
echo " docker-compose up -d"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# SECTION: QUICK REFERENCE
|
||||
# ============================================================================
|
||||
|
||||
echo "=== SECTION 10: QUICK REFERENCE ALIASES ==="
|
||||
echo ""
|
||||
echo "Add these to your ~/.bashrc for quick access:"
|
||||
echo ""
|
||||
cat << 'EOF'
|
||||
alias ds-start='docker-compose up -d'
|
||||
alias ds-stop='docker-compose down'
|
||||
alias ds-logs='docker-compose logs -f digiserver-app'
|
||||
alias ds-health='curl -k https://your-domain/api/health'
|
||||
alias ds-status='docker-compose ps'
|
||||
alias ds-bash='docker-compose exec digiserver-app bash'
|
||||
EOF
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# DONE
|
||||
# ============================================================================
|
||||
|
||||
echo "=== END OF REFERENCE ==="
|
||||
echo ""
|
||||
echo "For detailed documentation, see:"
|
||||
echo " - PRODUCTION_DEPLOYMENT_GUIDE.md"
|
||||
echo " - DEPLOYMENT_READINESS_SUMMARY.md"
|
||||
echo " - old_code_documentation/"
|
||||
echo ""
|
||||
@@ -1,27 +0,0 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
digiserver:
|
||||
build: .
|
||||
container_name: digiserver-v2-http
|
||||
ports:
|
||||
- "80:5000" # Direct HTTP exposure on port 80
|
||||
volumes:
|
||||
- ./instance:/app/instance
|
||||
- ./app/static/uploads:/app/app/static/uploads
|
||||
environment:
|
||||
- FLASK_ENV=production
|
||||
- SECRET_KEY=${SECRET_KEY:-your-secret-key-change-this}
|
||||
- ADMIN_USERNAME=${ADMIN_USERNAME:-admin}
|
||||
- ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin123}
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5000/').read()"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
# Usage: docker-compose -f docker-compose.http.yml up -d
|
||||
# Access at: http://localhost or http://your-server-ip
|
||||
# Note: This is for development/testing only. Use docker-compose.yml for production HTTPS.
|
||||
27
docker-compose.yml
Executable file → Normal file
@@ -8,7 +8,8 @@ services:
|
||||
expose:
|
||||
- "5000"
|
||||
volumes:
|
||||
- ./data:/app
|
||||
# Code is in the Docker image - no volume mount needed
|
||||
# Only mount persistent data folders:
|
||||
- ./data/instance:/app/instance
|
||||
- ./data/uploads:/app/app/static/uploads
|
||||
environment:
|
||||
@@ -26,19 +27,19 @@ services:
|
||||
networks:
|
||||
- digiserver-network
|
||||
|
||||
# Caddy reverse proxy with automatic HTTPS
|
||||
caddy:
|
||||
image: caddy:2-alpine
|
||||
container_name: digiserver-caddy
|
||||
# Nginx reverse proxy with HTTPS support
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
container_name: digiserver-nginx
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "443:443/udp" # HTTP/3 support
|
||||
- "2019:2019" # Caddy admin API
|
||||
volumes:
|
||||
- ./data/Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- ./data/caddy-data:/data
|
||||
- ./data/caddy-config:/config
|
||||
- ./data/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- ./data/nginx-custom-domains.conf:/etc/nginx/conf.d/custom-domains.conf:rw
|
||||
- ./data/nginx-ssl:/etc/nginx/ssl:ro
|
||||
- ./data/nginx-logs:/var/log/nginx
|
||||
- ./data/certbot:/var/www/certbot:ro # For Let's Encrypt ACME challenges
|
||||
environment:
|
||||
- DOMAIN=${DOMAIN:-localhost}
|
||||
- EMAIL=${EMAIL:-admin@localhost}
|
||||
@@ -46,6 +47,12 @@ services:
|
||||
digiserver-app:
|
||||
condition: service_started
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
networks:
|
||||
- digiserver-network
|
||||
|
||||
|
||||
0
fix_player_user_schema.py
Executable file → Normal file
30
generate_nginx_certs.sh
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
# Generate self-signed SSL certificates for Nginx
|
||||
# Usage: ./generate_nginx_certs.sh [domain] [days]
|
||||
|
||||
DOMAIN=${1:-localhost}
|
||||
DAYS=${2:-365}
|
||||
CERT_DIR="./data/nginx-ssl"
|
||||
|
||||
echo "🔐 Generating self-signed SSL certificate for Nginx"
|
||||
echo "Domain: $DOMAIN"
|
||||
echo "Valid for: $DAYS days"
|
||||
echo "Certificate directory: $CERT_DIR"
|
||||
|
||||
# Create directory if it doesnt exist
|
||||
mkdir -p "$CERT_DIR"
|
||||
|
||||
# Generate private key and certificate
|
||||
openssl req -x509 -nodes -days "$DAYS" \
|
||||
-newkey rsa:2048 \
|
||||
-keyout "$CERT_DIR/key.pem" \
|
||||
-out "$CERT_DIR/cert.pem" \
|
||||
-subj "/CN=$DOMAIN/O=DigiServer/C=US"
|
||||
|
||||
# Set proper permissions
|
||||
chmod 644 "$CERT_DIR/cert.pem"
|
||||
chmod 600 "$CERT_DIR/key.pem"
|
||||
|
||||
echo "✅ Certificates generated successfully!"
|
||||
echo "Certificate: $CERT_DIR/cert.pem"
|
||||
echo "Key: $CERT_DIR/key.pem"
|
||||
157
https_manager.py
@@ -1,157 +0,0 @@
|
||||
"""Utility script for managing HTTPS configuration from command line."""
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, '/app')
|
||||
|
||||
from app.app import create_app
|
||||
from app.models.https_config import HTTPSConfig
|
||||
|
||||
def show_help():
|
||||
"""Display help information."""
|
||||
print("""
|
||||
HTTPS Configuration Management Utility
|
||||
======================================
|
||||
|
||||
Usage:
|
||||
python https_manager.py <command> [arguments]
|
||||
|
||||
Commands:
|
||||
status Show current HTTPS configuration status
|
||||
enable <hostname> <domain> <email> <ip> [port]
|
||||
Enable HTTPS with specified settings
|
||||
disable Disable HTTPS
|
||||
show Show detailed configuration
|
||||
|
||||
Examples:
|
||||
# Show current status
|
||||
python https_manager.py status
|
||||
|
||||
# Enable HTTPS
|
||||
python https_manager.py enable digiserver digiserver.sibiusb.harting.intra admin@example.com 10.76.152.164 443
|
||||
|
||||
# Disable HTTPS
|
||||
python https_manager.py disable
|
||||
|
||||
# Show detailed config
|
||||
python https_manager.py show
|
||||
""")
|
||||
|
||||
def show_status():
|
||||
"""Show current HTTPS status."""
|
||||
app = create_app()
|
||||
with app.app_context():
|
||||
config = HTTPSConfig.get_config()
|
||||
if config:
|
||||
print("\n" + "=" * 50)
|
||||
print("HTTPS Configuration Status")
|
||||
print("=" * 50)
|
||||
print(f"Status: {'✅ ENABLED' if config.https_enabled else '⚠️ DISABLED'}")
|
||||
print(f"Hostname: {config.hostname or 'N/A'}")
|
||||
print(f"Domain: {config.domain or 'N/A'}")
|
||||
print(f"IP Address: {config.ip_address or 'N/A'}")
|
||||
print(f"Port: {config.port}")
|
||||
print(f"Updated: {config.updated_at.strftime('%Y-%m-%d %H:%M:%S')} by {config.updated_by or 'N/A'}")
|
||||
if config.https_enabled:
|
||||
print(f"\nAccess URL: https://{config.domain}")
|
||||
print(f"Fallback: http://{config.ip_address}")
|
||||
print("=" * 50 + "\n")
|
||||
else:
|
||||
print("\n⚠️ No HTTPS configuration found. Use 'enable' command to create one.\n")
|
||||
|
||||
def enable_https(hostname: str, domain: str, ip_address: str, email: str, port: str = '443'):
|
||||
"""Enable HTTPS with specified settings."""
|
||||
app = create_app()
|
||||
with app.app_context():
|
||||
try:
|
||||
port_num = int(port)
|
||||
config = HTTPSConfig.create_or_update(
|
||||
https_enabled=True,
|
||||
hostname=hostname,
|
||||
domain=domain,
|
||||
ip_address=ip_address,
|
||||
email=email,
|
||||
port=port_num,
|
||||
updated_by='cli_admin'
|
||||
)
|
||||
print("\n" + "=" * 50)
|
||||
print("✅ HTTPS Configuration Updated")
|
||||
print("=" * 50)
|
||||
print(f"Hostname: {hostname}")
|
||||
print(f"Domain: {domain}")
|
||||
print(f"Email: {email}")
|
||||
print(f"IP Address: {ip_address}")
|
||||
print(f"Port: {port_num}")
|
||||
print(f"\nAccess URL: https://{domain}")
|
||||
print(f"Fallback: http://{ip_address}")
|
||||
print("=" * 50 + "\n")
|
||||
except Exception as e:
|
||||
print(f"\n❌ Error: {str(e)}\n")
|
||||
sys.exit(1)
|
||||
|
||||
def disable_https():
|
||||
"""Disable HTTPS."""
|
||||
app = create_app()
|
||||
with app.app_context():
|
||||
try:
|
||||
config = HTTPSConfig.create_or_update(
|
||||
https_enabled=False,
|
||||
updated_by='cli_admin'
|
||||
)
|
||||
print("\n" + "=" * 50)
|
||||
print("⚠️ HTTPS Disabled")
|
||||
print("=" * 50)
|
||||
print("The application is now running on HTTP only (port 80)")
|
||||
print("=" * 50 + "\n")
|
||||
except Exception as e:
|
||||
print(f"\n❌ Error: {str(e)}\n")
|
||||
sys.exit(1)
|
||||
|
||||
def show_config():
|
||||
"""Show detailed configuration."""
|
||||
app = create_app()
|
||||
with app.app_context():
|
||||
config = HTTPSConfig.get_config()
|
||||
if config:
|
||||
print("\n" + "=" * 50)
|
||||
print("Detailed HTTPS Configuration")
|
||||
print("=" * 50)
|
||||
for key, value in config.to_dict().items():
|
||||
print(f"{key:.<30} {value}")
|
||||
print("=" * 50 + "\n")
|
||||
else:
|
||||
print("\n⚠️ No HTTPS configuration found.\n")
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
if len(sys.argv) < 2:
|
||||
show_help()
|
||||
sys.exit(1)
|
||||
|
||||
command = sys.argv[1].lower()
|
||||
|
||||
if command == 'status':
|
||||
show_status()
|
||||
elif command == 'enable':
|
||||
if len(sys.argv) < 6:
|
||||
print("\nError: 'enable' requires: hostname domain email ip_address [port]\n")
|
||||
show_help()
|
||||
sys.exit(1)
|
||||
hostname = sys.argv[2]
|
||||
domain = sys.argv[3]
|
||||
email = sys.argv[4]
|
||||
ip_address = sys.argv[5]
|
||||
port = sys.argv[6] if len(sys.argv) > 6 else '443'
|
||||
enable_https(hostname, domain, ip_address, email, port)
|
||||
elif command == 'disable':
|
||||
disable_https()
|
||||
elif command == 'show':
|
||||
show_config()
|
||||
elif command in ['help', '-h', '--help']:
|
||||
show_help()
|
||||
else:
|
||||
print(f"\nUnknown command: {command}\n")
|
||||
show_help()
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
153
migrate_network.sh
Executable file
@@ -0,0 +1,153 @@
|
||||
#!/bin/bash
|
||||
# Network Migration Script for DigiServer
|
||||
# Use this when moving the server to a new network with a different IP address
|
||||
# Example: ./migrate_network.sh 10.55.150.160
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Check arguments
|
||||
if [ $# -lt 1 ]; then
|
||||
echo -e "${RED}❌ Usage: ./migrate_network.sh <new_ip_address> [hostname]${NC}"
|
||||
echo ""
|
||||
echo " Example: ./migrate_network.sh 10.55.150.160"
|
||||
echo " Example: ./migrate_network.sh 10.55.150.160 digiserver-secured"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NEW_IP="$1"
|
||||
HOSTNAME="${2:-digiserver}"
|
||||
EMAIL="${EMAIL:-admin@example.com}"
|
||||
PORT="${PORT:-443}"
|
||||
|
||||
# Validate IP format
|
||||
if ! [[ "$NEW_IP" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
|
||||
echo -e "${RED}❌ Invalid IP address format: $NEW_IP${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${BLUE}║ DigiServer Network Migration ║${NC}"
|
||||
echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${BLUE}Migration Settings:${NC}"
|
||||
echo " New IP Address: $NEW_IP"
|
||||
echo " Hostname: $HOSTNAME"
|
||||
echo " Email: $EMAIL"
|
||||
echo " Port: $PORT"
|
||||
echo ""
|
||||
|
||||
# Check if containers are running
|
||||
echo -e "${YELLOW}🔍 [1/4] Checking containers...${NC}"
|
||||
if ! docker compose ps | grep -q "digiserver-app"; then
|
||||
echo -e "${RED}❌ digiserver-app container not running!${NC}"
|
||||
echo "Please start containers with: docker compose up -d"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}✅ Containers are running${NC}"
|
||||
echo ""
|
||||
|
||||
# Step 1: Regenerate SSL certificates for new IP
|
||||
echo -e "${YELLOW}🔐 [2/4] Regenerating SSL certificates for new IP...${NC}"
|
||||
echo " Generating self-signed certificate for $NEW_IP..."
|
||||
|
||||
CERT_DIR="./data/nginx-ssl"
|
||||
mkdir -p "$CERT_DIR"
|
||||
|
||||
openssl req -x509 -nodes -days 365 \
|
||||
-newkey rsa:2048 \
|
||||
-keyout "$CERT_DIR/key.pem" \
|
||||
-out "$CERT_DIR/cert.pem" \
|
||||
-subj "/CN=$NEW_IP/O=DigiServer/C=US" >/dev/null 2>&1
|
||||
|
||||
chmod 644 "$CERT_DIR/cert.pem"
|
||||
chmod 600 "$CERT_DIR/key.pem"
|
||||
|
||||
echo -e " ${GREEN}✓${NC} Certificates regenerated for $NEW_IP"
|
||||
echo -e "${GREEN}✅ SSL certificates updated${NC}"
|
||||
echo ""
|
||||
|
||||
# Step 2: Update HTTPS configuration in database
|
||||
echo -e "${YELLOW}🔧 [3/4] Updating HTTPS configuration in database...${NC}"
|
||||
|
||||
docker compose exec -T digiserver-app python << EOF
|
||||
from app.app import create_app
|
||||
from app.models.https_config import HTTPSConfig
|
||||
from app.extensions import db
|
||||
|
||||
app = create_app('production')
|
||||
with app.app_context():
|
||||
# Update or create HTTPS config for the new IP
|
||||
https_config = HTTPSConfig.query.first()
|
||||
|
||||
if https_config:
|
||||
https_config.hostname = '$HOSTNAME'
|
||||
https_config.ip_address = '$NEW_IP'
|
||||
https_config.email = '$EMAIL'
|
||||
https_config.port = $PORT
|
||||
https_config.enabled = True
|
||||
db.session.commit()
|
||||
print(f" ✓ HTTPS configuration updated")
|
||||
print(f" Hostname: {https_config.hostname}")
|
||||
print(f" IP: {https_config.ip_address}")
|
||||
print(f" Port: {https_config.port}")
|
||||
else:
|
||||
print(" ⚠️ No existing HTTPS config found")
|
||||
print(" This will be created on next app startup")
|
||||
EOF
|
||||
|
||||
echo -e "${GREEN}✅ Database configuration updated${NC}"
|
||||
echo ""
|
||||
|
||||
# Step 3: Restart containers
|
||||
echo -e "${YELLOW}🔄 [4/4] Restarting containers...${NC}"
|
||||
|
||||
docker compose restart nginx digiserver-app
|
||||
sleep 3
|
||||
|
||||
if ! docker compose ps | grep -q "Up"; then
|
||||
echo -e "${RED}❌ Containers failed to restart!${NC}"
|
||||
docker compose logs | tail -20
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✅ Containers restarted successfully${NC}"
|
||||
echo ""
|
||||
|
||||
# Verification
|
||||
echo -e "${YELLOW}🔍 Verifying HTTPS connectivity...${NC}"
|
||||
sleep 2
|
||||
|
||||
if curl -s -k -I https://$NEW_IP 2>/dev/null | grep -q "HTTP"; then
|
||||
echo -e "${GREEN}✅ HTTPS connection verified${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ HTTPS verification pending (containers warming up)${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}╔════════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${GREEN}║ ✅ Network Migration Complete! ║${NC}"
|
||||
echo -e "${GREEN}╚════════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${BLUE}📍 New Access Points:${NC}"
|
||||
echo " 🔒 https://$NEW_IP"
|
||||
echo " 🔒 https://$HOSTNAME.local (if mDNS enabled)"
|
||||
echo ""
|
||||
|
||||
echo -e "${BLUE}📋 Changes Made:${NC}"
|
||||
echo " ✓ SSL certificates regenerated for $NEW_IP"
|
||||
echo " ✓ Database HTTPS config updated"
|
||||
echo " ✓ Nginx and app containers restarted"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}⏳ Allow 30 seconds for containers to become fully healthy${NC}"
|
||||
echo ""
|
||||
0
migrations/add_player_user_table.py
Executable file → Normal file
0
migrations/migrate_player_user_global.py
Executable file → Normal file
21
nginx-custom-domains.conf
Normal file
@@ -0,0 +1,21 @@
|
||||
# Nginx configuration for custom HTTPS domains
|
||||
# This file will be dynamically generated based on HTTPSConfig database entries
|
||||
# Include this in your nginx.conf with: include /etc/nginx/conf.d/custom-domains.conf;
|
||||
|
||||
# Example entry for custom domain:
|
||||
# server {
|
||||
# listen 443 ssl http2;
|
||||
# listen [::]:443 ssl http2;
|
||||
# server_name digiserver.example.com;
|
||||
#
|
||||
# ssl_certificate /etc/nginx/ssl/custom/cert.pem;
|
||||
# ssl_certificate_key /etc/nginx/ssl/custom/key.pem;
|
||||
#
|
||||
# location / {
|
||||
# proxy_pass http://digiserver_app;
|
||||
# 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;
|
||||
# }
|
||||
# }
|
||||
129
nginx.conf
Normal file
@@ -0,0 +1,129 @@
|
||||
user nginx;
|
||||
worker_processes auto;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
use epoll;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
client_max_body_size 2048M;
|
||||
|
||||
# Gzip compression
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/rss+xml;
|
||||
|
||||
# Upstream to Flask application
|
||||
upstream digiserver_app {
|
||||
server digiserver-app:5000;
|
||||
keepalive 32;
|
||||
}
|
||||
|
||||
# HTTP Server - redirect to HTTPS
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
server_name _;
|
||||
|
||||
# Allow ACME challenges for Let's Encrypt
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
}
|
||||
|
||||
# Redirect HTTP to HTTPS for non-ACME requests
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
# HTTPS Server (with self-signed cert by default)
|
||||
server {
|
||||
listen 443 ssl http2 default_server;
|
||||
listen [::]:443 ssl http2 default_server;
|
||||
server_name localhost;
|
||||
|
||||
# SSL certificate paths (will be volume-mounted)
|
||||
ssl_certificate /etc/nginx/ssl/cert.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/key.pem;
|
||||
|
||||
# SSL Configuration
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
|
||||
# Security Headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
||||
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
|
||||
|
||||
# CORS Headers for API endpoints (allows player device connections)
|
||||
add_header 'Access-Control-Allow-Origin' '*' always;
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
|
||||
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
|
||||
add_header 'Access-Control-Max-Age' '3600' always;
|
||||
|
||||
# Handle OPTIONS requests for CORS preflight
|
||||
if ($request_method = 'OPTIONS') {
|
||||
return 204;
|
||||
}
|
||||
|
||||
# Proxy settings
|
||||
location / {
|
||||
proxy_pass http://digiserver_app;
|
||||
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_set_header X-Forwarded-Host $server_name;
|
||||
proxy_set_header X-Forwarded-Port $server_port;
|
||||
|
||||
# Timeouts for large uploads
|
||||
proxy_connect_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_read_timeout 300s;
|
||||
|
||||
# Buffering
|
||||
proxy_buffering on;
|
||||
proxy_buffer_size 128k;
|
||||
proxy_buffers 4 256k;
|
||||
proxy_busy_buffers_size 256k;
|
||||
}
|
||||
|
||||
# Static files caching
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
proxy_pass http://digiserver_app;
|
||||
proxy_cache_valid 200 60d;
|
||||
expires 60d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
|
||||
# Additional server blocks for custom domains can be included here
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
||||
0
old_code_documentation/.env.example
Executable file → Normal file
201
old_code_documentation/DEPLOYMENT_ARCHITECTURE_ANALYSIS.md
Normal file
@@ -0,0 +1,201 @@
|
||||
# Dockerfile vs init-data.sh Analysis
|
||||
|
||||
**Date:** January 17, 2026
|
||||
|
||||
## Current Architecture
|
||||
|
||||
### Current Workflow
|
||||
```
|
||||
1. Run init-data.sh (on host)
|
||||
↓
|
||||
2. Copies app code → data/app/
|
||||
3. Docker build creates image
|
||||
4. Docker run mounts ./data:/app
|
||||
5. Container runs with host's data/ folder
|
||||
```
|
||||
|
||||
### Current Docker Setup
|
||||
- **Dockerfile**: Copies code from build context to `/app` inside image
|
||||
- **docker-compose**: Mounts `./data:/app` **OVERRIDING** the Dockerfile copy
|
||||
- **Result**: Code in image is replaced by volume mount to host's `./data` folder
|
||||
|
||||
---
|
||||
|
||||
## Problem with Current Approach
|
||||
|
||||
1. **Code Duplication**
|
||||
- Code exists in: Host `./app/` folder
|
||||
- Code copied to: Host `./data/app/` folder
|
||||
- Code in Docker image: Ignored/overridden
|
||||
|
||||
2. **Extra Deployment Step**
|
||||
- Must run `init-data.sh` before deployment
|
||||
- Manual file copying required
|
||||
- Room for sync errors
|
||||
|
||||
3. **No Dockerfile Optimization**
|
||||
- Dockerfile copies code but it's never used
|
||||
- Volume mount replaces everything
|
||||
- Wastes build time and image space
|
||||
|
||||
---
|
||||
|
||||
## Proposed Solution: Two Options
|
||||
|
||||
### **Option 1: Use Dockerfile Copy (Recommended)** ✅
|
||||
|
||||
**Change Dockerfile:**
|
||||
```dockerfile
|
||||
# Copy everything to /app inside image
|
||||
COPY . /app/
|
||||
|
||||
# No need for volume mount - image contains all code
|
||||
```
|
||||
|
||||
**Change docker-compose.yml:**
|
||||
```yaml
|
||||
volumes:
|
||||
# REMOVE the ./data:/app volume mount
|
||||
# Keep only data-specific mounts:
|
||||
- ./data/instance:/app/instance # Database
|
||||
- ./data/uploads:/app/app/static/uploads # User uploads
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- ✅ Single source of truth (Dockerfile)
|
||||
- ✅ Code is immutable in image
|
||||
- ✅ No init-data.sh needed
|
||||
- ✅ Faster deployment (no file copying)
|
||||
- ✅ Cleaner architecture
|
||||
- ✅ Can upgrade code by rebuilding image
|
||||
|
||||
**Drawbacks:**
|
||||
- Code changes require docker-compose rebuild
|
||||
- Can't edit code in container (which is good for production)
|
||||
|
||||
---
|
||||
|
||||
### **Option 2: Keep Current (With Improvements)**
|
||||
|
||||
**Keep:**
|
||||
- init-data.sh for copying code to data/
|
||||
- Volume mount at ./data:/app
|
||||
|
||||
**Improve:**
|
||||
- Add validation that init-data.sh ran successfully
|
||||
- Check file sync status before starting app
|
||||
- Add automated sync on container restart
|
||||
|
||||
**Benefits:**
|
||||
- ✅ Dev-friendly (can edit code, restart container)
|
||||
- ✅ Faster iteration during development
|
||||
|
||||
**Drawbacks:**
|
||||
- ❌ Production anti-pattern (code changes without rebuild)
|
||||
- ❌ Extra deployment complexity
|
||||
- ❌ Manual init-data.sh step required
|
||||
|
||||
---
|
||||
|
||||
## Current Production Setup Evaluation
|
||||
|
||||
**Current System:** Option 2 (with volume mount override)
|
||||
|
||||
### Why This Setup Exists
|
||||
|
||||
The current architecture with `./data:/app` volume mount suggests:
|
||||
1. **Development-focused** - Allows code editing and hot-reload
|
||||
2. **Host-based persistence** - All data on host machine
|
||||
3. **Easy backup** - Just backup the `./data/` folder
|
||||
|
||||
### Is This Actually Used?
|
||||
|
||||
- ✅ Code updates via `git pull` in `/app/` folder
|
||||
- ✅ Then `cp -r app/* data/app/` copies to running container
|
||||
- ✅ Allows live code updates without container rebuild
|
||||
|
||||
---
|
||||
|
||||
## Recommendation
|
||||
|
||||
### For Production
|
||||
**Use Option 1 (Dockerfile-based):**
|
||||
- Build immutable images
|
||||
- No init-data.sh needed
|
||||
- Cleaner deployment pipeline
|
||||
- Better for CI/CD
|
||||
|
||||
### For Development
|
||||
**Keep Option 2 (current approach):**
|
||||
- Code editing and hot-reload
|
||||
- Faster iteration
|
||||
|
||||
---
|
||||
|
||||
## Implementation Steps for Option 1
|
||||
|
||||
### 1. **Update Dockerfile**
|
||||
```dockerfile
|
||||
# Instead of: COPY . .
|
||||
# Change docker-compose volume mount pattern
|
||||
```
|
||||
|
||||
### 2. **Update docker-compose.yml**
|
||||
```yaml
|
||||
volumes:
|
||||
# Remove: ./data:/app
|
||||
# Keep only:
|
||||
- ./data/instance:/app/instance
|
||||
- ./data/uploads:/app/app/static/uploads
|
||||
```
|
||||
|
||||
### 3. **Update deploy.sh**
|
||||
```bash
|
||||
# Remove: bash init-data.sh
|
||||
# Just build and run:
|
||||
docker-compose build
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### 4. **Add Migration Path**
|
||||
```bash
|
||||
# For existing deployments:
|
||||
# Copy any instance/database data from data/instance to new location
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Persistence Strategy (Post-Migration)
|
||||
|
||||
```
|
||||
Current: After Option 1:
|
||||
./data/app/ (code) → /app/ (in image)
|
||||
./data/instance/ (db) → ./data/instance/ (volume mount)
|
||||
./data/uploads/ (files) → ./data/uploads/ (volume mount)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Risk Assessment
|
||||
|
||||
### Option 1 (Dockerfile-only)
|
||||
- **Risk Level:** LOW ✅
|
||||
- **Data Loss Risk:** NONE (instance & uploads still mounted)
|
||||
- **Rollback:** Can use old image tag
|
||||
|
||||
### Option 2 (Current)
|
||||
- **Risk Level:** MEDIUM
|
||||
- **Data Loss Risk:** Manual copying errors
|
||||
- **Rollback:** Manual file restore
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
**Recommendation: Option 1 (Dockerfile-based)** for production deployment
|
||||
- Simpler architecture
|
||||
- Better practices
|
||||
- Faster deployment
|
||||
- Cleaner code management
|
||||
|
||||
Would you like to implement this change?
|
||||
0
old_code_documentation/DOCKER.md
Executable file → Normal file
144
old_code_documentation/EDIT_MEDIA_TROUBLESHOOTING.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# Edit Media API Troubleshooting Guide
|
||||
|
||||
## Issue
|
||||
Players are trying to send edited images to the server via the edit image API, but nothing is happening on the server.
|
||||
|
||||
## Diagnosis Performed
|
||||
|
||||
### 1. **API Endpoint Status** ✅
|
||||
- **Endpoint**: `POST /api/player-edit-media`
|
||||
- **Status**: Exists and properly implemented
|
||||
- **Location**: `app/blueprints/api.py` (lines 711-851)
|
||||
- **Authentication**: Requires Bearer token with valid player auth code
|
||||
|
||||
### 2. **Bug Found and Fixed** 🐛
|
||||
Found undefined variable bug in the `receive_edited_media()` function:
|
||||
- **Issue**: `playlist` variable was only defined inside an `if` block
|
||||
- **Problem**: When a player has no assigned playlist, the variable remained undefined
|
||||
- **Error**: Would cause `UnboundLocalError` when trying to return the response
|
||||
- **Fix**: Initialize `playlist = None` before the conditional block
|
||||
- **Commit**: `8a89df3`
|
||||
|
||||
### 3. **Server Logs Check** ✅
|
||||
- No `player-edit-media` requests found in recent logs
|
||||
- **Conclusion**: Requests are not reaching the server, indicating a client-side issue
|
||||
|
||||
## Possible Root Causes
|
||||
|
||||
### A. **Player App Not Sending Requests**
|
||||
The player application might not be calling the edit media endpoint. Check:
|
||||
- Is the "edit on player" feature enabled for the content?
|
||||
- Does the player app have code to capture edited images?
|
||||
- Are there errors in the player app logs?
|
||||
|
||||
### B. **Wrong Endpoint URL**
|
||||
If the player app is hardcoded with an incorrect URL, requests won't reach the server.
|
||||
- **Expected URL**: `{server_url}/api/player-edit-media`
|
||||
- **Required Header**: `Authorization: Bearer {player_auth_code}`
|
||||
|
||||
### C. **Network Issues**
|
||||
- Firewall blocking requests
|
||||
- Network connectivity issues between player and server
|
||||
- SSL/HTTPS certificate validation failures
|
||||
|
||||
### D. **Request Format Issues**
|
||||
The endpoint expects:
|
||||
```
|
||||
Content-Type: multipart/form-data
|
||||
- image_file: The edited image file (binary)
|
||||
- metadata: JSON string with this structure:
|
||||
{
|
||||
"time_of_modification": "2026-01-17T19:50:00Z",
|
||||
"original_name": "image.jpg",
|
||||
"new_name": "image_v1.jpg",
|
||||
"version": 1,
|
||||
"user_card_data": "optional_user_code"
|
||||
}
|
||||
```
|
||||
|
||||
### E. **Authentication Issues**
|
||||
- Player's auth code might be invalid
|
||||
- Bearer token not being sent correctly
|
||||
- Auth code might have changed
|
||||
|
||||
## Testing Steps
|
||||
|
||||
### 1. **Verify Endpoint is Accessible**
|
||||
```bash
|
||||
curl -X POST http://localhost:5000/api/player-edit-media \
|
||||
-H "Authorization: Bearer <valid_player_auth_code>" \
|
||||
-F "image_file=@test.jpg" \
|
||||
-F "metadata={\"time_of_modification\":\"2026-01-17T20:00:00Z\",\"original_name\":\"4k1.jpg\",\"new_name\":\"4k1_v1.jpg\",\"version\":1}"
|
||||
```
|
||||
|
||||
### 2. **Check Player Logs**
|
||||
Look for errors in the player application logs when attempting to send edits
|
||||
|
||||
### 3. **Monitor Server Logs**
|
||||
Enable debug logging and watch for:
|
||||
```bash
|
||||
docker compose logs digiserver-app -f | grep -i "edit\|player-edit"
|
||||
```
|
||||
|
||||
### 4. **Verify Player Has Valid Auth Code**
|
||||
```bash
|
||||
curl -X POST http://localhost:5000/api/auth/verify \
|
||||
-H "Authorization: Bearer <player_auth_code>" \
|
||||
-H "Content-Type: application/json"
|
||||
```
|
||||
|
||||
## Server API Response
|
||||
|
||||
### Success Response (200 OK)
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Edited media received and processed",
|
||||
"edit_id": 123,
|
||||
"version": 1,
|
||||
"old_filename": "image.jpg",
|
||||
"new_filename": "image_v1.jpg",
|
||||
"new_playlist_version": 34
|
||||
}
|
||||
```
|
||||
|
||||
### Error Responses
|
||||
- **401**: Missing or invalid authorization header
|
||||
- **403**: Invalid authentication code
|
||||
- **400**: Missing required fields (image_file, metadata, etc.)
|
||||
- **404**: Original content file not found in system
|
||||
- **500**: Internal server error (check logs)
|
||||
|
||||
## Expected Server Behavior
|
||||
|
||||
When an edit is successfully received:
|
||||
1. ✅ File is saved to `/static/uploads/edited_media/<content_id>/<filename>`
|
||||
2. ✅ Metadata JSON is saved alongside the file
|
||||
3. ✅ PlayerEdit record is created in database
|
||||
4. ✅ PlayerUser record is auto-created if user_card_data provided
|
||||
5. ✅ Playlist version is incremented (if player has assigned playlist)
|
||||
6. ✅ Playlist cache is cleared
|
||||
7. ✅ Action is logged in server_log table
|
||||
|
||||
## Database Records
|
||||
|
||||
After successful upload, check:
|
||||
```sql
|
||||
-- Player edit records
|
||||
SELECT * FROM player_edit WHERE player_id = ? ORDER BY created_at DESC;
|
||||
|
||||
-- Verify file exists
|
||||
ls -la app/static/uploads/edited_media/
|
||||
|
||||
-- Check server logs
|
||||
SELECT * FROM server_log WHERE action LIKE '%edited%' ORDER BY created_at DESC;
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Check if player app is configured with correct server URL
|
||||
2. Verify player has "edit on player" enabled for the content
|
||||
3. Check player app logs for any error messages
|
||||
4. Test endpoint connectivity using curl/Postman
|
||||
5. Monitor server logs while player attempts to send an edit
|
||||
6. Verify player's auth code is valid and unchanged
|
||||
96
old_code_documentation/GROUPS_ANALYSIS.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# Groups Feature - Archived
|
||||
|
||||
**Status: ARCHIVED AND REMOVED ✅**
|
||||
|
||||
**Archive Date:** January 17, 2026
|
||||
|
||||
## What Was Done
|
||||
|
||||
### 1. **Files Archived**
|
||||
- `/app/templates/groups/` → `/old_code_documentation/templates_groups/`
|
||||
- `/app/blueprints/groups.py` → `/old_code_documentation/blueprint_groups.py`
|
||||
|
||||
### 2. **Code Removed**
|
||||
- Removed groups blueprint import from `app/app.py`
|
||||
- Removed groups blueprint registration from `register_blueprints()` function
|
||||
- Removed Group import from `app/blueprints/admin.py` (unused)
|
||||
- Removed Group import from `app/blueprints/api.py` (unused)
|
||||
- Commented out `/api/groups` endpoint in API
|
||||
|
||||
### 3. **What Remained in Code**
|
||||
- **NOT removed:** Group model in `app/models/group.py`
|
||||
- Kept for database backward compatibility
|
||||
- No imports or references to it now
|
||||
- Database table is orphaned but safe to keep
|
||||
|
||||
- **NOT removed:** `app/utils/group_player_management.py`
|
||||
- Contains utility functions that may be referenced
|
||||
- Can be archived later if confirmed unused
|
||||
|
||||
## Summary
|
||||
|
||||
✅ Groups feature is now completely **unavailable in the UI and app logic**
|
||||
✅ No routes, blueprints, or navigation links to groups
|
||||
✅ Application loads cleanly without groups
|
||||
✅ Database tables preserved for backward compatibility
|
||||
|
||||
## Why Groups Was Removed
|
||||
|
||||
1. **Functionality replaced by Playlists**
|
||||
- Groups: "Organize content into categories"
|
||||
- Playlists: "Organize content into collections assigned to players"
|
||||
|
||||
2. **Never used in the current workflow**
|
||||
- Dashboard: Players → Playlists → Content
|
||||
- No mention of groups in any UI navigation
|
||||
- Players have NO relationship to groups
|
||||
|
||||
3. **Redundant architecture**
|
||||
- Playlists provide superior functionality
|
||||
- Players directly assign to playlists
|
||||
- No need for intermediate grouping layer
|
||||
|
||||
## Original Purpose (Deprecated)
|
||||
|
||||
- Groups were designed to organize content
|
||||
- Could contain multiple content items
|
||||
- Players could be assigned to groups
|
||||
- **BUT:** Player model never implemented group relationship
|
||||
- **Result:** Feature was incomplete and unused
|
||||
|
||||
## Current Workflow (Active) ✅
|
||||
|
||||
```
|
||||
1. Create Playlist (organize content)
|
||||
2. Upload Media (add files)
|
||||
3. Add Content to Playlist (manage items)
|
||||
4. Add Player (register device)
|
||||
5. Assign Playlist to Player (connect directly)
|
||||
6. Players auto-download and display
|
||||
```
|
||||
|
||||
## If Groups Data Exists
|
||||
|
||||
The `group` and `group_content` database tables still exist but are orphaned:
|
||||
- No code references them
|
||||
- No migrations to drop them
|
||||
- Safe to keep or drop as needed
|
||||
|
||||
## Future Cleanup
|
||||
|
||||
When ready, can be removed completely:
|
||||
- `app/models/group.py` - Drop Group model
|
||||
- Database migrations to drop `group` and `group_content` tables
|
||||
- Remove utility functions from `app/utils/group_player_management.py`
|
||||
- Clean up any remaining imports
|
||||
|
||||
## References
|
||||
|
||||
- **Archive date:** January 17, 2026
|
||||
- **Related:** See `LEGACY_PLAYLIST_ROUTES.md` for similar cleanup
|
||||
- **Similar action:** Playlist templates also archived as legacy
|
||||
|
||||
---
|
||||
|
||||
**Status:** ✅ Complete - Groups feature successfully archived and removed from active codebase
|
||||
|
||||
0
old_code_documentation/HTTPS_SETUP.md
Executable file → Normal file
0
old_code_documentation/IMPLEMENTATION_OPTIONAL_LIBREOFFICE.md
Executable file → Normal file
51
old_code_documentation/LEGACY_PLAYLIST_ROUTES.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Legacy Playlist Routes & Templates
|
||||
|
||||
## Status: DEPRECATED ❌
|
||||
|
||||
The `playlist/` folder contains legacy code that has been superseded by the content management interface.
|
||||
|
||||
## What Changed
|
||||
|
||||
### Old Workflow (DEPRECATED)
|
||||
- Route: `/playlist/<player_id>`
|
||||
- Template: `playlist/manage_playlist.html`
|
||||
- Used for managing playlists at the player level
|
||||
|
||||
### New Workflow (ACTIVE) ✅
|
||||
- Route: `/content/playlist/<playlist_id>/manage`
|
||||
- Template: `content/manage_playlist_content.html`
|
||||
- Used for managing playlists in the content area
|
||||
- Accessed from: Players → Manage Player → "Edit Playlist Content" button
|
||||
|
||||
## Migration Notes
|
||||
|
||||
**January 17, 2026:**
|
||||
- Moved `app/templates/playlist/` to `old_code_documentation/playlist/`
|
||||
- Updated `/playlist/<player_id>` route to redirect to the new content management interface
|
||||
- All playlist operations now go through the content management area (`manage_playlist_content.html`)
|
||||
|
||||
## Why the Change?
|
||||
|
||||
1. **Unified Interface**: Single playlist management interface instead of duplicate functionality
|
||||
2. **Better UX**: Content management area is the primary interface accessed from players
|
||||
3. **Maintenance**: Reduces code duplication and maintenance burden
|
||||
|
||||
## Routes Still in Code
|
||||
|
||||
The routes in `app/blueprints/playlist.py` still exist but are now legacy:
|
||||
- `@playlist_bp.route('/<int:player_id>')` - Redirects to content management
|
||||
- `@playlist_bp.route('/<int:player_id>/add')` - No longer used
|
||||
- `@playlist_bp.route('/<int:player_id>/remove/<int:content_id>')` - No longer used
|
||||
- etc.
|
||||
|
||||
These can be removed in a future cleanup if needed.
|
||||
|
||||
## Features in New Interface
|
||||
|
||||
The new `manage_playlist_content.html` includes all features plus:
|
||||
- ✅ Drag-to-reorder functionality
|
||||
- ✅ Duration spinner buttons (⬆️ ⬇️)
|
||||
- ✅ Audio on/off toggle
|
||||
- ✅ Edit mode toggle for PDFs/images
|
||||
- ✅ Dark mode support
|
||||
- ✅ Bulk delete with checkboxes
|
||||
262
old_code_documentation/MODERNIZATION_COMPLETE.md
Normal file
@@ -0,0 +1,262 @@
|
||||
# Deployment Architecture - Complete Modernization Summary
|
||||
|
||||
**Date:** January 17, 2026
|
||||
**Status:** ✅ COMPLETE & PRODUCTION READY
|
||||
|
||||
## What Was Accomplished
|
||||
|
||||
### 1. **Code Deployment Modernized (Option 1)**
|
||||
- ✅ Moved code into Docker image (no volume override)
|
||||
- ✅ Eliminated init-data.sh manual step
|
||||
- ✅ Cleaner separation: code (immutable image) vs data (persistent volumes)
|
||||
|
||||
### 2. **Legacy Code Cleaned**
|
||||
- ✅ Archived groups feature (not used, replaced by playlists)
|
||||
- ✅ Archived legacy playlist routes (redirects to content area now)
|
||||
- ✅ Removed unused imports and API endpoints
|
||||
|
||||
### 3. **Persistence Unified in /data Folder**
|
||||
- ✅ Moved nginx.conf to data/
|
||||
- ✅ Moved nginx-custom-domains.conf to data/
|
||||
- ✅ All runtime files now in single data/ folder
|
||||
- ✅ Clear separation: source code (git) vs runtime data (data/)
|
||||
|
||||
---
|
||||
|
||||
## Complete Architecture (NOW)
|
||||
|
||||
### Repository Structure (Source Code)
|
||||
```
|
||||
/srv/digiserver-v2/
|
||||
├── app/ # Flask application (BUILT INTO DOCKER IMAGE)
|
||||
├── migrations/ # Database migrations (BUILT INTO DOCKER IMAGE)
|
||||
├── Dockerfile # Copies everything above into image
|
||||
├── docker-compose.yml # Container orchestration
|
||||
├── requirements.txt # Python dependencies
|
||||
├── .gitignore
|
||||
└── [other source files] # All built into image
|
||||
```
|
||||
|
||||
### Container Runtime Structure (/data folder)
|
||||
```
|
||||
data/
|
||||
├── instance/ # Database & config (PERSISTENT)
|
||||
│ ├── digiserver.db
|
||||
│ └── server.log
|
||||
├── uploads/ # User uploads (PERSISTENT)
|
||||
│ ├── app/static/uploads/
|
||||
│ └── [user files]
|
||||
├── nginx.conf # Nginx main config (PERSISTENT) ✅ NEW
|
||||
├── nginx-custom-domains.conf # Custom domains (PERSISTENT) ✅ NEW
|
||||
├── nginx-ssl/ # SSL certificates (PERSISTENT)
|
||||
├── nginx-logs/ # Web server logs (PERSISTENT)
|
||||
├── certbot/ # Let's Encrypt data (PERSISTENT)
|
||||
├── caddy-config/ # Caddy configurations
|
||||
└── [other runtime files]
|
||||
```
|
||||
|
||||
### Docker Container Volumes (No Code Mounts!)
|
||||
```yaml
|
||||
digiserver-app:
|
||||
volumes:
|
||||
- ./data/instance:/app/instance # DB
|
||||
- ./data/uploads:/app/app/static/uploads # Uploads
|
||||
# ✅ NO CODE MOUNT - code is in image!
|
||||
|
||||
nginx:
|
||||
volumes:
|
||||
- ./data/nginx.conf:/etc/nginx/nginx.conf # ✅ FROM data/
|
||||
- ./data/nginx-custom-domains.conf:/etc/nginx/conf.d/custom-domains.conf # ✅ FROM data/
|
||||
- ./data/nginx-ssl:/etc/nginx/ssl # Certs
|
||||
- ./data/nginx-logs:/var/log/nginx # Logs
|
||||
- ./data/certbot:/var/www/certbot # ACME
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deployment Flow (NOW)
|
||||
|
||||
### Fresh Deployment
|
||||
```bash
|
||||
cd /srv/digiserver-v2
|
||||
|
||||
# 1. Prepare data folder
|
||||
mkdir -p data/{instance,uploads,nginx-ssl,nginx-logs,certbot}
|
||||
cp nginx.conf data/
|
||||
cp nginx-custom-domains.conf data/
|
||||
|
||||
# 2. Build image (includes app code)
|
||||
docker-compose build
|
||||
|
||||
# 3. Deploy
|
||||
docker-compose up -d
|
||||
|
||||
# 4. Initialize database (automatic on first run)
|
||||
```
|
||||
|
||||
### Code Updates
|
||||
```bash
|
||||
# 1. Get new code
|
||||
git pull
|
||||
|
||||
# 2. Rebuild image (code change → new image)
|
||||
docker-compose build
|
||||
|
||||
# 3. Deploy new version
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
### Configuration Changes
|
||||
```bash
|
||||
# Edit config in data/ (PERSISTENT)
|
||||
nano data/nginx.conf
|
||||
nano data/nginx-custom-domains.conf
|
||||
|
||||
# Reload without full restart
|
||||
docker-compose restart nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Improvements
|
||||
|
||||
### ✅ Deployment Simplicity
|
||||
| Aspect | Before | After |
|
||||
|--------|--------|-------|
|
||||
| Manual setup step | init-data.sh required | None - auto in image |
|
||||
| Config location | Mixed (root + data/) | Single (data/) |
|
||||
| Code update process | Copy + restart | Build + restart |
|
||||
| Backup strategy | Multiple locations | Single data/ folder |
|
||||
|
||||
### ✅ Production Readiness
|
||||
- Immutable code in image (reproducible deployments)
|
||||
- Version-controlled via image tags
|
||||
- Easy rollback: use old image tag
|
||||
- CI/CD friendly: build → test → deploy
|
||||
|
||||
### ✅ Data Safety
|
||||
- All persistent data in one folder
|
||||
- Easy backup: `tar czf backup.tar.gz data/`
|
||||
- Easy restore: `tar xzf backup.tar.gz`
|
||||
- Clear separation from source code
|
||||
|
||||
### ✅ Repository Cleanliness
|
||||
```
|
||||
Before: After:
|
||||
./nginx.conf ❌ ./data/nginx.conf ✅
|
||||
./nginx-custom-domains.conf ./data/nginx-custom-domains.conf
|
||||
./init-data.sh ❌ (archived as deprecated)
|
||||
./app/ ✅ ./app/ ✅ (in image)
|
||||
./data/app/ ❌ (redundant) [none - in image]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checklist: All Changes Deployed ✅
|
||||
|
||||
- [x] docker-compose.yml updated (no code volume mount)
|
||||
- [x] Dockerfile enhanced (code baked in)
|
||||
- [x] init-data.sh archived (no longer needed)
|
||||
- [x] Groups feature archived (legacy/unused)
|
||||
- [x] Playlist routes simplified (legacy redirects)
|
||||
- [x] Nginx configs moved to data/ folder
|
||||
- [x] All containers running healthy
|
||||
- [x] HTTP/HTTPS working
|
||||
- [x] Database persistent
|
||||
- [x] Uploads persistent
|
||||
- [x] Configuration persistent
|
||||
|
||||
---
|
||||
|
||||
## Testing Results ✅
|
||||
|
||||
```
|
||||
✓ Docker build: SUCCESS
|
||||
✓ Container startup: SUCCESS
|
||||
✓ Flask app responding: SUCCESS
|
||||
✓ Nginx HTTP (port 80): SUCCESS
|
||||
✓ Nginx HTTPS (port 443): SUCCESS
|
||||
✓ Database accessible: SUCCESS
|
||||
✓ Uploads persisting: SUCCESS
|
||||
✓ Logs persisting: SUCCESS
|
||||
✓ Config persistence: SUCCESS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File References
|
||||
|
||||
### Migration & Implementation Docs
|
||||
- `old_code_documentation/OPTION1_IMPLEMENTATION.md` - Docker architecture change
|
||||
- `old_code_documentation/NGINX_CONFIG_MIGRATION.md` - Config file relocation
|
||||
- `old_code_documentation/GROUPS_ANALYSIS.md` - Archived feature
|
||||
- `old_code_documentation/LEGACY_PLAYLIST_ROUTES.md` - Simplified routes
|
||||
|
||||
### Archived Code
|
||||
- `old_code_documentation/init-data.sh.deprecated` - Old setup script
|
||||
- `old_code_documentation/blueprint_groups.py` - Groups feature
|
||||
- `old_code_documentation/templates_groups/` - Group templates
|
||||
- `old_code_documentation/playlist/` - Legacy playlist templates
|
||||
|
||||
---
|
||||
|
||||
## Next Steps (Optional Cleanup)
|
||||
|
||||
### Option A: Keep Root Files (Safe)
|
||||
```bash
|
||||
# Keep nginx.conf and nginx-custom-domains.conf in root as backups
|
||||
# They're not used but serve as reference
|
||||
# Already ignored by .gitignore
|
||||
```
|
||||
|
||||
### Option B: Clean Repository (Recommended)
|
||||
```bash
|
||||
# Remove root nginx files (already in data/)
|
||||
rm nginx.conf
|
||||
rm nginx-custom-domains.conf
|
||||
|
||||
# Add to .gitignore if needed:
|
||||
echo "nginx.conf" >> .gitignore
|
||||
echo "nginx-custom-domains.conf" >> .gitignore
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Production Deployment
|
||||
|
||||
### Recommended Workflow
|
||||
```bash
|
||||
# 1. Code changes
|
||||
git commit -m "feature: add new UI"
|
||||
|
||||
# 2. Build and test
|
||||
docker-compose build
|
||||
docker-compose up -d
|
||||
# [run tests]
|
||||
|
||||
# 3. Tag version
|
||||
git tag v1.2.3
|
||||
docker tag digiserver-v2-digiserver-app:latest digiserver-v2-digiserver-app:v1.2.3
|
||||
|
||||
# 4. Push to registry
|
||||
docker push myregistry/digiserver:v1.2.3
|
||||
|
||||
# 5. Deploy
|
||||
docker pull myregistry/digiserver:v1.2.3
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Your DigiServer deployment is now:
|
||||
- 🚀 **Modern**: Docker best practices implemented
|
||||
- 📦 **Clean**: Single source of truth for each layer
|
||||
- 💾 **Persistent**: All data safely isolated
|
||||
- 🔄 **Maintainable**: Clear separation of concerns
|
||||
- 🏭 **Production-Ready**: Version control & rollback support
|
||||
- ⚡ **Fast**: No manual setup steps
|
||||
- 🔒 **Secure**: Immutable code in images
|
||||
|
||||
**Status: ✅ READY FOR PRODUCTION**
|
||||
111
old_code_documentation/NGINX_CONFIG_MIGRATION.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Nginx Config Files Moved to Data Folder
|
||||
|
||||
**Date:** January 17, 2026
|
||||
**Purpose:** Complete persistence isolation - all Docker runtime files in `data/` folder
|
||||
|
||||
## What Changed
|
||||
|
||||
### Files Moved
|
||||
- `./nginx.conf` → `./data/nginx.conf`
|
||||
- `./nginx-custom-domains.conf` → `./data/nginx-custom-domains.conf`
|
||||
|
||||
### docker-compose.yml Updated
|
||||
```yaml
|
||||
volumes:
|
||||
- ./data/nginx.conf:/etc/nginx/nginx.conf:ro # ✅ NOW in data/
|
||||
- ./data/nginx-custom-domains.conf:/etc/nginx/conf.d/custom-domains.conf:rw # ✅ NOW in data/
|
||||
- ./data/nginx-ssl:/etc/nginx/ssl:ro
|
||||
- ./data/nginx-logs:/var/log/nginx
|
||||
- ./data/certbot:/var/www/certbot:ro
|
||||
```
|
||||
|
||||
## Complete Data Folder Structure (Now Unified)
|
||||
|
||||
```
|
||||
/data/
|
||||
├── app/ # Flask application (in Docker image, not mounted)
|
||||
├── instance/ # Database & config
|
||||
│ ├── digiserver.db
|
||||
│ └── server.log
|
||||
├── uploads/ # User uploads
|
||||
│ └── app/static/uploads/...
|
||||
├── nginx.conf # ✅ Nginx main config
|
||||
├── nginx-custom-domains.conf # ✅ Custom domain config
|
||||
├── nginx-ssl/ # SSL certificates
|
||||
│ ├── cert.pem
|
||||
│ └── key.pem
|
||||
├── nginx-logs/ # Nginx logs
|
||||
│ ├── access.log
|
||||
│ └── error.log
|
||||
└── certbot/ # Let's Encrypt certificates
|
||||
```
|
||||
|
||||
## Benefits
|
||||
|
||||
✅ **Unified Persistence:** All runtime configuration in `/data`
|
||||
✅ **Easy Backup:** Single `data/` folder contains everything
|
||||
✅ **Consistent Permissions:** All files managed together
|
||||
✅ **Clean Repository:** Root directory only has source code
|
||||
✅ **Deployment Clarity:** Clear separation: source (`./app`) vs runtime (`./data`)
|
||||
|
||||
## Testing Results
|
||||
|
||||
- ✅ Nginx started successfully with new config paths
|
||||
- ✅ HTTP requests working (port 80)
|
||||
- ✅ HTTPS requests working (port 443)
|
||||
- ✅ No configuration errors
|
||||
|
||||
## Updating Existing Deployments
|
||||
|
||||
If you have an existing deployment:
|
||||
|
||||
```bash
|
||||
# 1. Copy configs to data/
|
||||
cp nginx.conf data/nginx.conf
|
||||
cp nginx-custom-domains.conf data/nginx-custom-domains.conf
|
||||
|
||||
# 2. Update docker-compose.yml
|
||||
# (Already updated - change volume paths from ./ to ./data/)
|
||||
|
||||
# 3. Restart nginx
|
||||
docker-compose restart nginx
|
||||
|
||||
# 4. Verify
|
||||
curl http://localhost
|
||||
curl -k https://localhost
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
|
||||
### If You Edit Nginx Config
|
||||
```bash
|
||||
# Edit the config in data/, NOT in root
|
||||
nano data/nginx.conf
|
||||
nano data/nginx-custom-domains.conf
|
||||
|
||||
# Then restart nginx
|
||||
docker-compose restart nginx
|
||||
```
|
||||
|
||||
### Root Files Now Optional
|
||||
The old `nginx.conf` and `nginx-custom-domains.conf` in the root can be:
|
||||
- **Deleted** (cleanest - all runtime files in data/)
|
||||
- **Kept** (reference/backup - but not used by containers)
|
||||
|
||||
### Recommendations
|
||||
- Delete root nginx config files for cleaner repository
|
||||
- Keep in `.gitignore` if you want to preserve them as backups
|
||||
- All active configs now in `data/` folder which can be `.gitignore`d
|
||||
|
||||
## Related Changes
|
||||
|
||||
Part of ongoing simplification:
|
||||
1. ✅ Option 1 Implementation - Dockerfile-based code deployment
|
||||
2. ✅ Groups feature archived
|
||||
3. ✅ Legacy playlist routes simplified
|
||||
4. ✅ Nginx configs now in data/ folder
|
||||
|
||||
All contributing to:
|
||||
- Cleaner repository structure
|
||||
- Complete persistence isolation
|
||||
- Production-ready deployment model
|
||||
84
old_code_documentation/NGINX_SETUP_QUICK.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Quick Start: Nginx Setup for DigiServer v2
|
||||
|
||||
## Pre-requisites
|
||||
- SSL certificates in `./data/nginx-ssl/cert.pem` and `./data/nginx-ssl/key.pem`
|
||||
- Docker and Docker Compose installed
|
||||
- Port 80 and 443 available
|
||||
|
||||
## Quick Setup (3 steps)
|
||||
|
||||
### 1. Generate Self-Signed Certificates
|
||||
```bash
|
||||
./generate_nginx_certs.sh localhost 365
|
||||
```
|
||||
|
||||
### 2. Update Nginx Configuration
|
||||
- Edit `nginx.conf` to set your domain:
|
||||
```nginx
|
||||
server_name localhost; # Change to your domain
|
||||
```
|
||||
|
||||
### 3. Start Docker Compose
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
### Check if Nginx is running
|
||||
```bash
|
||||
docker ps | grep nginx
|
||||
```
|
||||
|
||||
### Test HTTP → HTTPS redirect
|
||||
```bash
|
||||
curl -L http://localhost
|
||||
```
|
||||
|
||||
### Test HTTPS (with self-signed cert)
|
||||
```bash
|
||||
curl -k https://localhost
|
||||
```
|
||||
|
||||
### View logs
|
||||
```bash
|
||||
docker logs digiserver-nginx
|
||||
docker exec digiserver-nginx tail -f /var/log/nginx/access.log
|
||||
```
|
||||
|
||||
## Using Production Certificates
|
||||
|
||||
### Option A: Let's Encrypt (Free)
|
||||
1. Install certbot: `apt-get install certbot`
|
||||
2. Generate cert: `certbot certonly --standalone -d your-domain.com`
|
||||
3. Copy cert: `cp /etc/letsencrypt/live/your-domain.com/fullchain.pem ./data/nginx-ssl/cert.pem`
|
||||
4. Copy key: `cp /etc/letsencrypt/live/your-domain.com/privkey.pem ./data/nginx-ssl/key.pem`
|
||||
5. Fix permissions: `sudo chown 101:101 ./data/nginx-ssl/*`
|
||||
6. Reload: `docker exec digiserver-nginx nginx -s reload`
|
||||
|
||||
### Option B: Commercial Certificate
|
||||
1. Place your certificate files in `./data/nginx-ssl/cert.pem` and `./data/nginx-ssl/key.pem`
|
||||
2. Fix permissions: `sudo chown 101:101 ./data/nginx-ssl/*`
|
||||
3. Reload: `docker exec digiserver-nginx nginx -s reload`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| Port 80/443 in use | `sudo netstat -tlnp \| grep :80` or `:443` |
|
||||
| Certificate permission denied | `sudo chown 101:101 ./data/nginx-ssl/*` |
|
||||
| Nginx won't start | `docker logs digiserver-nginx` |
|
||||
| Connection refused | Check firewall: `sudo ufw allow 80/tcp && sudo ufw allow 443/tcp` |
|
||||
|
||||
## File Locations
|
||||
- Main config: `./nginx.conf`
|
||||
- SSL certs: `./data/nginx-ssl/`
|
||||
- Logs: `./data/nginx-logs/`
|
||||
- Custom domains: `./nginx-custom-domains.conf` (auto-generated)
|
||||
|
||||
## Next: Production Setup
|
||||
1. Update `.env` with your DOMAIN and EMAIL
|
||||
2. Configure HTTPS settings in admin panel
|
||||
3. Run: `python nginx_manager.py generate`
|
||||
4. Test: `docker exec digiserver-nginx nginx -t`
|
||||
5. Reload: `docker exec digiserver-nginx nginx -s reload`
|
||||