Sync from main server - 2026-04-16 13:42:46
This commit is contained in:
136
platform/app.py
136
platform/app.py
@@ -1,19 +1,28 @@
|
||||
# app.py
|
||||
from flask import Flask, render_template, request, redirect, url_for, session, jsonify
|
||||
import os
|
||||
import subprocess
|
||||
import threading
|
||||
import uuid
|
||||
import time
|
||||
from config import MAIN_SERVER_IP, VM_HOST, VM_PORT, VM_KEY, VM_USER
|
||||
|
||||
from config import (
|
||||
MAIN_SERVER_IP, RUNNING_ON_MAIN_SERVER,
|
||||
VM_HOST, VM_PORT, VM_KEY, VM_USER,
|
||||
MAIN_SERVER_KEY, MAIN_SERVER_PORT, MAIN_SERVER_USER,
|
||||
)
|
||||
from modules.auth import login_required
|
||||
from modules.backups import (
|
||||
get_containers, get_all_root_containers, get_local_backups,
|
||||
get_vm_backups, get_all_stats, get_system_info
|
||||
get_containers, get_all_root_containers,
|
||||
get_local_backups, get_vm_backups,
|
||||
get_all_stats, get_system_info,
|
||||
get_rootless_user_containers_remote,
|
||||
container_action,
|
||||
)
|
||||
from modules.commands import run_command, run_ssh_to_vm
|
||||
from modules.commands import run_command
|
||||
from modules.users import (
|
||||
get_all_users, get_user_containers, get_all_users_containers,
|
||||
create_user, delete_user, get_user_disk_usage
|
||||
create_user, delete_user, get_user_disk_usage,
|
||||
)
|
||||
|
||||
app = Flask(__name__)
|
||||
@@ -46,12 +55,13 @@ def _stream_restore(job_id, cmd):
|
||||
@app.route('/')
|
||||
@login_required
|
||||
def dashboard():
|
||||
containers = get_containers()
|
||||
containers = get_containers()
|
||||
running_count = sum(1 for c in containers if 'Up' in c.get('status', ''))
|
||||
backups = get_local_backups()
|
||||
vm_backups = get_vm_backups()
|
||||
system = get_system_info()
|
||||
users = get_all_users()
|
||||
backups = get_local_backups()
|
||||
vm_backups = get_vm_backups()
|
||||
system = get_system_info()
|
||||
# Users are still LOCAL (users on the platform host)
|
||||
users = get_all_users()
|
||||
return render_template('dashboard.html',
|
||||
containers=containers,
|
||||
running_count=running_count,
|
||||
@@ -59,11 +69,12 @@ def dashboard():
|
||||
vm_backups=vm_backups,
|
||||
main_server=MAIN_SERVER_IP,
|
||||
system=system,
|
||||
users=users)
|
||||
users=users,
|
||||
running_on_main=RUNNING_ON_MAIN_SERVER)
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# API — system info + stats (live poll)
|
||||
# API — system + stats (always from main server)
|
||||
# ─────────────────────────────────────────────
|
||||
@app.route('/api/system')
|
||||
@login_required
|
||||
@@ -74,14 +85,13 @@ def api_system():
|
||||
@app.route('/api/stats')
|
||||
@login_required
|
||||
def api_stats():
|
||||
"""Container resource stats for ALL containers (root + rootless users)."""
|
||||
return jsonify(get_all_stats())
|
||||
|
||||
|
||||
@app.route('/api/containers')
|
||||
@login_required
|
||||
def api_containers():
|
||||
containers = get_all_root_containers()
|
||||
containers = get_all_root_containers()
|
||||
running_count = sum(1 for c in containers if 'Up' in c.get('status', ''))
|
||||
return jsonify({'containers': containers, 'running': running_count})
|
||||
|
||||
@@ -89,14 +99,35 @@ def api_containers():
|
||||
@app.route('/api/containers/all')
|
||||
@login_required
|
||||
def api_containers_all():
|
||||
"""Root containers + all users' rootless containers combined."""
|
||||
"""Root containers + rootless-user containers, all from main server."""
|
||||
root_ctrs = get_all_root_containers()
|
||||
user_ctrs = get_all_users_containers()
|
||||
user_ctrs = get_rootless_user_containers_remote()
|
||||
all_ctrs = root_ctrs + user_ctrs
|
||||
running = sum(1 for c in all_ctrs if 'Up' in c.get('status', ''))
|
||||
return jsonify({'containers': all_ctrs, 'running': running})
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# API — container actions
|
||||
# ─────────────────────────────────────────────
|
||||
@app.route('/api/container/action', methods=['POST'])
|
||||
@login_required
|
||||
def api_container_action():
|
||||
"""
|
||||
POST JSON: { "name": "container-name", "action": "start|stop|restart" }
|
||||
Runs the action on the main server (via SSH if on VM).
|
||||
"""
|
||||
data = request.get_json() or {}
|
||||
name = data.get('name', '').strip()
|
||||
action = data.get('action', '').strip()
|
||||
|
||||
if not name or not action:
|
||||
return jsonify({'success': False, 'message': 'name and action required'}), 400
|
||||
|
||||
success, output = container_action(name, action)
|
||||
return jsonify({'success': success, 'output': output})
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# API — backups
|
||||
# ─────────────────────────────────────────────
|
||||
@@ -107,7 +138,7 @@ def api_backups():
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# API — users management
|
||||
# API — users (LOCAL — users on this host)
|
||||
# ─────────────────────────────────────────────
|
||||
@app.route('/api/users')
|
||||
@login_required
|
||||
@@ -130,7 +161,7 @@ def api_user_disk(username):
|
||||
@app.route('/api/users/create', methods=['POST'])
|
||||
@login_required
|
||||
def api_create_user():
|
||||
data = request.get_json() or {}
|
||||
data = request.get_json() or {}
|
||||
username = data.get('username', '').strip()
|
||||
password = data.get('password', '').strip()
|
||||
setup_docker = data.get('setup_docker', True)
|
||||
@@ -151,7 +182,7 @@ def api_create_user():
|
||||
@app.route('/api/users/delete', methods=['POST'])
|
||||
@login_required
|
||||
def api_delete_user():
|
||||
data = request.get_json() or {}
|
||||
data = request.get_json() or {}
|
||||
username = data.get('username', '').strip()
|
||||
remove_home = data.get('remove_home', False)
|
||||
|
||||
@@ -185,22 +216,46 @@ def restore_start():
|
||||
if not backup_file:
|
||||
return jsonify({'error': 'No backup file specified'}), 400
|
||||
|
||||
# Resolve archive path on this server
|
||||
# ── Resolve backup archive path ──────────────────────────────────────────
|
||||
if backup_source == 'local':
|
||||
backup_path = f"/root/backups/{backup_file}"
|
||||
if not os.path.exists(backup_path):
|
||||
return jsonify({'error': f'Backup not found: {backup_path}'}), 400
|
||||
# Backup is on main server at /root/backups/
|
||||
if RUNNING_ON_MAIN_SERVER:
|
||||
backup_path = f"/root/backups/{backup_file}"
|
||||
if not os.path.exists(backup_path):
|
||||
return jsonify({'error': f'Not found: {backup_path}'}), 400
|
||||
else:
|
||||
# We're on VM → need to pull backup from main server to /tmp/ first
|
||||
backup_path = f"/tmp/{backup_file}"
|
||||
if not os.path.exists(backup_path):
|
||||
pull_cmd = (
|
||||
f"scp -i {MAIN_SERVER_KEY} -P {MAIN_SERVER_PORT} "
|
||||
f"-o StrictHostKeyChecking=no -o ConnectTimeout=15 "
|
||||
f"{MAIN_SERVER_USER}@{MAIN_SERVER_IP}:/root/backups/{backup_file} "
|
||||
f"{backup_path}"
|
||||
)
|
||||
res = subprocess.run(pull_cmd, shell=True, capture_output=True, text=True)
|
||||
if res.returncode != 0:
|
||||
return jsonify({'error': f'Failed to pull from main server: {res.stderr}'}), 500
|
||||
else:
|
||||
backup_path = f"/tmp/{backup_file}"
|
||||
if not os.path.exists(backup_path):
|
||||
pull_cmd = (
|
||||
f"scp -i {VM_KEY} -P {VM_PORT} "
|
||||
f"-o StrictHostKeyChecking=no -o ConnectTimeout=15 "
|
||||
f"{VM_USER}@{VM_HOST}:/backups/main-server/{backup_file} {backup_path}"
|
||||
)
|
||||
result = subprocess.run(pull_cmd, shell=True, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
return jsonify({'error': f'Failed to pull from VM: {result.stderr}'}), 500
|
||||
# VM backup
|
||||
if RUNNING_ON_MAIN_SERVER:
|
||||
# Pull from VM via tunnel
|
||||
backup_path = f"/tmp/{backup_file}"
|
||||
if not os.path.exists(backup_path):
|
||||
pull_cmd = (
|
||||
f"scp -i {VM_KEY} -P {VM_PORT} "
|
||||
f"-o StrictHostKeyChecking=no -o ConnectTimeout=15 "
|
||||
f"{VM_USER}@{VM_HOST}:/backups/main-server/{backup_file} "
|
||||
f"{backup_path}"
|
||||
)
|
||||
res = subprocess.run(pull_cmd, shell=True, capture_output=True, text=True)
|
||||
if res.returncode != 0:
|
||||
return jsonify({'error': f'Failed to pull from VM: {res.stderr}'}), 500
|
||||
else:
|
||||
# We're on VM → backup is local
|
||||
backup_path = f"/backups/main-server/{backup_file}"
|
||||
if not os.path.exists(backup_path):
|
||||
return jsonify({'error': f'Not found: {backup_path}'}), 400
|
||||
|
||||
restore_script_local = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), 'restore-myapps.sh'
|
||||
@@ -208,20 +263,28 @@ def restore_start():
|
||||
if not os.path.exists(restore_script_local):
|
||||
return jsonify({'error': f'restore-myapps.sh not found at {restore_script_local}'}), 500
|
||||
|
||||
# ── Determine the actual target ──────────────────────────────────────────
|
||||
# "local" always means THIS host — wherever the platform is currently deployed.
|
||||
# Run the restore script directly, no SSH indirection needed.
|
||||
if target == 'local':
|
||||
hostname = os.uname().nodename
|
||||
session_dir = f"/tmp/restore-session-{uuid.uuid4().hex[:8]}"
|
||||
cmd = (
|
||||
f"set -e && mkdir -p {session_dir} && "
|
||||
f"echo '📂 Extracting backup locally...' && "
|
||||
f"set -e && "
|
||||
f"echo '🖥️ Restoring on this server ({hostname})...' && "
|
||||
f"mkdir -p {session_dir} && "
|
||||
f"echo '📂 Extracting backup...' && "
|
||||
f"tar -xzf {backup_path} -C {session_dir} --strip-components=1 && "
|
||||
f"cp {restore_script_local} {session_dir}/restore-myapps.sh && "
|
||||
f"chmod +x {session_dir}/restore-myapps.sh && "
|
||||
f"cd {session_dir} && bash restore-myapps.sh ; "
|
||||
f"EXIT=$? ; rm -rf {session_dir} ; exit $EXIT"
|
||||
)
|
||||
|
||||
else:
|
||||
# Explicit remote machine (custom IP)
|
||||
if not remote_ip:
|
||||
return jsonify({'error': 'remote_ip required for remote restore'}), 400
|
||||
return jsonify({'error': 'remote_ip required'}), 400
|
||||
|
||||
base_opts = "-o StrictHostKeyChecking=no -o ConnectTimeout=15"
|
||||
if auth_method == 'key':
|
||||
@@ -247,7 +310,6 @@ def restore_start():
|
||||
f"echo '🚀 Running restore on {remote_ip}:{remote_port}...' && "
|
||||
f"{ssh_prefix} {remote_user}@{remote_ip} "
|
||||
f"'set -e && cd {remote_dest} && "
|
||||
f"echo \"📂 Extracting backup...\" && "
|
||||
f"tar -xzf {backup_file} --strip-components=1 && "
|
||||
f"chmod +x restore-myapps.sh && bash restore-myapps.sh' ; "
|
||||
f"EXIT=$? ; "
|
||||
|
||||
Reference in New Issue
Block a user