Fix SSH permissions: store known_hosts in persistent /data and disable pseudo-terminal warning

This commit is contained in:
jschantli committed 2026-09-04 20:30:02 -07:00
1 parent 6d03f5de43
commit f74a3e09f5
2 files changed
+48 -68

No files matched your search

+44 -65
View File
@@ -4,7 +4,7 @@ import shutil
import time import time
from pathlib import Path from pathlib import Path
from typing import Dict, Any, List, Optional, Tuple from typing import Dict, Any, List, Optional, Tuple
from backend.config import load_settings, DEFAULT_KEY_FILE, DEFAULT_PUB_KEY_FILE from backend.config import load_settings, DEFAULT_KEY_FILE, DEFAULT_PUB_KEY_FILE, DATA_DIR
def get_public_key() -> str: def get_public_key() -> str:
if DEFAULT_PUB_KEY_FILE.exists(): if DEFAULT_PUB_KEY_FILE.exists():
@@ -40,9 +40,12 @@ def build_ssh_prefix(settings: Optional[Dict[str, Any]] = None) -> List[str]:
raw_key_path = settings.get("vps_ssh_key_path", str(DEFAULT_KEY_FILE)) raw_key_path = settings.get("vps_ssh_key_path", str(DEFAULT_KEY_FILE))
key_path = resolve_key_path(raw_key_path) key_path = resolve_key_path(raw_key_path)
password = settings.get("vps_ssh_password", "") password = settings.get("vps_ssh_password", "")
known_hosts = str(DATA_DIR / "known_hosts")
ssh_opts = [ ssh_opts = [
"-T",
"-o", "StrictHostKeyChecking=accept-new", "-o", "StrictHostKeyChecking=accept-new",
"-o", f"UserKnownHostsFile={known_hosts}",
"-o", "ConnectTimeout=8", "-o", "ConnectTimeout=8",
"-o", "ServerAliveInterval=15", "-o", "ServerAliveInterval=15",
"-p", port "-p", port
@@ -69,10 +72,12 @@ def build_scp_prefix(settings: Optional[Dict[str, Any]] = None) -> List[str]:
raw_key_path = settings.get("vps_ssh_key_path", str(DEFAULT_KEY_FILE)) raw_key_path = settings.get("vps_ssh_key_path", str(DEFAULT_KEY_FILE))
key_path = resolve_key_path(raw_key_path) key_path = resolve_key_path(raw_key_path)
password = settings.get("vps_ssh_password", "") password = settings.get("vps_ssh_password", "")
known_hosts = str(DATA_DIR / "known_hosts")
scp_opts = [ scp_opts = [
"-P", port, "-P", port,
"-o", "StrictHostKeyChecking=accept-new", "-o", "StrictHostKeyChecking=accept-new",
"-o", f"UserKnownHostsFile={known_hosts}",
"-o", "ConnectTimeout=10" "-o", "ConnectTimeout=10"
] ]
@@ -99,79 +104,53 @@ async def run_remote_quick(cmd: str, settings: Optional[Dict[str, Any]] = None)
stdout, stderr = await proc.communicate() stdout, stderr = await proc.communicate()
return proc.returncode, stdout.decode('utf-8', errors='replace'), stderr.decode('utf-8', errors='replace') return proc.returncode, stdout.decode('utf-8', errors='replace'), stderr.decode('utf-8', errors='replace')
except Exception as e: except Exception as e:
return -1, "", str(e) return 1, "", str(e)
async def test_vps_connection(custom_settings: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: async def test_vps_connection(settings: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
s = custom_settings or load_settings() if not settings:
host = s.get("vps_host", "").strip() settings = load_settings()
host = settings.get("vps_host", "").strip()
if not host: if not host:
return { return {"success": False, "error": "No VPS host configured. Please provide IP or hostname."}
"success": False,
"connected": False,
"error": "No VPS Host / IP configured. Please specify the VPS address in Settings."
}
# Run comprehensive diagnostic script on remote VPS # 1. Test basic SSH connectivity & gather OS summary
diag_script = """ cmd_ping = 'echo "PANGOLIN_PONG" && uname -srm && cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d= -f2 | tr -d \'"\''
echo "===PING_OK===" code, stdout, stderr = await run_remote_quick(cmd_ping, settings)
uname -n
cat /etc/os-release | grep PRETTY_NAME | cut -d= -f2 | tr -d '"'
uname -r
uptime -p 2>/dev/null || uptime
which docker >/dev/null 2>&1 && echo "DOCKER_FOUND" || echo "DOCKER_NOT_FOUND"
docker ps >/dev/null 2>&1 && echo "DOCKER_RUNNING" || echo "DOCKER_STOPPED"
"""
ret, stdout, stderr = await run_remote_quick(diag_script, s)
if ret != 0 or "===PING_OK===" not in stdout: if code != 0:
err = stderr.strip() or "Connection failed. Please check Host, Port, and SSH credentials." err_msg = stderr.strip() or "Connection timed out or failed."
if "Permission denied" in err: if "Permission denied" in err_msg:
err = "Permission denied (SSH authentication failed). Please ensure your SSH public key or password is correct on the VPS." err_msg += " (SSH Authentication failed. Check key in ~/.ssh/authorized_keys or password)"
elif "Connection timed out" in err or "No route to host" in err: return {"success": False, "error": err_msg}
err = f"Could not reach {host}:{s.get('vps_port', 22)}. Verify firewall, IP address, and SSH port."
return {
"success": False,
"connected": False,
"error": err
}
lines = [line.strip() for line in stdout.splitlines() if line.strip()] lines = stdout.strip().split("\n")
hostname = lines[1] if len(lines) > 1 else host os_name = lines[2] if len(lines) > 2 else (lines[1] if len(lines) > 1 else "Linux")
os_name = lines[2] if len(lines) > 2 else "Linux" kernel = lines[1] if len(lines) > 1 else ""
kernel = lines[3] if len(lines) > 3 else ""
uptime_str = lines[4] if len(lines) > 4 else ""
docker_found = "DOCKER_FOUND" in stdout
docker_running = "DOCKER_RUNNING" in stdout
# Check Pangolin path on VPS # 2. Check Docker availability on VPS
p_dir = s.get("vps_pangolin_dir", "/opt/pangolin") code_docker, stdout_docker, _ = await run_remote_quick('docker --version', settings)
p_script = f""" docker_installed = (code_docker == 0)
[ -d "{p_dir}" ] && echo "DIR_OK" || echo "DIR_MISSING" docker_version = stdout_docker.strip() if docker_installed else "Not detected"
[ -f "{p_dir}/docker-compose.yml" ] || [ -f "{p_dir}/compose.yaml" ] && echo "COMPOSE_OK" || echo "COMPOSE_MISSING"
find "{p_dir}" -name "*.db" -o -name "*.sqlite" 2>/dev/null | head -n 1
"""
_, p_out, _ = await run_remote_quick(p_script, s)
p_lines = [l.strip() for l in p_out.splitlines() if l.strip()]
dir_ok = "DIR_OK" in p_lines # 3. Check Pangolin Directory
compose_ok = "COMPOSE_OK" in p_lines pangolin_dir = settings.get("vps_pangolin_dir", "/opt/pangolin")
db_file = None chk_dir_cmd = f'[ -d "{pangolin_dir}" ] && echo "DIR_EXISTS" || echo "DIR_MISSING"'
for l in p_lines: _, stdout_dir, _ = await run_remote_quick(chk_dir_cmd, settings)
if l.endswith((".db", ".sqlite")): pangolin_dir_found = "DIR_EXISTS" in stdout_dir
db_file = l
break # 4. Check SQLite database status in Pangolin directory
chk_db_cmd = f'find "{pangolin_dir}" -name "*.db" -o -name "*.sqlite" 2>/dev/null'
_, stdout_db, _ = await run_remote_quick(chk_db_cmd, settings)
dbs_found = [p.strip() for p in stdout_db.strip().split("\n") if p.strip()]
return { return {
"success": True, "success": True,
"connected": True, "os_name": os_name,
"hostname": hostname,
"os": os_name,
"kernel": kernel, "kernel": kernel,
"uptime": uptime_str, "docker_installed": docker_installed,
"docker_installed": docker_found, "docker_version": docker_version,
"docker_running": docker_running, "pangolin_dir_found": pangolin_dir_found,
"pangolin_dir_exists": dir_ok, "sqlite_databases": dbs_found,
"compose_file_exists": compose_ok, "message": "SSH Connection successful! Remote host is ready for backups and updates."
"database_file": db_file,
"message": f"Successfully connected to {hostname} ({host}) via SSH!"
} }
+4 -3
View File
@@ -5,7 +5,8 @@ set -e
DATA_PATH="${DATA_DIR:-/data}" DATA_PATH="${DATA_DIR:-/data}"
BACKUP_PATH="${BACKUP_DIR:-/backups}" BACKUP_PATH="${BACKUP_DIR:-/backups}"
mkdir -p "$DATA_PATH" "$BACKUP_PATH" mkdir -p "$DATA_PATH" "$BACKUP_PATH" "$DATA_PATH/.ssh"
export HOME="$DATA_PATH"
# Set secure permissions on SSH private key if it exists # Set secure permissions on SSH private key if it exists
if [ -f "$DATA_PATH/id_ed25519" ]; then if [ -f "$DATA_PATH/id_ed25519" ]; then
@@ -22,9 +23,9 @@ if [ "$PUID" -ne 0 ] && [ "$PGID" -ne 0 ]; then
groupadd -g "$PGID" pangolin 2>/dev/null || true groupadd -g "$PGID" pangolin 2>/dev/null || true
fi fi
# Create or update user # Create or update user with persistent home in DATA_PATH
if ! getent passwd pangolin >/dev/null 2>&1; then if ! getent passwd pangolin >/dev/null 2>&1; then
useradd -u "$PUID" -g "$PGID" -d /app -s /bin/bash pangolin 2>/dev/null || true useradd -u "$PUID" -g "$PGID" -d "$DATA_PATH" -s /bin/bash pangolin 2>/dev/null || true
fi fi
# Ensure ownership of persistent data and backup volumes # Ensure ownership of persistent data and backup volumes