The standby's "SSH to main server for containers/stats/system info"
fallback (RUNNING_ON_MAIN_SERVER=False path) was wired in code across
backups.py/users.py/commands.py/app.py but never actually worked:
outbound SSH from the VM is blocked by its own firewall (deliberate
hardening — ufw DENY OUT on 22/tcp and 2222/tcp, left untouched), and
separately the VM's contabo-key was never added to the main server's
authorized_keys in the first place.
Fixed via a persistent reverse tunnel instead of opening the VM's
firewall: main-to-vm-tunnel.service (systemd, auto-restart) runs on the
main server and keeps `ssh -R 2224:localhost:22` open to the VM, so the
VM can reach the main server's SSH via localhost:2224 without ever
needing outbound access itself. Added the standby's public key to the
main server's authorized_keys (chattr +i-locked — unlocked, appended,
re-locked immediately).
config.py (gitignored, not in this commit — edited live on both servers
directly) gets new MAIN_SERVER_SSH_HOST/PORT ("localhost"/"2224"), kept
deliberately separate from the existing MAIN_SERVER_IP (which stays the
real IP and is still used for on-page display in several templates —
overloading it for the tunnel target would have silently changed what's
shown in the UI). All four call sites switched from
MAIN_SERVER_IP/MAIN_SERVER_PORT to the new pair. Also fixed a pre-existing
bug in commands.py's run_command(): its SSH fallback had no -i key at all,
so it could never have authenticated even with the tunnel in place.
Verified end-to-end on the live standby: run_command(), backups.py's and
users.py's _ssh_main() all confirmed to actually execute on the real main
server (hostname/whoami/backup count all matched), not erroring or
silently no-op'ing.
41 lines
1.4 KiB
Python
41 lines
1.4 KiB
Python
import subprocess
|
|
from config import RUNNING_ON_MAIN_SERVER, MAIN_SERVER_KEY, MAIN_SERVER_SSH_HOST, MAIN_SERVER_SSH_PORT
|
|
|
|
def run_command(command):
|
|
"""Run command on main server (directly or via SSH)"""
|
|
if RUNNING_ON_MAIN_SERVER:
|
|
try:
|
|
result = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=30)
|
|
return result.stdout, result.stderr
|
|
except subprocess.TimeoutExpired:
|
|
return "", "Command timeout"
|
|
except Exception as e:
|
|
return "", str(e)
|
|
else:
|
|
ssh_cmd = [
|
|
"ssh",
|
|
"-i", MAIN_SERVER_KEY,
|
|
"-p", MAIN_SERVER_SSH_PORT,
|
|
"-o", "StrictHostKeyChecking=no",
|
|
"-o", "ConnectTimeout=10",
|
|
"-o", "BatchMode=yes",
|
|
f"root@{MAIN_SERVER_SSH_HOST}",
|
|
command
|
|
]
|
|
try:
|
|
result = subprocess.run(ssh_cmd, capture_output=True, text=True, timeout=30)
|
|
return result.stdout, result.stderr
|
|
except subprocess.TimeoutExpired:
|
|
return "", "Connection timeout"
|
|
except Exception as e:
|
|
return "", str(e)
|
|
|
|
def run_ssh_to_vm(command):
|
|
"""Run command on VM via reverse tunnel"""
|
|
from config import VM_PASSWORD
|
|
import os
|
|
|
|
cmd = f"sshpass -p '{VM_PASSWORD}' ssh -p 2223 -o StrictHostKeyChecking=no -o ConnectTimeout=5 root@localhost '{command}'"
|
|
result = os.popen(cmd).read()
|
|
return result
|