Sync from main server - 2026-04-15 13:03:38

This commit is contained in:
root
2026-04-15 13:03:38 +02:00
parent 3f3d3a9fc3
commit 68870eb3db
8 changed files with 2455 additions and 1212 deletions

View File

@@ -1,31 +1,27 @@
import os
import glob
import subprocess
from config import RUNNING_ON_MAIN_SERVER, VM_HOST, VM_PORT, VM_KEY, VM_USER, MAIN_SERVER_IP
import json
from config import RUNNING_ON_MAIN_SERVER, VM_HOST, VM_PORT, VM_KEY, VM_USER
def run_command(cmd):
def _run(cmd, timeout=20):
try:
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30)
return result.stdout, result.stderr
r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
return r.stdout.strip(), r.stderr.strip()
except Exception as e:
return '', str(e)
def get_local_backups():
"""
On main server → /root/backups/ (backups of the main server, stored locally)
On VM → /backups/main-server/ (backups of the main server, stored on VM)
"""
if RUNNING_ON_MAIN_SERVER:
path = "/root/backups/myapps-backup-*.tar.gz"
else:
path = "/backups/main-server/myapps-backup-*.tar.gz"
# ────────────────────────────────────────────────────────────────
# BACKUPS
# ────────────────────────────────────────────────────────────────
stdout, _ = run_command(f"ls -t {path} 2>/dev/null | head -20")
def get_local_backups():
stdout, _ = _run("ls -t /root/backups/myapps-backup-*.tar.gz 2>/dev/null | head -20")
files = []
if stdout:
for line in stdout.strip().split('\n'):
for line in stdout.split('\n'):
line = line.strip()
if line:
files.append(os.path.basename(line))
@@ -33,14 +29,8 @@ def get_local_backups():
def get_vm_backups():
"""
On main server → SSH into VM (via tunnel port 2223) to list /backups/main-server/
On VM → SSH into main server (port 22) to list /root/backups/
"""
backups = []
vm_backups = []
if RUNNING_ON_MAIN_SERVER:
# Original logic — unchanged
try:
cmd = (
f"ssh -i {VM_KEY} -p {VM_PORT} "
@@ -48,58 +38,143 @@ def get_vm_backups():
f"{VM_USER}@{VM_HOST} "
f"'ls -t /backups/main-server/myapps-backup-*.tar.gz 2>/dev/null | head -20'"
)
stdout, _ = run_command(cmd)
stdout, _ = _run(cmd, timeout=20)
if stdout:
for line in stdout.strip().split('\n'):
for line in stdout.split('\n'):
line = line.strip()
if line and '.tar.gz' in line:
backups.append(os.path.basename(line))
vm_backups.append(os.path.basename(line))
except Exception as e:
print(f"[backups] Error fetching VM backups: {e}")
print(f"[backups] VM backup fetch error: {e}")
else:
# Running on VM → SSH into main server to list its local backups
try:
cmd = (
f"ssh -i {VM_KEY} -p 22 "
f"-o StrictHostKeyChecking=no -o ConnectTimeout=10 "
f"{VM_USER}@{MAIN_SERVER_IP} "
f"'ls -t /root/backups/myapps-backup-*.tar.gz 2>/dev/null | head -20'"
)
stdout, _ = run_command(cmd)
if stdout:
for line in stdout.strip().split('\n'):
line = line.strip()
if line and '.tar.gz' in line:
backups.append(os.path.basename(line))
except Exception as e:
print(f"[backups] Error fetching main server backups from VM: {e}")
backup_dir = '/backups/main-server'
if os.path.exists(backup_dir):
files = glob.glob(f'{backup_dir}/myapps-backup-*.tar.gz')
files.sort(key=os.path.getmtime, reverse=True)
vm_backups = [os.path.basename(f) for f in files[:20]]
return vm_backups
return backups
# ────────────────────────────────────────────────────────────────
# ROOT CONTAINERS
# ────────────────────────────────────────────────────────────────
def get_containers():
"""
On main server → local docker ps (original, unchanged)
On VM → SSH into main server to get its containers
"""
if RUNNING_ON_MAIN_SERVER:
stdout, _ = run_command(
"docker ps -a --format '{{.Names}}|{{.Status}}' 2>/dev/null"
"""Root app containers only (filtered)."""
stdout, _ = _run(
"docker ps -a --format '{{.Names}}|{{.Status}}|{{.Image}}|{{.Ports}}' 2>/dev/null | "
"grep -E 'frappe|nextcloud|mautic|n8n|odoo'"
)
containers = []
if stdout:
for line in stdout.split('\n'):
if '|' not in line:
continue
parts = line.split('|')
containers.append({
'name': parts[0].strip(),
'status': parts[1].strip(),
'image': parts[2].strip(),
'ports': parts[3].strip() if len(parts) > 3 else '',
'owner': 'root',
})
return containers
def get_all_root_containers():
"""ALL root docker containers (unfiltered)."""
stdout, _ = _run(
"docker ps -a --format '{{.Names}}|{{.Status}}|{{.Image}}|{{.Ports}}' 2>/dev/null"
)
containers = []
if stdout:
for line in stdout.split('\n'):
if '|' not in line:
continue
parts = line.split('|')
containers.append({
'name': parts[0].strip(),
'status': parts[1].strip(),
'image': parts[2].strip(),
'ports': parts[3].strip() if len(parts) > 3 else '',
'owner': 'root',
})
return containers
# ────────────────────────────────────────────────────────────────
# CONTAINER STATS
# ────────────────────────────────────────────────────────────────
def get_container_stats(docker_socket=None):
"""One-shot stats snapshot. Returns dict keyed by container name."""
if docker_socket:
cmd = (
f"DOCKER_HOST=unix://{docker_socket} "
f"docker stats --no-stream --format "
f"'{{{{.Name}}}}|{{{{.CPUPerc}}}}|{{{{.MemUsage}}}}|{{{{.MemPerc}}}}|{{{{.NetIO}}}}|{{{{.BlockIO}}}}' 2>/dev/null"
)
else:
cmd = (
f"ssh -i {VM_KEY} -p 22 "
f"-o StrictHostKeyChecking=no -o ConnectTimeout=10 "
f"{VM_USER}@{MAIN_SERVER_IP} "
f"\"docker ps -a --format '{{{{.Names}}}}|{{{{.Status}}}}' 2>/dev/null\""
"docker stats --no-stream --format "
"'{{.Name}}|{{.CPUPerc}}|{{.MemUsage}}|{{.MemPerc}}|{{.NetIO}}|{{.BlockIO}}' 2>/dev/null"
)
stdout, _ = run_command(cmd)
containers = []
stdout, _ = _run(cmd, timeout=30)
stats = {}
if stdout:
for line in stdout.strip().split('\n'):
if '|' in line:
name, status = line.split('|', 1)
containers.append({'name': name.strip(), 'status': status.strip()})
return containers
for line in stdout.split('\n'):
if '|' not in line:
continue
parts = line.split('|')
if len(parts) < 6:
continue
name = parts[0].strip()
stats[name] = {
'cpu': parts[1].strip(),
'mem': parts[2].strip(),
'mem_pct': parts[3].strip(),
'net': parts[4].strip(),
'block': parts[5].strip(),
}
return stats
def get_all_stats():
"""Stats for root + all rootless-user containers combined."""
all_stats = get_container_stats()
try:
import pwd
for pw in pwd.getpwall():
if pw.pw_uid < 1000 or pw.pw_name == 'nobody':
continue
sock = f"/run/user/{pw.pw_uid}/docker.sock"
if os.path.exists(sock):
user_stats = get_container_stats(docker_socket=sock)
all_stats.update(user_stats)
except Exception as e:
print(f"[stats] Error: {e}")
return all_stats
def get_system_info():
"""Host-level system stats."""
cpu_out, _ = _run("top -bn1 | grep 'Cpu(s)' | awk '{print $2+$4}'")
mem_out, _ = _run("free -m | awk 'NR==2{printf \"%s/%sMB\", $3, $2}'")
mem_pct, _ = _run("free | awk 'NR==2{printf \"%.0f\", $3/$2*100}'")
disk_out, _ = _run("df -h / | awk 'NR==2{printf \"%s/%s\", $3, $2}'")
disk_pct, _ = _run("df / | awk 'NR==2{print $5}' | tr -d '%'")
load_out, _ = _run("cat /proc/loadavg | awk '{print $1, $2, $3}'")
uptime_out, _ = _run("uptime -p")
docker_v, _ = _run("docker --version | cut -d' ' -f3 | tr -d ','")
return {
'cpu_pct': cpu_out or '0',
'memory': mem_out or 'N/A',
'mem_pct': mem_pct or '0',
'disk': disk_out or 'N/A',
'disk_pct': disk_pct or '0',
'load': load_out or 'N/A',
'uptime': uptime_out or 'N/A',
'docker_v': docker_v or 'N/A',
}