updated player deployment for digiserver
This commit is contained in:
+564
@@ -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
|
||||
@@ -0,0 +1,71 @@
|
||||
# DigiServer Standalone Deployment Archive
|
||||
|
||||
This folder contains files from the original standalone DigiServer v2 deployment setup. These files are **no longer used** in the Enterprise Digital Platform integrated environment.
|
||||
|
||||
## Why These Files Are Here
|
||||
|
||||
DigiServer v2 was originally developed as a standalone application with its own:
|
||||
- Docker Compose configuration
|
||||
- Nginx reverse proxy
|
||||
- Deployment scripts
|
||||
- HTTPS/SSL setup utilities
|
||||
|
||||
When DigiServer was integrated into the Enterprise Digital Platform, these functions were consolidated into the platform-wide infrastructure (root-level `docker-compose.yml`, central nginx, etc.).
|
||||
|
||||
## Files in This Archive
|
||||
|
||||
| File | Purpose | Status |
|
||||
|------|---------|--------|
|
||||
| `docker-compose.yml` | Standalone app orchestration | ❌ Replaced by root docker-compose.yml |
|
||||
| `nginx.conf` | Standalone reverse proxy config | ❌ Replaced by platform nginx |
|
||||
| `nginx-custom-domains.conf` | Custom domain support | ❌ Replaced by platform nginx |
|
||||
| `QUICK_DEPLOYMENT.md` | Standalone deployment guide | 📚 Reference only |
|
||||
| `deploy.sh` | Standalone deployment script | ❌ No longer needed |
|
||||
| `verify-deployment.sh` | Standalone verification script | ❌ No longer needed |
|
||||
| `deployment-commands-reference.sh` | Deployment command reference | 📚 Reference only |
|
||||
| `generate_nginx_certs.sh` | Standalone HTTPS cert generation | ❌ Platform handles this |
|
||||
| `migrate_network.sh` | Network migration utility | ❌ Standalone only |
|
||||
|
||||
## Files Still in Use
|
||||
|
||||
The following files from the original setup are **still active** and needed:
|
||||
|
||||
- ✅ **Dockerfile** - Used by platform docker-compose.yml to build digiserver image
|
||||
- ✅ **docker-entrypoint.sh** - Referenced by Dockerfile as container startup script
|
||||
|
||||
## When You Might Need Files from This Archive
|
||||
|
||||
### Reference Information
|
||||
- Review `QUICK_DEPLOYMENT.md` if you need to understand original standalone setup
|
||||
- Check `deployment-commands-reference.sh` for historical deployment context
|
||||
|
||||
### Troubleshooting
|
||||
- `verify-deployment.sh` - Could be adapted for debugging container health
|
||||
- `nginx.conf` - Reference for nginx configuration patterns (platform uses central nginx)
|
||||
|
||||
### Historical Record
|
||||
- `deploy.sh` - Shows original deployment workflow
|
||||
- `docker-compose.yml` - Shows original app structure for reference
|
||||
|
||||
## For Enterprise Platform
|
||||
|
||||
All deployment is now handled through:
|
||||
- **Root `docker-compose.yml`** - Orchestrates all services including digiserver
|
||||
- **Root `nginx/nginx.conf`** - Central reverse proxy configuration
|
||||
- **Docker multi-service architecture** - All apps in one network
|
||||
|
||||
## Restoring a File (if needed)
|
||||
|
||||
To restore any file from this archive to active use:
|
||||
|
||||
```bash
|
||||
cp standalone-deployment-archive/<filename> ../
|
||||
```
|
||||
|
||||
However, most files will need modifications to work with the integrated platform architecture.
|
||||
|
||||
---
|
||||
|
||||
**Archive Date:** June 7, 2026
|
||||
**DigiServer Integration:** Complete
|
||||
**Status:** Enterprise Digital Platform v1.0
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
#!/bin/bash
|
||||
# Automated deployment script for DigiServer on a new PC
|
||||
# Run this script to completely set up DigiServer with all configurations
|
||||
|
||||
set -e # Exit on any error
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${BLUE}║ DigiServer Automated Deployment ║${NC}"
|
||||
echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
# Check if docker compose is available
|
||||
if ! docker compose version &> /dev/null; then
|
||||
echo -e "${RED}❌ docker compose not found!${NC}"
|
||||
echo "Please install docker compose first"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if we're in the project directory
|
||||
if [ ! -f "docker-compose.yml" ]; then
|
||||
echo -e "${RED}❌ docker-compose.yml not found!${NC}"
|
||||
echo "Please run this script from the digiserver-v2 directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
# INITIALIZATION: Create data directories and copy nginx configs
|
||||
# ============================================================================
|
||||
echo -e "${YELLOW}📁 Initializing data directories...${NC}"
|
||||
|
||||
# Create necessary data directories
|
||||
mkdir -p data/instance
|
||||
mkdir -p data/uploads
|
||||
mkdir -p data/nginx-ssl
|
||||
mkdir -p data/nginx-logs
|
||||
mkdir -p data/certbot
|
||||
|
||||
# Copy nginx configuration files from repo root to data folder
|
||||
if [ -f "nginx.conf" ]; then
|
||||
cp nginx.conf data/nginx.conf
|
||||
echo -e " ${GREEN}✓${NC} nginx.conf copied to data/"
|
||||
else
|
||||
echo -e " ${RED}❌ nginx.conf not found in repo root!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f "nginx-custom-domains.conf" ]; then
|
||||
cp nginx-custom-domains.conf data/nginx-custom-domains.conf
|
||||
echo -e " ${GREEN}✓${NC} nginx-custom-domains.conf copied to data/"
|
||||
else
|
||||
echo -e " ${RED}❌ nginx-custom-domains.conf not found in repo root!${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✅ Data directories initialized${NC}"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# CONFIGURATION VARIABLES
|
||||
# ============================================================================
|
||||
HOSTNAME="${HOSTNAME:-digiserver}"
|
||||
DOMAIN="${DOMAIN:-digiserver.sibiusb.harting.intra}"
|
||||
IP_ADDRESS="${IP_ADDRESS:-10.76.152.164}"
|
||||
EMAIL="${EMAIL:-admin@example.com}"
|
||||
PORT="${PORT:-443}"
|
||||
|
||||
echo -e "${BLUE}Configuration:${NC}"
|
||||
echo " Hostname: $HOSTNAME"
|
||||
echo " Domain: $DOMAIN"
|
||||
echo " IP Address: $IP_ADDRESS"
|
||||
echo " Email: $EMAIL"
|
||||
echo " Port: $PORT"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# STEP 1: Start containers
|
||||
# ============================================================================
|
||||
echo -e "${YELLOW}📦 [1/6] Starting containers...${NC}"
|
||||
docker compose up -d
|
||||
|
||||
echo -e "${YELLOW}⏳ Waiting for containers to be healthy...${NC}"
|
||||
sleep 10
|
||||
|
||||
# Verify containers are running
|
||||
if ! docker compose ps | grep -q "Up"; then
|
||||
echo -e "${RED}❌ Containers failed to start!${NC}"
|
||||
docker compose logs
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}✅ Containers started successfully${NC}"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# STEP 2: Run database migrations
|
||||
# ============================================================================
|
||||
echo -e "${YELLOW}📊 [2/6] Running database migrations...${NC}"
|
||||
|
||||
echo -e " • Creating https_config table..."
|
||||
docker compose exec -T digiserver-app python /app/migrations/add_https_config_table.py
|
||||
echo -e " • Creating player_user table..."
|
||||
docker compose exec -T digiserver-app python /app/migrations/add_player_user_table.py
|
||||
echo -e " • Adding email to https_config..."
|
||||
docker compose exec -T digiserver-app python /app/migrations/add_email_to_https_config.py
|
||||
echo -e " • Migrating player_user global settings..."
|
||||
docker compose exec -T digiserver-app python /app/migrations/migrate_player_user_global.py
|
||||
|
||||
echo -e "${GREEN}✅ All database migrations completed${NC}"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# STEP 3: Configure HTTPS
|
||||
# ============================================================================
|
||||
echo -e "${YELLOW}🔒 [3/6] Configuring HTTPS...${NC}"
|
||||
|
||||
docker compose exec -T digiserver-app python /app/https_manager.py enable \
|
||||
"$HOSTNAME" \
|
||||
"$DOMAIN" \
|
||||
"$EMAIL" \
|
||||
"$IP_ADDRESS" \
|
||||
"$PORT"
|
||||
|
||||
echo -e "${GREEN}✅ HTTPS configured successfully${NC}"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# STEP 4: Verify database setup
|
||||
# ============================================================================
|
||||
echo -e "${YELLOW}🔍 [4/6] Verifying database setup...${NC}"
|
||||
|
||||
docker compose exec -T digiserver-app python -c "
|
||||
from app.app import create_app
|
||||
from sqlalchemy import inspect
|
||||
|
||||
app = create_app()
|
||||
with app.app_context():
|
||||
inspector = inspect(app.extensions.db.engine)
|
||||
tables = inspector.get_table_names()
|
||||
print(' Database tables:')
|
||||
for table in sorted(tables):
|
||||
print(f' ✓ {table}')
|
||||
print(f'')
|
||||
print(f' ✅ Total tables: {len(tables)}')
|
||||
" 2>/dev/null || echo " ⚠️ Database verification skipped"
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# STEP 5: Verify Caddy configuration
|
||||
# ============================================================================
|
||||
echo -e "${YELLOW}🔧 [5/6] Verifying Caddy configuration...${NC}"
|
||||
|
||||
docker compose exec -T caddy caddy validate --config /etc/caddy/Caddyfile >/dev/null 2>&1
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e " ${GREEN}✅ Caddy configuration is valid${NC}"
|
||||
else
|
||||
echo -e " ${YELLOW}⚠️ Caddy validation skipped${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ============================================================================
|
||||
# STEP 6: Display summary
|
||||
# ============================================================================
|
||||
echo -e "${YELLOW}📋 [6/6] Displaying configuration summary...${NC}"
|
||||
echo ""
|
||||
|
||||
docker compose exec -T digiserver-app python /app/https_manager.py status
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}╔════════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${GREEN}║ 🎉 Deployment Complete! ║${NC}"
|
||||
echo -e "${GREEN}╚════════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${BLUE}📍 Access Points:${NC}"
|
||||
echo " 🔒 https://$HOSTNAME"
|
||||
echo " 🔒 https://$IP_ADDRESS"
|
||||
echo " 🔒 https://$DOMAIN"
|
||||
echo ""
|
||||
|
||||
echo -e "${BLUE}📝 Default Credentials:${NC}"
|
||||
echo " Username: admin"
|
||||
echo " Password: admin123 (⚠️ CHANGE IN PRODUCTION)"
|
||||
echo ""
|
||||
|
||||
echo -e "${BLUE}📚 Documentation:${NC}"
|
||||
echo " • DEPLOYMENT_COMMANDS.md - Detailed docker exec commands"
|
||||
echo " • HTTPS_CONFIGURATION.md - HTTPS setup details"
|
||||
echo " • setup_https.sh - Manual configuration script"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}Next Steps:${NC}"
|
||||
echo "1. Access the application at one of the URLs above"
|
||||
echo "2. Log in with admin credentials"
|
||||
echo "3. Change the default password immediately"
|
||||
echo "4. Configure your players and content"
|
||||
echo ""
|
||||
|
||||
echo -e "${BLUE}📞 Support:${NC}"
|
||||
echo "For troubleshooting, see DEPLOYMENT_COMMANDS.md section 7"
|
||||
echo ""
|
||||
+246
@@ -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 ""
|
||||
@@ -0,0 +1,61 @@
|
||||
#version: '3.8'
|
||||
|
||||
services:
|
||||
digiserver-app:
|
||||
build: .
|
||||
container_name: digiserver-v2
|
||||
# Don't expose directly; use Caddy reverse proxy instead
|
||||
expose:
|
||||
- "5000"
|
||||
volumes:
|
||||
# Code is in the Docker image - no volume mount needed
|
||||
# Only mount persistent data folders:
|
||||
- ./data/instance:/app/instance
|
||||
- ./data/uploads:/app/app/static/uploads
|
||||
environment:
|
||||
- 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
|
||||
networks:
|
||||
- digiserver-network
|
||||
|
||||
# Nginx reverse proxy with HTTPS support
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
container_name: digiserver-nginx
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./data/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- ./data/nginx-custom-domains.conf:/etc/nginx/conf.d/custom-domains.conf:rw
|
||||
- ./data/nginx-ssl:/etc/nginx/ssl:ro
|
||||
- ./data/nginx-logs:/var/log/nginx
|
||||
- ./data/certbot:/var/www/certbot:ro # For Let's Encrypt ACME challenges
|
||||
environment:
|
||||
- DOMAIN=${DOMAIN:-localhost}
|
||||
- EMAIL=${EMAIL:-admin@localhost}
|
||||
depends_on:
|
||||
digiserver-app:
|
||||
condition: service_started
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
networks:
|
||||
- digiserver-network
|
||||
|
||||
networks:
|
||||
digiserver-network:
|
||||
driver: bridge
|
||||
Executable
+30
@@ -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"
|
||||
+153
@@ -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 ""
|
||||
+21
@@ -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;
|
||||
# }
|
||||
# }
|
||||
@@ -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;
|
||||
}
|
||||
+342
@@ -0,0 +1,342 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Production Deployment Verification Script
|
||||
# Run this before and after production deployment
|
||||
|
||||
set -e
|
||||
|
||||
echo "╔════════════════════════════════════════════════════════════════╗"
|
||||
echo "║ DigiServer v2 Production Deployment Verification ║"
|
||||
echo "╚════════════════════════════════════════════════════════════════╝"
|
||||
|
||||
TIMESTAMP=$(date +%Y-%m-%d\ %H:%M:%S)
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# Color codes
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Counters
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
WARNINGS=0
|
||||
|
||||
# Helper functions
|
||||
pass() {
|
||||
echo -e "${GREEN}✓${NC} $1"
|
||||
((PASSED++))
|
||||
}
|
||||
|
||||
fail() {
|
||||
echo -e "${RED}✗${NC} $1"
|
||||
((FAILED++))
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo -e "${YELLOW}⚠${NC} $1"
|
||||
((WARNINGS++))
|
||||
}
|
||||
|
||||
info() {
|
||||
echo -e "${BLUE}ℹ${NC} $1"
|
||||
}
|
||||
|
||||
section() {
|
||||
echo -e "\n${BLUE}═══════════════════════════════════════${NC}"
|
||||
echo -e "${BLUE} $1${NC}"
|
||||
echo -e "${BLUE}═══════════════════════════════════════${NC}"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
section "1. Git Status"
|
||||
# ============================================================================
|
||||
|
||||
if git rev-parse --git-dir > /dev/null 2>&1; then
|
||||
pass "Git repository initialized"
|
||||
|
||||
BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
COMMIT=$(git rev-parse --short HEAD)
|
||||
info "Current branch: $BRANCH, Commit: $COMMIT"
|
||||
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
warn "Uncommitted changes detected"
|
||||
git status --short
|
||||
else
|
||||
pass "All changes committed"
|
||||
fi
|
||||
else
|
||||
fail "Not a git repository"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
section "2. Environment Configuration"
|
||||
# ============================================================================
|
||||
|
||||
if [ -f .env ]; then
|
||||
pass ".env file exists"
|
||||
else
|
||||
warn ".env file not found (using defaults or docker-compose environment)"
|
||||
fi
|
||||
|
||||
if [ -f .env.example ]; then
|
||||
pass ".env.example template exists"
|
||||
else
|
||||
warn ".env.example template missing"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
section "3. Docker Configuration"
|
||||
# ============================================================================
|
||||
|
||||
if command -v docker &> /dev/null; then
|
||||
pass "Docker installed"
|
||||
DOCKER_VERSION=$(docker --version | cut -d' ' -f3 | tr -d ',')
|
||||
info "Docker version: $DOCKER_VERSION"
|
||||
else
|
||||
fail "Docker not installed"
|
||||
fi
|
||||
|
||||
if command -v docker-compose &> /dev/null; then
|
||||
pass "Docker Compose installed"
|
||||
DC_VERSION=$(docker-compose --version | cut -d' ' -f3 | tr -d ',')
|
||||
info "Docker Compose version: $DC_VERSION"
|
||||
else
|
||||
fail "Docker Compose not installed"
|
||||
fi
|
||||
|
||||
if [ -f docker-compose.yml ]; then
|
||||
pass "docker-compose.yml exists"
|
||||
|
||||
# Validate syntax
|
||||
if docker-compose config > /dev/null 2>&1; then
|
||||
pass "docker-compose.yml syntax valid"
|
||||
else
|
||||
fail "docker-compose.yml syntax error"
|
||||
fi
|
||||
else
|
||||
fail "docker-compose.yml not found"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
section "4. Dockerfile & Images"
|
||||
# ============================================================================
|
||||
|
||||
if [ -f Dockerfile ]; then
|
||||
pass "Dockerfile exists"
|
||||
|
||||
# Check for security best practices
|
||||
if grep -q "HEALTHCHECK" Dockerfile; then
|
||||
pass "Health check configured"
|
||||
else
|
||||
warn "No health check in Dockerfile"
|
||||
fi
|
||||
|
||||
if grep -q "USER appuser" Dockerfile || grep -q "USER.*:1000" Dockerfile; then
|
||||
pass "Non-root user configured"
|
||||
else
|
||||
warn "Root user may be used in container"
|
||||
fi
|
||||
|
||||
if grep -q "FROM.*alpine\|FROM.*slim\|FROM.*distroless" Dockerfile; then
|
||||
pass "Minimal base image used"
|
||||
else
|
||||
warn "Large base image detected"
|
||||
fi
|
||||
else
|
||||
fail "Dockerfile not found"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
section "5. Python Dependencies"
|
||||
# ============================================================================
|
||||
|
||||
if [ -f requirements.txt ]; then
|
||||
pass "requirements.txt exists"
|
||||
|
||||
PACKAGE_COUNT=$(wc -l < requirements.txt)
|
||||
info "Total packages: $PACKAGE_COUNT"
|
||||
|
||||
# Check for critical packages
|
||||
for pkg in Flask SQLAlchemy gunicorn flask-cors cryptography; do
|
||||
if grep -q "$pkg" requirements.txt; then
|
||||
pass "$pkg installed"
|
||||
else
|
||||
warn "$pkg not found in requirements.txt"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check for specific versions
|
||||
FLASK_VERSION=$(grep "^Flask==" requirements.txt | cut -d'=' -f3)
|
||||
SQLALCHEMY_VERSION=$(grep "^SQLAlchemy==" requirements.txt | cut -d'=' -f3)
|
||||
|
||||
if [ -n "$FLASK_VERSION" ]; then
|
||||
info "Flask version: $FLASK_VERSION"
|
||||
fi
|
||||
if [ -n "$SQLALCHEMY_VERSION" ]; then
|
||||
info "SQLAlchemy version: $SQLALCHEMY_VERSION"
|
||||
fi
|
||||
else
|
||||
fail "requirements.txt not found"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
section "6. Database Configuration"
|
||||
# ============================================================================
|
||||
|
||||
if [ -d migrations ]; then
|
||||
pass "migrations directory exists"
|
||||
|
||||
MIGRATION_COUNT=$(find migrations -name "*.py" | wc -l)
|
||||
info "Migration files: $MIGRATION_COUNT"
|
||||
|
||||
if [ "$MIGRATION_COUNT" -gt 0 ]; then
|
||||
pass "Database migrations configured"
|
||||
else
|
||||
warn "No migration files found"
|
||||
fi
|
||||
else
|
||||
warn "migrations directory not found"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
section "7. SSL/TLS Certificate"
|
||||
# ============================================================================
|
||||
|
||||
if [ -f data/nginx-ssl/cert.pem ]; then
|
||||
pass "SSL certificate found"
|
||||
|
||||
CERT_EXPIRY=$(openssl x509 -enddate -noout -in data/nginx-ssl/cert.pem 2>/dev/null | cut -d= -f2)
|
||||
EXPIRY_EPOCH=$(date -d "$CERT_EXPIRY" +%s 2>/dev/null || echo 0)
|
||||
NOW_EPOCH=$(date +%s)
|
||||
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
|
||||
|
||||
info "Certificate expires: $CERT_EXPIRY"
|
||||
info "Days remaining: $DAYS_LEFT days"
|
||||
|
||||
if [ "$DAYS_LEFT" -lt 0 ]; then
|
||||
fail "Certificate has expired!"
|
||||
elif [ "$DAYS_LEFT" -lt 30 ]; then
|
||||
warn "Certificate expires in less than 30 days"
|
||||
else
|
||||
pass "Certificate is valid"
|
||||
fi
|
||||
|
||||
if [ -f data/nginx-ssl/key.pem ]; then
|
||||
pass "SSL private key found"
|
||||
else
|
||||
warn "SSL private key not found"
|
||||
fi
|
||||
else
|
||||
warn "SSL certificate not found (self-signed required)"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
section "8. Configuration Files"
|
||||
# ============================================================================
|
||||
|
||||
if [ -f app/config.py ]; then
|
||||
pass "Flask config.py exists"
|
||||
|
||||
if grep -q "class ProductionConfig" app/config.py; then
|
||||
pass "ProductionConfig class defined"
|
||||
else
|
||||
warn "ProductionConfig class missing"
|
||||
fi
|
||||
|
||||
if grep -q "SESSION_COOKIE_SECURE" app/config.py; then
|
||||
pass "SESSION_COOKIE_SECURE configured"
|
||||
else
|
||||
warn "SESSION_COOKIE_SECURE not configured"
|
||||
fi
|
||||
else
|
||||
fail "app/config.py not found"
|
||||
fi
|
||||
|
||||
if [ -f nginx.conf ]; then
|
||||
pass "nginx.conf exists"
|
||||
|
||||
if grep -q "ssl_protocols" nginx.conf; then
|
||||
pass "SSL protocols configured"
|
||||
else
|
||||
warn "SSL protocols not configured"
|
||||
fi
|
||||
|
||||
if grep -q "access-control-allow" nginx.conf; then
|
||||
pass "CORS headers in nginx"
|
||||
else
|
||||
info "CORS headers may be handled by Flask only"
|
||||
fi
|
||||
else
|
||||
warn "nginx.conf not found"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
section "9. Runtime Verification"
|
||||
# ============================================================================
|
||||
|
||||
if docker-compose ps 2>/dev/null | grep -q "Up"; then
|
||||
pass "Docker containers are running"
|
||||
|
||||
# Check if app is healthy
|
||||
if docker-compose ps 2>/dev/null | grep -q "digiserver-app.*healthy"; then
|
||||
pass "DigiServer app container is healthy"
|
||||
else
|
||||
warn "DigiServer app container health status unknown"
|
||||
fi
|
||||
|
||||
if docker-compose ps 2>/dev/null | grep -q "digiserver-nginx.*healthy"; then
|
||||
pass "Nginx container is healthy"
|
||||
else
|
||||
warn "Nginx container health status unknown"
|
||||
fi
|
||||
else
|
||||
info "Docker containers not running (will start on deployment)"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
section "10. Security Best Practices"
|
||||
# ============================================================================
|
||||
|
||||
# Check for hardcoded secrets
|
||||
if grep -r "SECRET_KEY\|PASSWORD\|API_KEY" app/ 2>/dev/null | grep -v "os.getenv\|config.py\|#" | wc -l | grep -q "^0$"; then
|
||||
pass "No hardcoded secrets found"
|
||||
else
|
||||
warn "Possible hardcoded secrets detected (verify they use os.getenv)"
|
||||
fi
|
||||
|
||||
# Check for debug mode
|
||||
if grep -q "DEBUG.*=.*True" app/config.py 2>/dev/null; then
|
||||
fail "DEBUG mode is enabled"
|
||||
else
|
||||
pass "DEBUG mode is disabled"
|
||||
fi
|
||||
|
||||
# ============================================================================
|
||||
section "Summary"
|
||||
# ============================================================================
|
||||
|
||||
echo ""
|
||||
echo -e "Test Results:"
|
||||
echo -e " ${GREEN}Passed: $PASSED${NC}"
|
||||
echo -e " ${YELLOW}Warnings: $WARNINGS${NC}"
|
||||
echo -e " ${RED}Failed: $FAILED${NC}"
|
||||
|
||||
TOTAL=$((PASSED + FAILED + WARNINGS))
|
||||
PERCENTAGE=$((PASSED * 100 / (PASSED + FAILED)))
|
||||
|
||||
if [ "$FAILED" -eq 0 ]; then
|
||||
echo -e "\n${GREEN}✓ Production Deployment Ready!${NC}"
|
||||
echo "Recommendation: Safe to deploy to production"
|
||||
exit 0
|
||||
elif [ "$FAILED" -le 2 ] && [ "$WARNINGS" -gt 0 ]; then
|
||||
echo -e "\n${YELLOW}⚠ Deployment Possible with Caution${NC}"
|
||||
echo "Recommendation: Address warnings before deployment"
|
||||
exit 0
|
||||
else
|
||||
echo -e "\n${RED}✗ Deployment Not Recommended${NC}"
|
||||
echo "Recommendation: Fix critical failures before deployment"
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user