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
|
PROGRESS.md
|
||||||
README.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
|
# Install Python dependencies
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
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 . .
|
||||||
|
|
||||||
# Copy and set permissions for entrypoint script
|
# 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
|
import os
|
||||||
from flask import Flask, render_template
|
from flask import Flask, render_template
|
||||||
|
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
from app.config import DevelopmentConfig, ProductionConfig, TestingConfig
|
from app.config import DevelopmentConfig, ProductionConfig, TestingConfig
|
||||||
@@ -37,6 +38,10 @@ def create_app(config_name=None):
|
|||||||
|
|
||||||
app.config.from_object(config)
|
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
|
# Initialize extensions
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
bcrypt.init_app(app)
|
bcrypt.init_app(app)
|
||||||
@@ -47,6 +52,18 @@ def create_app(config_name=None):
|
|||||||
# Configure Flask-Login
|
# Configure Flask-Login
|
||||||
configure_login_manager(app)
|
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 components
|
||||||
register_blueprints(app)
|
register_blueprints(app)
|
||||||
register_error_handlers(app)
|
register_error_handlers(app)
|
||||||
@@ -63,7 +80,6 @@ def register_blueprints(app):
|
|||||||
from app.blueprints.auth import auth_bp
|
from app.blueprints.auth import auth_bp
|
||||||
from app.blueprints.admin import admin_bp
|
from app.blueprints.admin import admin_bp
|
||||||
from app.blueprints.players import players_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.content import content_bp
|
||||||
from app.blueprints.playlist import playlist_bp
|
from app.blueprints.playlist import playlist_bp
|
||||||
from app.blueprints.api import api_bp
|
from app.blueprints.api import api_bp
|
||||||
@@ -73,7 +89,6 @@ def register_blueprints(app):
|
|||||||
app.register_blueprint(auth_bp)
|
app.register_blueprint(auth_bp)
|
||||||
app.register_blueprint(admin_bp)
|
app.register_blueprint(admin_bp)
|
||||||
app.register_blueprint(players_bp)
|
app.register_blueprint(players_bp)
|
||||||
app.register_blueprint(groups_bp)
|
|
||||||
app.register_blueprint(content_bp)
|
app.register_blueprint(content_bp)
|
||||||
app.register_blueprint(playlist_bp)
|
app.register_blueprint(playlist_bp)
|
||||||
app.register_blueprint(api_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 typing import Optional
|
||||||
|
|
||||||
from app.extensions import db, bcrypt
|
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.logger import log_action
|
||||||
from app.utils.caddy_manager import CaddyConfigGenerator
|
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')
|
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
|
||||||
|
|
||||||
@@ -870,10 +871,14 @@ def https_config():
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
log_action('info', f'HTTPS status auto-corrected to enabled (detected from request)')
|
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',
|
return render_template('admin/https_config.html',
|
||||||
config=config,
|
config=config,
|
||||||
is_https_active=is_https_active,
|
is_https_active=is_https_active,
|
||||||
current_host=current_host)
|
current_host=current_host,
|
||||||
|
nginx_status=nginx_status)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log_action('error', f'Error loading HTTPS config page: {str(e)}')
|
log_action('error', f'Error loading HTTPS config page: {str(e)}')
|
||||||
flash('Error loading HTTPS configuration page.', 'danger')
|
flash('Error loading HTTPS configuration page.', 'danger')
|
||||||
|
|||||||
61
app/blueprints/api.py
Executable file → Normal file
@@ -7,7 +7,7 @@ import bcrypt
|
|||||||
from typing import Optional, Dict, List
|
from typing import Optional, Dict, List
|
||||||
|
|
||||||
from app.extensions import db, cache
|
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
|
from app.utils.logger import log_action
|
||||||
|
|
||||||
api_bp = Blueprint('api', __name__, url_prefix='/api')
|
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'])
|
@api_bp.route('/auth/player', methods=['POST'])
|
||||||
@rate_limit(max_requests=120, window=60)
|
@rate_limit(max_requests=120, window=60)
|
||||||
def authenticate_player():
|
def authenticate_player():
|
||||||
@@ -593,31 +599,33 @@ def system_info():
|
|||||||
return jsonify({'error': 'Internal server error'}), 500
|
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 = []
|
# DEPRECATED: Groups functionality has been archived
|
||||||
for group in groups:
|
# @api_bp.route('/groups', methods=['GET'])
|
||||||
groups_data.append({
|
# @rate_limit(max_requests=60, window=60)
|
||||||
'id': group.id,
|
# def list_groups():
|
||||||
'name': group.name,
|
# """List all groups with basic information."""
|
||||||
'description': group.description,
|
# try:
|
||||||
'player_count': group.players.count(),
|
# groups = Group.query.order_by(Group.name).all()
|
||||||
'content_count': group.contents.count()
|
#
|
||||||
})
|
# groups_data = []
|
||||||
|
# for group in groups:
|
||||||
return jsonify({
|
# groups_data.append({
|
||||||
'groups': groups_data,
|
# 'id': group.id,
|
||||||
'count': len(groups_data)
|
# 'name': group.name,
|
||||||
})
|
# 'description': group.description,
|
||||||
|
# 'player_count': group.players.count(),
|
||||||
except Exception as e:
|
# 'content_count': group.contents.count()
|
||||||
log_action('error', f'Error listing groups: {str(e)}')
|
# })
|
||||||
return jsonify({'error': 'Internal server error'}), 500
|
#
|
||||||
|
# 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'])
|
@api_bp.route('/content', methods=['GET'])
|
||||||
@@ -819,6 +827,7 @@ def receive_edited_media():
|
|||||||
db.session.add(edit_record)
|
db.session.add(edit_record)
|
||||||
|
|
||||||
# Update playlist version to force player refresh
|
# Update playlist version to force player refresh
|
||||||
|
playlist = None
|
||||||
if player.playlist_id:
|
if player.playlist_id:
|
||||||
from app.models.playlist import Playlist
|
from app.models.playlist import Playlist
|
||||||
playlist = db.session.get(Playlist, player.playlist_id)
|
playlist = db.session.get(Playlist, player.playlist_id)
|
||||||
@@ -839,7 +848,7 @@ def receive_edited_media():
|
|||||||
'version': version,
|
'version': version,
|
||||||
'old_filename': old_filename,
|
'old_filename': old_filename,
|
||||||
'new_filename': new_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
|
}), 200
|
||||||
|
|
||||||
except Exception as e:
|
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
|
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')
|
@content_bp.route('/upload-media-page')
|
||||||
@login_required
|
@login_required
|
||||||
def upload_media_page():
|
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>')
|
@playlist_bp.route('/<int:player_id>')
|
||||||
@login_required
|
@login_required
|
||||||
def manage_playlist(player_id: int):
|
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)
|
player = Player.query.get_or_404(player_id)
|
||||||
|
|
||||||
# Get content from player's assigned playlist
|
|
||||||
playlist_items = []
|
|
||||||
if player.playlist_id:
|
if player.playlist_id:
|
||||||
playlist = Playlist.query.get(player.playlist_id)
|
# Redirect to the new content management interface
|
||||||
if playlist:
|
return redirect(url_for('content.manage_playlist_content', playlist_id=player.playlist_id))
|
||||||
playlist_items = playlist.get_content_ordered()
|
else:
|
||||||
|
# Player has no playlist assigned
|
||||||
# Get available content (all content not in current playlist)
|
flash('This player has no playlist assigned.', 'warning')
|
||||||
all_content = Content.query.all()
|
return redirect(url_for('players.manage_player', player_id=player_id))
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
@playlist_bp.route('/<int:player_id>/add', methods=['POST'])
|
@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_HTTPONLY = True
|
||||||
SESSION_COOKIE_SAMESITE = 'Lax'
|
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
|
# Cache
|
||||||
SEND_FILE_MAX_AGE_DEFAULT = 300 # 5 minutes for static files
|
SEND_FILE_MAX_AGE_DEFAULT = 300 # 5 minutes for static files
|
||||||
|
|
||||||
@@ -86,6 +91,7 @@ class ProductionConfig(Config):
|
|||||||
|
|
||||||
# Security
|
# Security
|
||||||
SESSION_COOKIE_SECURE = True
|
SESSION_COOKIE_SECURE = True
|
||||||
|
SESSION_COOKIE_SAMESITE = 'Lax'
|
||||||
WTF_CSRF_ENABLED = True
|
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_login import LoginManager
|
||||||
from flask_migrate import Migrate
|
from flask_migrate import Migrate
|
||||||
from flask_caching import Cache
|
from flask_caching import Cache
|
||||||
|
from flask_cors import CORS
|
||||||
|
|
||||||
# Initialize extensions (will be bound to app in create_app)
|
# Initialize extensions (will be bound to app in create_app)
|
||||||
db = SQLAlchemy()
|
db = SQLAlchemy()
|
||||||
@@ -14,6 +15,7 @@ bcrypt = Bcrypt()
|
|||||||
login_manager = LoginManager()
|
login_manager = LoginManager()
|
||||||
migrate = Migrate()
|
migrate = Migrate()
|
||||||
cache = Cache()
|
cache = Cache()
|
||||||
|
cors = CORS()
|
||||||
|
|
||||||
# Configure login manager
|
# Configure login manager
|
||||||
login_manager.login_view = 'auth.login'
|
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>
|
</form>
|
||||||
</div>
|
</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 -->
|
<!-- Information Section -->
|
||||||
<div class="card info-card">
|
<div class="card info-card">
|
||||||
<h2>ℹ️ Important Information</h2>
|
<h2>ℹ️ Important Information</h2>
|
||||||
@@ -466,6 +555,45 @@
|
|||||||
color: #0066cc;
|
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 {
|
.info-sections {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
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;
|
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 {
|
.audio-checkbox {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -154,6 +215,36 @@
|
|||||||
body.dark-mode .available-content {
|
body.dark-mode .available-content {
|
||||||
color: #e2e8f0;
|
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>
|
</style>
|
||||||
|
|
||||||
<div class="container" style="max-width: 1400px;">
|
<div class="container" style="max-width: 1400px;">
|
||||||
@@ -230,7 +321,27 @@
|
|||||||
{% elif content.content_type == 'pdf' %}📄 PDF
|
{% elif content.content_type == 'pdf' %}📄 PDF
|
||||||
{% else %}📁 Other{% endif %}
|
{% else %}📁 Other{% endif %}
|
||||||
</td>
|
</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>
|
<td>
|
||||||
{% if content.content_type == 'video' %}
|
{% if content.content_type == 'video' %}
|
||||||
<label class="audio-toggle">
|
<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) {
|
function toggleAudio(contentId, enabled) {
|
||||||
const muted = !enabled; // Checkbox is "enabled audio", but backend stores "muted"
|
const muted = !enabled; // Checkbox is "enabled audio", but backend stores "muted"
|
||||||
const playlistId = {{ playlist.id }};
|
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 -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Check if docker-compose is available
|
# Check if docker compose is available
|
||||||
if ! command -v docker-compose &> /dev/null; then
|
if ! docker compose version &> /dev/null; then
|
||||||
echo -e "${RED}❌ docker-compose not found!${NC}"
|
echo -e "${RED}❌ docker compose not found!${NC}"
|
||||||
echo "Please install docker-compose first"
|
echo "Please install docker compose first"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -30,6 +30,38 @@ if [ ! -f "docker-compose.yml" ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
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
|
# CONFIGURATION VARIABLES
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -51,15 +83,15 @@ echo ""
|
|||||||
# STEP 1: Start containers
|
# STEP 1: Start containers
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
echo -e "${YELLOW}📦 [1/6] Starting containers...${NC}"
|
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}"
|
echo -e "${YELLOW}⏳ Waiting for containers to be healthy...${NC}"
|
||||||
sleep 10
|
sleep 10
|
||||||
|
|
||||||
# Verify containers are running
|
# 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}"
|
echo -e "${RED}❌ Containers failed to start!${NC}"
|
||||||
docker-compose logs
|
docker compose logs
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo -e "${GREEN}✅ Containers started successfully${NC}"
|
echo -e "${GREEN}✅ Containers started successfully${NC}"
|
||||||
@@ -71,13 +103,13 @@ echo ""
|
|||||||
echo -e "${YELLOW}📊 [2/6] Running database migrations...${NC}"
|
echo -e "${YELLOW}📊 [2/6] Running database migrations...${NC}"
|
||||||
|
|
||||||
echo -e " • Creating https_config table..."
|
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..."
|
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..."
|
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..."
|
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 -e "${GREEN}✅ All database migrations completed${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -87,7 +119,7 @@ echo ""
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
echo -e "${YELLOW}🔒 [3/6] Configuring HTTPS...${NC}"
|
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" \
|
"$HOSTNAME" \
|
||||||
"$DOMAIN" \
|
"$DOMAIN" \
|
||||||
"$EMAIL" \
|
"$EMAIL" \
|
||||||
@@ -102,7 +134,7 @@ echo ""
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
echo -e "${YELLOW}🔍 [4/6] Verifying database setup...${NC}"
|
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 app.app import create_app
|
||||||
from sqlalchemy import inspect
|
from sqlalchemy import inspect
|
||||||
|
|
||||||
@@ -123,7 +155,7 @@ echo ""
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
echo -e "${YELLOW}🔧 [5/6] Verifying Caddy configuration...${NC}"
|
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
|
if [ $? -eq 0 ]; then
|
||||||
echo -e " ${GREEN}✅ Caddy configuration is valid${NC}"
|
echo -e " ${GREEN}✅ Caddy configuration is valid${NC}"
|
||||||
else
|
else
|
||||||
@@ -137,7 +169,7 @@ echo ""
|
|||||||
echo -e "${YELLOW}📋 [6/6] Displaying configuration summary...${NC}"
|
echo -e "${YELLOW}📋 [6/6] Displaying configuration summary...${NC}"
|
||||||
echo ""
|
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 ""
|
||||||
echo -e "${GREEN}╔════════════════════════════════════════════════════════════════╗${NC}"
|
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:
|
expose:
|
||||||
- "5000"
|
- "5000"
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/app
|
# Code is in the Docker image - no volume mount needed
|
||||||
|
# Only mount persistent data folders:
|
||||||
- ./data/instance:/app/instance
|
- ./data/instance:/app/instance
|
||||||
- ./data/uploads:/app/app/static/uploads
|
- ./data/uploads:/app/app/static/uploads
|
||||||
environment:
|
environment:
|
||||||
@@ -26,19 +27,19 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- digiserver-network
|
- digiserver-network
|
||||||
|
|
||||||
# Caddy reverse proxy with automatic HTTPS
|
# Nginx reverse proxy with HTTPS support
|
||||||
caddy:
|
nginx:
|
||||||
image: caddy:2-alpine
|
image: nginx:alpine
|
||||||
container_name: digiserver-caddy
|
container_name: digiserver-nginx
|
||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
- "443:443"
|
- "443:443"
|
||||||
- "443:443/udp" # HTTP/3 support
|
|
||||||
- "2019:2019" # Caddy admin API
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/Caddyfile:/etc/caddy/Caddyfile:ro
|
- ./data/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||||
- ./data/caddy-data:/data
|
- ./data/nginx-custom-domains.conf:/etc/nginx/conf.d/custom-domains.conf:rw
|
||||||
- ./data/caddy-config:/config
|
- ./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:
|
environment:
|
||||||
- DOMAIN=${DOMAIN:-localhost}
|
- DOMAIN=${DOMAIN:-localhost}
|
||||||
- EMAIL=${EMAIL:-admin@localhost}
|
- EMAIL=${EMAIL:-admin@localhost}
|
||||||
@@ -46,6 +47,12 @@ services:
|
|||||||
digiserver-app:
|
digiserver-app:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
networks:
|
networks:
|
||||||
- digiserver-network
|
- 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`
|
||||||