diff --git a/DEPLOYMENT-GUIDE.md b/DEPLOYMENT-GUIDE.md new file mode 100644 index 0000000..30d032f --- /dev/null +++ b/DEPLOYMENT-GUIDE.md @@ -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! diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0e3be35 --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/backup-web-app.py b/backup-web-app.py index 9081bc6..79195c7 100644 --- a/backup-web-app.py +++ b/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 diff --git a/backup-web-app.service b/backup-web-app.service new file mode 100644 index 0000000..835d81b --- /dev/null +++ b/backup-web-app.service @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5dff38f --- /dev/null +++ b/docker-compose.yml @@ -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" diff --git a/gunicorn.conf.py b/gunicorn.conf.py new file mode 100644 index 0000000..93f3328 --- /dev/null +++ b/gunicorn.conf.py @@ -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' +} diff --git a/manage-backup-web-service.sh b/manage-backup-web-service.sh new file mode 100755 index 0000000..548c877 --- /dev/null +++ b/manage-backup-web-service.sh @@ -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 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..cdbf562 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +Flask==2.3.3 +Werkzeug==2.3.7 +gunicorn==21.2.0 diff --git a/run-backup-web-screen.sh b/run-backup-web-screen.sh new file mode 100755 index 0000000..3b05c2a --- /dev/null +++ b/run-backup-web-screen.sh @@ -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 diff --git a/run-production.sh b/run-production.sh new file mode 100755 index 0000000..0a96bdd --- /dev/null +++ b/run-production.sh @@ -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" diff --git a/test-web-integration.py b/test-web-integration.py index b896459..befe84a 100644 --- a/test-web-integration.py +++ b/test-web-integration.py @@ -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!')