feat: Implement comprehensive backup web application with Docker, systemd service, and Gunicorn support

This commit is contained in:
Peter Wood
2025-06-18 10:02:07 -04:00
parent 6d726cb015
commit 8cd33d4568
11 changed files with 799 additions and 42 deletions

200
DEPLOYMENT-GUIDE.md Normal file
View 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
View 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"]

View File

@@ -16,9 +16,8 @@ Author: Shell Repository
import os import os
import json import json
import logging import logging
from datetime import datetime, timedelta from datetime import datetime
from pathlib import Path from flask import Flask, render_template, jsonify, request, abort
from flask import Flask, render_template, jsonify, request, send_file, abort
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
import subprocess import subprocess
@@ -48,10 +47,10 @@ def load_json_file(filepath):
"""Safely load JSON file with error handling""" """Safely load JSON file with error handling"""
try: try:
if os.path.exists(filepath): if os.path.exists(filepath):
with open(filepath, 'r') as f: with open(filepath, 'r', encoding='utf-8') as f:
return json.load(f) return json.load(f)
except Exception as e: except (OSError, json.JSONDecodeError, UnicodeDecodeError) as e:
logger.error(f"Error loading JSON file {filepath}: {e}") logger.error("Error loading JSON file %s: %s", filepath, e)
return None return None
@@ -206,7 +205,6 @@ def index():
"""Main dashboard""" """Main dashboard"""
try: try:
# Get all services with their metrics # Get all services with their metrics
service_names = get_services()
services_data = [] services_data = []
# Status counters for summary # Status counters for summary
@@ -259,8 +257,8 @@ def index():
} }
return render_template('dashboard.html', data=dashboard_data) return render_template('dashboard.html', data=dashboard_data)
except Exception as e: except (OSError, IOError, json.JSONDecodeError) as e:
logger.error(f"Error in index route: {e}") logger.error("Error in index route: %s", e)
return f"Error: {e}", 500 return f"Error: {e}", 500
@@ -285,8 +283,9 @@ def api_service_details(service_name):
'backup_files': backup_files, 'backup_files': backup_files,
'log_files': log_files 'log_files': log_files
}) })
except Exception as e: except (OSError, IOError, json.JSONDecodeError) as e:
logger.error(f"Error getting service details for {service_name}: {e}") logger.error("Error getting service details for %s: %s",
service_name, e)
return jsonify({'error': str(e)}), 500 return jsonify({'error': str(e)}), 500
@@ -328,8 +327,8 @@ def service_detail(service_name):
service_data['latest_backup'] = latest_backup['path'] service_data['latest_backup'] = latest_backup['path']
return render_template('service.html', service=service_data) return render_template('service.html', service=service_data)
except Exception as e: except (OSError, IOError, json.JSONDecodeError) as e:
logger.error(f"Error in service detail for {service_name}: {e}") logger.error("Error in service detail for %s: %s", service_name, e)
return f"Error: {e}", 500 return f"Error: {e}", 500
@@ -369,8 +368,8 @@ def logs_view():
}) })
return render_template('logs.html', logs=formatted_logs, filter_service=service_filter) return render_template('logs.html', logs=formatted_logs, filter_service=service_filter)
except Exception as e: except (OSError, IOError) as e:
logger.error(f"Error in logs view: {e}") logger.error("Error in logs view: %s", e)
return f"Error: {e}", 500 return f"Error: {e}", 500
@@ -408,7 +407,7 @@ def view_log(filename):
# Read last N lines for large files # Read last N lines for large files
max_lines = int(request.args.get('lines', 1000)) 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() lines = f.readlines()
if len(lines) > max_lines: if len(lines) > max_lines:
lines = lines[-max_lines:] lines = lines[-max_lines:]
@@ -427,8 +426,8 @@ def view_log(filename):
"%Y-%m-%d %H:%M:%S"), "%Y-%m-%d %H:%M:%S"),
total_lines=len(lines), total_lines=len(lines),
lines_shown=min(len(lines), max_lines)) lines_shown=min(len(lines), max_lines))
except Exception as e: except (OSError, IOError, UnicodeDecodeError, ValueError) as e:
logger.error(f"Error viewing log {filename}: {e}") logger.error("Error viewing log %s: %s", filename, e)
return f"Error: {e}", 500 return f"Error: {e}", 500
@@ -449,7 +448,8 @@ def api_refresh_metrics():
env=env, env=env,
capture_output=True, capture_output=True,
text=True, text=True,
timeout=300 # 5 minute timeout timeout=300, # 5 minute timeout
check=False
) )
if result.returncode == 0: if result.returncode == 0:
@@ -460,7 +460,7 @@ def api_refresh_metrics():
'output': result.stdout 'output': result.stdout
}) })
else: else:
logger.error(f"Metrics refresh failed: {result.stderr}") logger.error("Metrics refresh failed: %s", result.stderr)
return jsonify({ return jsonify({
'status': 'error', 'status': 'error',
'message': 'Metrics refresh failed', 'message': 'Metrics refresh failed',
@@ -477,8 +477,8 @@ def api_refresh_metrics():
'status': 'error', 'status': 'error',
'message': 'Metrics refresh timed out' 'message': 'Metrics refresh timed out'
}), 408 }), 408
except Exception as e: except (OSError, subprocess.SubprocessError) as e:
logger.error(f"Error refreshing metrics: {e}") logger.error("Error refreshing metrics: %s", e)
return jsonify({ return jsonify({
'status': 'error', 'status': 'error',
'message': str(e) 'message': str(e)
@@ -498,14 +498,14 @@ def health_check():
@app.errorhandler(404) @app.errorhandler(404)
def not_found(error): def not_found(_error):
return render_template('error.html', return render_template('error.html',
error_code=404, error_code=404,
error_message="Page not found"), 404 error_message="Page not found"), 404
@app.errorhandler(500) @app.errorhandler(500)
def internal_error(error): def internal_error(_error):
return render_template('error.html', return render_template('error.html',
error_code=500, error_code=500,
error_message="Internal server error"), 500 error_message="Internal server error"), 500

24
backup-web-app.service Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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"

View File

@@ -2,7 +2,6 @@
import os import os
import json import json
import sys
# Set environment # Set environment
os.environ['BACKUP_ROOT'] = '/home/acedanger/shell' os.environ['BACKUP_ROOT'] = '/home/acedanger/shell'
@@ -13,9 +12,9 @@ def load_json_file(filepath):
"""Safely load JSON file with error handling""" """Safely load JSON file with error handling"""
try: try:
if os.path.exists(filepath): if os.path.exists(filepath):
with open(filepath, 'r') as f: with open(filepath, 'r', encoding='utf-8') as f:
return json.load(f) return json.load(f)
except Exception as e: except (OSError, json.JSONDecodeError, UnicodeDecodeError) as e:
print(f"Error loading JSON file {filepath}: {e}") print(f"Error loading JSON file {filepath}: {e}")
return None return None
@@ -25,35 +24,35 @@ def get_service_metrics(service_name):
# Simple status file approach # Simple status file approach
status_file = os.path.join(METRICS_DIR, f'{service_name}_status.json') 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 { return {
'status': status, 'status': service_status,
'last_run': status.get('end_time') if status else None, 'last_run': service_status.get('end_time') if service_status else None,
'current_status': status.get('status', 'unknown') if status else 'never_run', 'current_status': service_status.get('status', 'unknown') if service_status else 'never_run',
'files_processed': status.get('files_processed', 0) if status else 0, 'files_processed': service_status.get('files_processed', 0) if service_status else 0,
'total_size': status.get('total_size_bytes', 0) if status else 0, 'total_size': service_status.get('total_size_bytes', 0) if service_status else 0,
'duration': status.get('duration_seconds', 0) if status else 0 'duration': service_status.get('duration_seconds', 0) if service_status else 0
} }
def get_consolidated_metrics(): def get_consolidated_metrics():
"""Get consolidated metrics across all services""" """Get consolidated metrics across all services"""
# With simplified approach, we consolidate by reading all status files # With simplified approach, we consolidate by reading all status files
services = {} all_services = {}
if os.path.exists(METRICS_DIR): if os.path.exists(METRICS_DIR):
for filename in os.listdir(METRICS_DIR): for filename in os.listdir(METRICS_DIR):
if filename.endswith('_status.json'): if filename.endswith('_status.json'):
service_name = filename.replace('_status.json', '') service_name = filename.replace('_status.json', '')
status_file = os.path.join(METRICS_DIR, filename) status_file = os.path.join(METRICS_DIR, filename)
status = load_json_file(status_file) service_status = load_json_file(status_file)
if status: if service_status:
services[service_name] = status all_services[service_name] = service_status
return { return {
'services': services, 'services': all_services,
'total_services': len(services), 'total_services': len(all_services),
'last_updated': '2025-06-18T05:15:00-04:00' 'last_updated': '2025-06-18T05:15:00-04:00'
} }
@@ -70,7 +69,7 @@ if __name__ == "__main__":
files = metrics['files_processed'] files = metrics['files_processed']
duration = metrics['duration'] duration = metrics['duration']
print(f' {service}: {status} ({files} files, {duration}s)') print(f' {service}: {status} ({files} files, {duration}s)')
except Exception as e: except (OSError, IOError, KeyError) as e:
print(f' {service}: Error - {e}') print(f' {service}: Error - {e}')
# Test consolidated metrics # Test consolidated metrics
@@ -82,7 +81,7 @@ if __name__ == "__main__":
for name, status in services.items(): for name, status in services.items():
message = status.get('message', 'N/A') message = status.get('message', 'N/A')
print(f' {name}: {status["status"]} - {message}') print(f' {name}: {status["status"]} - {message}')
except Exception as e: except (OSError, IOError, KeyError) as e:
print(f' Error: {e}') print(f' Error: {e}')
print('\n✅ Web integration test completed successfully!') print('\n✅ Web integration test completed successfully!')