mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
110 lines
3.7 KiB
Python
110 lines
3.7 KiB
Python
"""SSH key agent and optional sudo password for remote."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import getpass
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
import deploy.ui as ui
|
|
|
|
|
|
@dataclass
|
|
class SshCredentials:
|
|
server: str
|
|
sudo_password: str
|
|
|
|
|
|
class SshAuth:
|
|
def __init__(self, server: str) -> None:
|
|
self._server = server
|
|
|
|
def authenticate(self) -> SshCredentials:
|
|
ui.step("Authentication")
|
|
self._ensure_agent()
|
|
key_file = Path.home() / ".ssh" / "id_rsa"
|
|
self._ensure_key_file(key_file)
|
|
self._ensure_key_in_agent(key_file)
|
|
self._verify_key_auth(key_file)
|
|
sudo_password = self._prompt_sudo()
|
|
return SshCredentials(server=self._server, sudo_password=sudo_password)
|
|
|
|
def _ensure_agent(self) -> None:
|
|
if os.environ.get("SSH_AUTH_SOCK"):
|
|
return
|
|
subprocess.run(["ssh-agent", "-s"], capture_output=True, check=False)
|
|
|
|
def _ensure_key_file(self, key_file: Path) -> None:
|
|
if not key_file.is_file():
|
|
ui.error(f"SSH key not found at {key_file}")
|
|
sys.stderr.write(
|
|
" Please generate an SSH key pair first:\n"
|
|
" ssh-keygen -t rsa -b 4096 -C 'your_email@example.com'\n"
|
|
)
|
|
raise SystemExit(1)
|
|
|
|
def _ensure_key_in_agent(self, key_file: Path) -> None:
|
|
loaded = False
|
|
r = subprocess.run(["ssh-add", "-l"], capture_output=True, text=True)
|
|
if r.returncode == 0:
|
|
fp_r = subprocess.run(
|
|
["ssh-keygen", "-lf", str(key_file)],
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
if fp_r.returncode == 0:
|
|
parts = fp_r.stdout.strip().split()
|
|
fingerprint = parts[1] if len(parts) > 1 else ""
|
|
if fingerprint and fingerprint in r.stdout:
|
|
loaded = True
|
|
if not loaded:
|
|
ui.substep("Adding SSH key to agent...")
|
|
if subprocess.run(["ssh-add", str(key_file)], capture_output=True).returncode != 0:
|
|
ui.error("Failed to add SSH key to agent. Check your key passphrase.")
|
|
raise SystemExit(1)
|
|
|
|
def _verify_key_auth(self, key_file: Path) -> None:
|
|
pub = key_file.with_suffix(key_file.suffix + ".pub")
|
|
ok = subprocess.run(
|
|
[
|
|
"ssh",
|
|
"-o",
|
|
"BatchMode=yes",
|
|
"-o",
|
|
"ConnectTimeout=10",
|
|
"-o",
|
|
"StrictHostKeyChecking=no",
|
|
self._server,
|
|
"echo 'SSH key works'",
|
|
],
|
|
capture_output=True,
|
|
).returncode
|
|
if ok == 0:
|
|
return
|
|
ui.error(f"SSH key authentication failed for {self._server}")
|
|
sys.stderr.write(
|
|
f' Copy your public key to the server, then re-run deploy:\n ssh-copy-id -i "{pub}" "{self._server}"\n\n'
|
|
" Or manually append this key to ~/.ssh/authorized_keys on the server:\n"
|
|
)
|
|
if pub.is_file():
|
|
sys.stderr.write(f" {pub.read_text(encoding='utf-8', errors='replace').strip()}\n")
|
|
raise SystemExit(1)
|
|
|
|
def _prompt_sudo(self) -> str:
|
|
while True:
|
|
pw = getpass.getpass(" • Sudo password: ")
|
|
if not pw:
|
|
ui.warning("No password provided - assuming passwordless sudo")
|
|
return ""
|
|
chk = subprocess.run(
|
|
["ssh", self._server, "sudo", "-S", "-v"],
|
|
input=(pw + "\n").encode(),
|
|
capture_output=True,
|
|
)
|
|
if chk.returncode == 0:
|
|
return pw
|
|
ui.error("Invalid password, please try again")
|