Fix SSH permissions: store known_hosts in persistent /data and disable pseudo-terminal warning
This commit is contained in:
1 parent
6d03f5de43
commit
f74a3e09f5
2 files changed
+48
-68
No files matched your search
+44
-65
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in new issue
Block a user