mirror of
https://github.com/acedanger/shell.git
synced 2025-12-05 21:40:12 -08:00
feat: Implement comprehensive backup web application with Docker, systemd service, and Gunicorn support
This commit is contained in:
200
DEPLOYMENT-GUIDE.md
Normal file
200
DEPLOYMENT-GUIDE.md
Normal file
@@ -0,0 +1,200 @@
|
||||
# Backup Web Application Deployment Guide
|
||||
|
||||
This guide covers multiple methods to keep the backup web application running perpetually on your server.
|
||||
|
||||
## Deployment Options
|
||||
|
||||
### 1. 🚀 Systemd Service (Recommended for Production)
|
||||
|
||||
**Best for:** Production environments, automatic startup on boot, proper logging, and system integration.
|
||||
|
||||
#### Setup Steps:
|
||||
|
||||
```bash
|
||||
# Install the service
|
||||
sudo ./manage-backup-web-service.sh install
|
||||
|
||||
# Start the service
|
||||
sudo ./manage-backup-web-service.sh start
|
||||
|
||||
# Check status
|
||||
./manage-backup-web-service.sh status
|
||||
|
||||
# View logs
|
||||
./manage-backup-web-service.sh logs
|
||||
```
|
||||
|
||||
#### Service Management:
|
||||
|
||||
```bash
|
||||
# Start/Stop/Restart
|
||||
sudo systemctl start backup-web-app
|
||||
sudo systemctl stop backup-web-app
|
||||
sudo systemctl restart backup-web-app
|
||||
|
||||
# Enable/Disable auto-start on boot
|
||||
sudo systemctl enable backup-web-app
|
||||
sudo systemctl disable backup-web-app
|
||||
|
||||
# Check logs
|
||||
sudo journalctl -u backup-web-app -f
|
||||
```
|
||||
|
||||
### 2. 🐳 Docker (Recommended for Isolation)
|
||||
|
||||
**Best for:** Containerized environments, easy deployment, consistent runtime.
|
||||
|
||||
#### Using Docker Compose:
|
||||
|
||||
```bash
|
||||
# Build and start
|
||||
docker-compose up -d
|
||||
|
||||
# View logs
|
||||
docker-compose logs -f
|
||||
|
||||
# Stop
|
||||
docker-compose down
|
||||
|
||||
# Rebuild and restart
|
||||
docker-compose up -d --build
|
||||
```
|
||||
|
||||
#### Using Docker directly:
|
||||
|
||||
```bash
|
||||
# Build image
|
||||
docker build -t backup-web-app .
|
||||
|
||||
# Run container
|
||||
docker run -d \
|
||||
--name backup-web-app \
|
||||
-p 5000:5000 \
|
||||
-v /mnt/share/media/backups:/data/backups:ro \
|
||||
-e BACKUP_ROOT=/data/backups \
|
||||
--restart unless-stopped \
|
||||
backup-web-app
|
||||
```
|
||||
|
||||
### 3. 📺 Screen Session (Quick & Simple)
|
||||
|
||||
**Best for:** Development, testing, quick deployments.
|
||||
|
||||
```bash
|
||||
# Start the application
|
||||
./run-backup-web-screen.sh start
|
||||
|
||||
# Check status
|
||||
./run-backup-web-screen.sh status
|
||||
|
||||
# View logs (connect to session)
|
||||
./run-backup-web-screen.sh logs
|
||||
|
||||
# Stop the application
|
||||
./run-backup-web-screen.sh stop
|
||||
```
|
||||
|
||||
### 4. ⚡ Production with Gunicorn
|
||||
|
||||
**Best for:** High-performance production deployments.
|
||||
|
||||
```bash
|
||||
# Install gunicorn
|
||||
pip install gunicorn
|
||||
|
||||
# Run with production settings
|
||||
./run-production.sh
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `BACKUP_ROOT`: Path to backup directory (default: `/mnt/share/media/backups`)
|
||||
- `PORT`: Application port (default: `5000`)
|
||||
- `FLASK_ENV`: Environment mode (`development` or `production`)
|
||||
- `FLASK_DEBUG`: Enable debug mode (`true` or `false`)
|
||||
|
||||
### Security Considerations
|
||||
|
||||
1. **Firewall**: Ensure port 5000 is properly secured
|
||||
2. **Reverse Proxy**: Consider using nginx for SSL termination
|
||||
3. **Authentication**: Add authentication for production use
|
||||
4. **File Permissions**: Ensure proper read permissions for backup directories
|
||||
|
||||
## Monitoring & Maintenance
|
||||
|
||||
### Health Checks
|
||||
|
||||
The application provides a health endpoint:
|
||||
```bash
|
||||
curl http://localhost:5000/health
|
||||
```
|
||||
|
||||
### Log Locations
|
||||
|
||||
- **Systemd**: `sudo journalctl -u backup-web-app`
|
||||
- **Docker**: `docker-compose logs` or `docker logs backup-web-app`
|
||||
- **Screen**: Connect to session with `screen -r backup-web-app`
|
||||
- **Gunicorn**: `/tmp/backup-web-app-access.log` and `/tmp/backup-web-app-error.log`
|
||||
|
||||
### Automatic Restarts
|
||||
|
||||
- **Systemd**: Built-in restart on failure
|
||||
- **Docker**: Use `--restart unless-stopped` or `restart: unless-stopped` in compose
|
||||
- **Screen**: Manual restart required
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Port already in use**:
|
||||
```bash
|
||||
sudo lsof -i :5000
|
||||
sudo netstat -tulpn | grep :5000
|
||||
```
|
||||
|
||||
2. **Permission denied for backup directory**:
|
||||
```bash
|
||||
sudo chown -R acedanger:acedanger /mnt/share/media/backups
|
||||
chmod -R 755 /mnt/share/media/backups
|
||||
```
|
||||
|
||||
3. **Service won't start**:
|
||||
```bash
|
||||
sudo journalctl -u backup-web-app -n 50
|
||||
```
|
||||
|
||||
### Performance Tuning
|
||||
|
||||
1. **Gunicorn Workers**: Adjust in `gunicorn.conf.py`
|
||||
2. **Memory Limits**: Set in systemd service or docker-compose
|
||||
3. **Log Rotation**: Configure logrotate for production
|
||||
|
||||
## Quick Start Commands
|
||||
|
||||
```bash
|
||||
# For development/testing (Screen)
|
||||
./run-backup-web-screen.sh start
|
||||
|
||||
# For production (Systemd)
|
||||
sudo ./manage-backup-web-service.sh install
|
||||
sudo ./manage-backup-web-service.sh start
|
||||
|
||||
# For containerized (Docker)
|
||||
docker-compose up -d
|
||||
|
||||
# Check if running
|
||||
curl http://localhost:5000/health
|
||||
```
|
||||
|
||||
## Recommended Setup
|
||||
|
||||
For a production server, use this combination:
|
||||
|
||||
1. **Primary**: Systemd service for reliability
|
||||
2. **Backup**: Docker setup for easy maintenance
|
||||
3. **Monitoring**: Set up log monitoring and alerts
|
||||
4. **Security**: Add reverse proxy with SSL
|
||||
|
||||
Choose the method that best fits your infrastructure and requirements!
|
||||
37
Dockerfile
Normal file
37
Dockerfile
Normal file
@@ -0,0 +1,37 @@
|
||||
# Dockerfile for Backup Web Application
|
||||
FROM python:3.11-slim
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy requirements and install Python dependencies
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy application files
|
||||
COPY backup-web-app.py .
|
||||
COPY templates/ ./templates/
|
||||
COPY static/ ./static/
|
||||
|
||||
# Create non-root user
|
||||
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
|
||||
USER appuser
|
||||
|
||||
# Expose port
|
||||
EXPOSE 5000
|
||||
|
||||
# Environment variables
|
||||
ENV FLASK_ENV=production
|
||||
ENV BACKUP_ROOT=/data/backups
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:5000/health || exit 1
|
||||
|
||||
# Run application
|
||||
CMD ["python", "backup-web-app.py"]
|
||||
@@ -16,9 +16,8 @@ Author: Shell Repository
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from flask import Flask, render_template, jsonify, request, send_file, abort
|
||||
from datetime import datetime
|
||||
from flask import Flask, render_template, jsonify, request, abort
|
||||
from werkzeug.utils import secure_filename
|
||||
import subprocess
|
||||
|
||||
@@ -48,10 +47,10 @@ def load_json_file(filepath):
|
||||
"""Safely load JSON file with error handling"""
|
||||
try:
|
||||
if os.path.exists(filepath):
|
||||
with open(filepath, 'r') as f:
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading JSON file {filepath}: {e}")
|
||||
except (OSError, json.JSONDecodeError, UnicodeDecodeError) as e:
|
||||
logger.error("Error loading JSON file %s: %s", filepath, e)
|
||||
return None
|
||||
|
||||
|
||||
@@ -206,7 +205,6 @@ def index():
|
||||
"""Main dashboard"""
|
||||
try:
|
||||
# Get all services with their metrics
|
||||
service_names = get_services()
|
||||
services_data = []
|
||||
|
||||
# Status counters for summary
|
||||
@@ -259,8 +257,8 @@ def index():
|
||||
}
|
||||
|
||||
return render_template('dashboard.html', data=dashboard_data)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in index route: {e}")
|
||||
except (OSError, IOError, json.JSONDecodeError) as e:
|
||||
logger.error("Error in index route: %s", e)
|
||||
return f"Error: {e}", 500
|
||||
|
||||
|
||||
@@ -285,8 +283,9 @@ def api_service_details(service_name):
|
||||
'backup_files': backup_files,
|
||||
'log_files': log_files
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting service details for {service_name}: {e}")
|
||||
except (OSError, IOError, json.JSONDecodeError) as e:
|
||||
logger.error("Error getting service details for %s: %s",
|
||||
service_name, e)
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@@ -328,8 +327,8 @@ def service_detail(service_name):
|
||||
service_data['latest_backup'] = latest_backup['path']
|
||||
|
||||
return render_template('service.html', service=service_data)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in service detail for {service_name}: {e}")
|
||||
except (OSError, IOError, json.JSONDecodeError) as e:
|
||||
logger.error("Error in service detail for %s: %s", service_name, e)
|
||||
return f"Error: {e}", 500
|
||||
|
||||
|
||||
@@ -369,8 +368,8 @@ def logs_view():
|
||||
})
|
||||
|
||||
return render_template('logs.html', logs=formatted_logs, filter_service=service_filter)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in logs view: {e}")
|
||||
except (OSError, IOError) as e:
|
||||
logger.error("Error in logs view: %s", e)
|
||||
return f"Error: {e}", 500
|
||||
|
||||
|
||||
@@ -408,7 +407,7 @@ def view_log(filename):
|
||||
# Read last N lines for large files
|
||||
max_lines = int(request.args.get('lines', 1000))
|
||||
|
||||
with open(log_path, 'r') as f:
|
||||
with open(log_path, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
if len(lines) > max_lines:
|
||||
lines = lines[-max_lines:]
|
||||
@@ -427,8 +426,8 @@ def view_log(filename):
|
||||
"%Y-%m-%d %H:%M:%S"),
|
||||
total_lines=len(lines),
|
||||
lines_shown=min(len(lines), max_lines))
|
||||
except Exception as e:
|
||||
logger.error(f"Error viewing log {filename}: {e}")
|
||||
except (OSError, IOError, UnicodeDecodeError, ValueError) as e:
|
||||
logger.error("Error viewing log %s: %s", filename, e)
|
||||
return f"Error: {e}", 500
|
||||
|
||||
|
||||
@@ -449,7 +448,8 @@ def api_refresh_metrics():
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=300 # 5 minute timeout
|
||||
timeout=300, # 5 minute timeout
|
||||
check=False
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
@@ -460,7 +460,7 @@ def api_refresh_metrics():
|
||||
'output': result.stdout
|
||||
})
|
||||
else:
|
||||
logger.error(f"Metrics refresh failed: {result.stderr}")
|
||||
logger.error("Metrics refresh failed: %s", result.stderr)
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': 'Metrics refresh failed',
|
||||
@@ -477,8 +477,8 @@ def api_refresh_metrics():
|
||||
'status': 'error',
|
||||
'message': 'Metrics refresh timed out'
|
||||
}), 408
|
||||
except Exception as e:
|
||||
logger.error(f"Error refreshing metrics: {e}")
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
logger.error("Error refreshing metrics: %s", e)
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': str(e)
|
||||
@@ -498,14 +498,14 @@ def health_check():
|
||||
|
||||
|
||||
@app.errorhandler(404)
|
||||
def not_found(error):
|
||||
def not_found(_error):
|
||||
return render_template('error.html',
|
||||
error_code=404,
|
||||
error_message="Page not found"), 404
|
||||
|
||||
|
||||
@app.errorhandler(500)
|
||||
def internal_error(error):
|
||||
def internal_error(_error):
|
||||
return render_template('error.html',
|
||||
error_code=500,
|
||||
error_message="Internal server error"), 500
|
||||
|
||||
24
backup-web-app.service
Normal file
24
backup-web-app.service
Normal file
@@ -0,0 +1,24 @@
|
||||
[Unit]
|
||||
Description=Backup Web Application
|
||||
After=network.target
|
||||
Wants=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=acedanger
|
||||
Group=acedanger
|
||||
WorkingDirectory=/home/acedanger/shell
|
||||
Environment=PATH=/usr/bin:/usr/local/bin
|
||||
Environment=BACKUP_ROOT=/mnt/share/media/backups
|
||||
Environment=FLASK_ENV=production
|
||||
Environment=PORT=5000
|
||||
ExecStart=/usr/bin/python3 /home/acedanger/shell/backup-web-app.py
|
||||
ExecReload=/bin/kill -s HUP $MAINPID
|
||||
KillMode=mixed
|
||||
TimeoutStopSec=5
|
||||
PrivateTmp=true
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
27
docker-compose.yml
Normal file
27
docker-compose.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
backup-web-app:
|
||||
build: .
|
||||
container_name: backup-web-app
|
||||
ports:
|
||||
- "5000:5000"
|
||||
volumes:
|
||||
- /mnt/share/media/backups:/data/backups:ro
|
||||
- ./logs:/app/logs
|
||||
environment:
|
||||
- BACKUP_ROOT=/data/backups
|
||||
- FLASK_ENV=production
|
||||
- PORT=5000
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
61
gunicorn.conf.py
Normal file
61
gunicorn.conf.py
Normal file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Gunicorn configuration for backup web application
|
||||
|
||||
import os
|
||||
import multiprocessing
|
||||
|
||||
# Server socket
|
||||
bind = f"0.0.0.0:{os.environ.get('PORT', '5000')}"
|
||||
backlog = 2048
|
||||
|
||||
# Worker processes
|
||||
workers = multiprocessing.cpu_count() * 2 + 1
|
||||
worker_class = "sync"
|
||||
worker_connections = 1000
|
||||
timeout = 30
|
||||
keepalive = 2
|
||||
|
||||
# Restart workers after this many requests, to help prevent memory leaks
|
||||
max_requests = 1000
|
||||
max_requests_jitter = 50
|
||||
|
||||
# Logging
|
||||
accesslog = "/tmp/backup-web-app-access.log"
|
||||
errorlog = "/tmp/backup-web-app-error.log"
|
||||
loglevel = "info"
|
||||
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'
|
||||
|
||||
# Process naming
|
||||
proc_name = "backup-web-app"
|
||||
|
||||
# Daemon mode
|
||||
daemon = False
|
||||
pidfile = "/tmp/backup-web-app.pid"
|
||||
umask = 0
|
||||
user = None
|
||||
group = None
|
||||
tmp_upload_dir = None
|
||||
|
||||
# SSL (if needed)
|
||||
# keyfile = "/path/to/keyfile"
|
||||
# certfile = "/path/to/certfile"
|
||||
|
||||
# Environment
|
||||
raw_env = [
|
||||
f"BACKUP_ROOT={os.environ.get('BACKUP_ROOT', '/mnt/share/media/backups')}",
|
||||
]
|
||||
|
||||
# Preload app for better performance
|
||||
preload_app = True
|
||||
|
||||
# Graceful timeout
|
||||
graceful_timeout = 30
|
||||
|
||||
# Security
|
||||
forwarded_allow_ips = "*"
|
||||
secure_scheme_headers = {
|
||||
'X-FORWARDED-PROTOCOL': 'ssl',
|
||||
'X-FORWARDED-PROTO': 'https',
|
||||
'X-FORWARDED-SSL': 'on'
|
||||
}
|
||||
197
manage-backup-web-service.sh
Executable file
197
manage-backup-web-service.sh
Executable file
@@ -0,0 +1,197 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Backup Web Application Service Manager
|
||||
# Manages the backup web application as a systemd service
|
||||
|
||||
set -e
|
||||
|
||||
SERVICE_NAME="backup-web-app"
|
||||
SERVICE_FILE="/home/acedanger/shell/${SERVICE_NAME}.service"
|
||||
SYSTEMD_DIR="/etc/systemd/system"
|
||||
APP_USER="acedanger"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
print_status() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
check_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
print_error "This script must be run as root (use sudo)"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
install_service() {
|
||||
print_status "Installing backup web application service..."
|
||||
|
||||
# Check if service file exists
|
||||
if [[ ! -f "$SERVICE_FILE" ]]; then
|
||||
print_error "Service file not found: $SERVICE_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy service file to systemd directory
|
||||
cp "$SERVICE_FILE" "$SYSTEMD_DIR/"
|
||||
print_success "Service file copied to $SYSTEMD_DIR"
|
||||
|
||||
# Reload systemd daemon
|
||||
systemctl daemon-reload
|
||||
print_success "Systemd daemon reloaded"
|
||||
|
||||
# Enable service to start on boot
|
||||
systemctl enable "$SERVICE_NAME"
|
||||
print_success "Service enabled for auto-start on boot"
|
||||
|
||||
print_success "Service installation completed!"
|
||||
print_status "Use 'sudo systemctl start $SERVICE_NAME' to start the service"
|
||||
}
|
||||
|
||||
start_service() {
|
||||
print_status "Starting backup web application service..."
|
||||
systemctl start "$SERVICE_NAME"
|
||||
sleep 2
|
||||
|
||||
if systemctl is-active --quiet "$SERVICE_NAME"; then
|
||||
print_success "Service started successfully"
|
||||
systemctl status "$SERVICE_NAME" --no-pager -l
|
||||
else
|
||||
print_error "Failed to start service"
|
||||
print_status "Check logs with: sudo journalctl -u $SERVICE_NAME -f"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
print_status "Stopping backup web application service..."
|
||||
systemctl stop "$SERVICE_NAME"
|
||||
print_success "Service stopped"
|
||||
}
|
||||
|
||||
restart_service() {
|
||||
print_status "Restarting backup web application service..."
|
||||
systemctl restart "$SERVICE_NAME"
|
||||
sleep 2
|
||||
|
||||
if systemctl is-active --quiet "$SERVICE_NAME"; then
|
||||
print_success "Service restarted successfully"
|
||||
else
|
||||
print_error "Failed to restart service"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
status_service() {
|
||||
print_status "Service status:"
|
||||
systemctl status "$SERVICE_NAME" --no-pager -l
|
||||
}
|
||||
|
||||
logs_service() {
|
||||
print_status "Following service logs (Ctrl+C to exit):"
|
||||
journalctl -u "$SERVICE_NAME" -f
|
||||
}
|
||||
|
||||
uninstall_service() {
|
||||
print_status "Uninstalling backup web application service..."
|
||||
|
||||
# Stop service if running
|
||||
if systemctl is-active --quiet "$SERVICE_NAME"; then
|
||||
systemctl stop "$SERVICE_NAME"
|
||||
print_status "Service stopped"
|
||||
fi
|
||||
|
||||
# Disable service
|
||||
if systemctl is-enabled --quiet "$SERVICE_NAME"; then
|
||||
systemctl disable "$SERVICE_NAME"
|
||||
print_status "Service disabled"
|
||||
fi
|
||||
|
||||
# Remove service file
|
||||
if [[ -f "$SYSTEMD_DIR/${SERVICE_NAME}.service" ]]; then
|
||||
rm "$SYSTEMD_DIR/${SERVICE_NAME}.service"
|
||||
print_status "Service file removed"
|
||||
fi
|
||||
|
||||
# Reload systemd daemon
|
||||
systemctl daemon-reload
|
||||
print_success "Service uninstalled successfully"
|
||||
}
|
||||
|
||||
show_help() {
|
||||
echo "Backup Web Application Service Manager"
|
||||
echo
|
||||
echo "Usage: $0 {install|start|stop|restart|status|logs|uninstall|help}"
|
||||
echo
|
||||
echo "Commands:"
|
||||
echo " install - Install the service (requires root)"
|
||||
echo " start - Start the service (requires root)"
|
||||
echo " stop - Stop the service (requires root)"
|
||||
echo " restart - Restart the service (requires root)"
|
||||
echo " status - Show service status"
|
||||
echo " logs - Follow service logs"
|
||||
echo " uninstall - Remove the service (requires root)"
|
||||
echo " help - Show this help message"
|
||||
echo
|
||||
echo "Examples:"
|
||||
echo " sudo $0 install # Install and enable the service"
|
||||
echo " sudo $0 start # Start the service"
|
||||
echo " $0 status # Check service status"
|
||||
echo " $0 logs # View live logs"
|
||||
}
|
||||
|
||||
# Main script logic
|
||||
case "${1:-}" in
|
||||
install)
|
||||
check_root
|
||||
install_service
|
||||
;;
|
||||
start)
|
||||
check_root
|
||||
start_service
|
||||
;;
|
||||
stop)
|
||||
check_root
|
||||
stop_service
|
||||
;;
|
||||
restart)
|
||||
check_root
|
||||
restart_service
|
||||
;;
|
||||
status)
|
||||
status_service
|
||||
;;
|
||||
logs)
|
||||
logs_service
|
||||
;;
|
||||
uninstall)
|
||||
check_root
|
||||
uninstall_service
|
||||
;;
|
||||
help|--help|-h)
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
print_error "Invalid command: ${1:-}"
|
||||
echo
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Flask==2.3.3
|
||||
Werkzeug==2.3.7
|
||||
gunicorn==21.2.0
|
||||
150
run-backup-web-screen.sh
Executable file
150
run-backup-web-screen.sh
Executable file
@@ -0,0 +1,150 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Simple script to run backup web app in a persistent screen session
|
||||
|
||||
SESSION_NAME="backup-web-app"
|
||||
APP_DIR="/home/acedanger/shell"
|
||||
PYTHON_CMD="python3"
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
print_status() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
check_screen() {
|
||||
if ! command -v screen &> /dev/null; then
|
||||
print_error "Screen is not installed. Install it with: sudo apt install screen"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
start_app() {
|
||||
check_screen
|
||||
|
||||
# Check if session already exists
|
||||
if screen -list | grep -q "$SESSION_NAME"; then
|
||||
print_warning "Session '$SESSION_NAME' already exists"
|
||||
print_status "Use './run-backup-web-screen.sh status' to check or './run-backup-web-screen.sh stop' to stop"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_status "Starting backup web app in screen session '$SESSION_NAME'..."
|
||||
|
||||
# Start new detached screen session
|
||||
cd "$APP_DIR" || exit 1
|
||||
screen -dmS "$SESSION_NAME" bash -c "
|
||||
export BACKUP_ROOT=/mnt/share/media/backups
|
||||
export FLASK_ENV=production
|
||||
$PYTHON_CMD backup-web-app.py
|
||||
"
|
||||
|
||||
sleep 2
|
||||
|
||||
if screen -list | grep -q "$SESSION_NAME"; then
|
||||
print_status "✅ Backup web app started successfully!"
|
||||
print_status "Session: $SESSION_NAME"
|
||||
print_status "URL: http://localhost:5000"
|
||||
print_status ""
|
||||
print_status "Commands:"
|
||||
print_status " View logs: ./run-backup-web-screen.sh logs"
|
||||
print_status " Stop app: ./run-backup-web-screen.sh stop"
|
||||
print_status " Status: ./run-backup-web-screen.sh status"
|
||||
else
|
||||
print_error "Failed to start the application"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
stop_app() {
|
||||
if screen -list | grep -q "$SESSION_NAME"; then
|
||||
print_status "Stopping backup web app..."
|
||||
screen -S "$SESSION_NAME" -X quit
|
||||
print_status "✅ Application stopped"
|
||||
else
|
||||
print_warning "No session '$SESSION_NAME' found"
|
||||
fi
|
||||
}
|
||||
|
||||
status_app() {
|
||||
if screen -list | grep -q "$SESSION_NAME"; then
|
||||
print_status "✅ Backup web app is running"
|
||||
print_status "Session details:"
|
||||
screen -list | grep "$SESSION_NAME"
|
||||
print_status ""
|
||||
print_status "Access the session with: screen -r $SESSION_NAME"
|
||||
print_status "Detach from session with: Ctrl+A, then D"
|
||||
else
|
||||
print_warning "❌ Backup web app is not running"
|
||||
fi
|
||||
}
|
||||
|
||||
show_logs() {
|
||||
if screen -list | grep -q "$SESSION_NAME"; then
|
||||
print_status "Connecting to session '$SESSION_NAME'..."
|
||||
print_status "Press Ctrl+A, then D to detach from the session"
|
||||
screen -r "$SESSION_NAME"
|
||||
else
|
||||
print_error "No session '$SESSION_NAME' found. App is not running."
|
||||
fi
|
||||
}
|
||||
|
||||
restart_app() {
|
||||
print_status "Restarting backup web app..."
|
||||
stop_app
|
||||
sleep 2
|
||||
start_app
|
||||
}
|
||||
|
||||
show_help() {
|
||||
echo "Backup Web App Screen Manager"
|
||||
echo
|
||||
echo "Usage: $0 {start|stop|restart|status|logs|help}"
|
||||
echo
|
||||
echo "Commands:"
|
||||
echo " start - Start the app in a screen session"
|
||||
echo " stop - Stop the app"
|
||||
echo " restart - Restart the app"
|
||||
echo " status - Check if app is running"
|
||||
echo " logs - Connect to the screen session to view logs"
|
||||
echo " help - Show this help message"
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
start)
|
||||
start_app
|
||||
;;
|
||||
stop)
|
||||
stop_app
|
||||
;;
|
||||
restart)
|
||||
restart_app
|
||||
;;
|
||||
status)
|
||||
status_app
|
||||
;;
|
||||
logs)
|
||||
show_logs
|
||||
;;
|
||||
help|--help|-h)
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
print_error "Invalid command: ${1:-}"
|
||||
echo
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
59
run-production.sh
Executable file
59
run-production.sh
Executable file
@@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Production runner for backup web application using Gunicorn
|
||||
|
||||
APP_DIR="/home/acedanger/shell"
|
||||
APP_MODULE="backup-web-app:app"
|
||||
CONFIG_FILE="gunicorn.conf.py"
|
||||
VENV_PATH="/home/acedanger/shell/venv"
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
print_status() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Check if we're in the right directory
|
||||
cd "$APP_DIR" || {
|
||||
print_error "Cannot change to app directory: $APP_DIR"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check for virtual environment
|
||||
if [[ -d "$VENV_PATH" ]]; then
|
||||
print_status "Activating virtual environment..."
|
||||
source "$VENV_PATH/bin/activate"
|
||||
fi
|
||||
|
||||
# Set environment variables
|
||||
export BACKUP_ROOT="/mnt/share/media/backups"
|
||||
export FLASK_ENV="production"
|
||||
|
||||
# Check if gunicorn is installed
|
||||
if ! command -v gunicorn &> /dev/null; then
|
||||
print_error "Gunicorn is not installed"
|
||||
print_status "Install with: pip install gunicorn"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_status "Starting backup web application with Gunicorn..."
|
||||
print_status "Configuration: $CONFIG_FILE"
|
||||
print_status "Module: $APP_MODULE"
|
||||
print_status "Directory: $APP_DIR"
|
||||
|
||||
# Start Gunicorn
|
||||
exec gunicorn \
|
||||
--config "$CONFIG_FILE" \
|
||||
"$APP_MODULE"
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
|
||||
# Set environment
|
||||
os.environ['BACKUP_ROOT'] = '/home/acedanger/shell'
|
||||
@@ -13,9 +12,9 @@ def load_json_file(filepath):
|
||||
"""Safely load JSON file with error handling"""
|
||||
try:
|
||||
if os.path.exists(filepath):
|
||||
with open(filepath, 'r') as f:
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
except (OSError, json.JSONDecodeError, UnicodeDecodeError) as e:
|
||||
print(f"Error loading JSON file {filepath}: {e}")
|
||||
return None
|
||||
|
||||
@@ -25,35 +24,35 @@ def get_service_metrics(service_name):
|
||||
# Simple status file approach
|
||||
status_file = os.path.join(METRICS_DIR, f'{service_name}_status.json')
|
||||
|
||||
status = load_json_file(status_file)
|
||||
service_status = load_json_file(status_file)
|
||||
|
||||
return {
|
||||
'status': status,
|
||||
'last_run': status.get('end_time') if status else None,
|
||||
'current_status': status.get('status', 'unknown') if status else 'never_run',
|
||||
'files_processed': status.get('files_processed', 0) if status else 0,
|
||||
'total_size': status.get('total_size_bytes', 0) if status else 0,
|
||||
'duration': status.get('duration_seconds', 0) if status else 0
|
||||
'status': service_status,
|
||||
'last_run': service_status.get('end_time') if service_status else None,
|
||||
'current_status': service_status.get('status', 'unknown') if service_status else 'never_run',
|
||||
'files_processed': service_status.get('files_processed', 0) if service_status else 0,
|
||||
'total_size': service_status.get('total_size_bytes', 0) if service_status else 0,
|
||||
'duration': service_status.get('duration_seconds', 0) if service_status else 0
|
||||
}
|
||||
|
||||
|
||||
def get_consolidated_metrics():
|
||||
"""Get consolidated metrics across all services"""
|
||||
# With simplified approach, we consolidate by reading all status files
|
||||
services = {}
|
||||
all_services = {}
|
||||
|
||||
if os.path.exists(METRICS_DIR):
|
||||
for filename in os.listdir(METRICS_DIR):
|
||||
if filename.endswith('_status.json'):
|
||||
service_name = filename.replace('_status.json', '')
|
||||
status_file = os.path.join(METRICS_DIR, filename)
|
||||
status = load_json_file(status_file)
|
||||
if status:
|
||||
services[service_name] = status
|
||||
service_status = load_json_file(status_file)
|
||||
if service_status:
|
||||
all_services[service_name] = service_status
|
||||
|
||||
return {
|
||||
'services': services,
|
||||
'total_services': len(services),
|
||||
'services': all_services,
|
||||
'total_services': len(all_services),
|
||||
'last_updated': '2025-06-18T05:15:00-04:00'
|
||||
}
|
||||
|
||||
@@ -70,7 +69,7 @@ if __name__ == "__main__":
|
||||
files = metrics['files_processed']
|
||||
duration = metrics['duration']
|
||||
print(f' {service}: {status} ({files} files, {duration}s)')
|
||||
except Exception as e:
|
||||
except (OSError, IOError, KeyError) as e:
|
||||
print(f' {service}: Error - {e}')
|
||||
|
||||
# Test consolidated metrics
|
||||
@@ -82,7 +81,7 @@ if __name__ == "__main__":
|
||||
for name, status in services.items():
|
||||
message = status.get('message', 'N/A')
|
||||
print(f' {name}: {status["status"]} - {message}')
|
||||
except Exception as e:
|
||||
except (OSError, IOError, KeyError) as e:
|
||||
print(f' Error: {e}')
|
||||
|
||||
print('\n✅ Web integration test completed successfully!')
|
||||
|
||||
Reference in New Issue
Block a user