Validate SSH when saving machines
This commit is contained in:
+3
-1
@@ -126,7 +126,9 @@ docker compose up --build
|
||||
2. After the API is running, open the app, go to **Settings**, and add machine entries:
|
||||
- **Local**: monitors the API host itself without SSH.
|
||||
- **SSH**: monitors another machine using a host, username, and a private key pasted directly into the machine settings, with an optional passphrase.
|
||||
- Use **Validate SSH + trust host** in the machine editor before saving to test the banner/auth flow and record the first trusted host key into the backend-managed `known_hosts` file.
|
||||
- The machine editor groups Connection, Monitoring / Files, Jellyfin, and Jellyseerr settings under separate headings so each service area is easier to scan.
|
||||
- Saving an SSH machine now validates the banner/auth flow and records the first trusted host key into the backend-managed `known_hosts` file, surfacing any errors if the host cannot be reached or authenticated.
|
||||
- Use **Validate SSH + trust host** in the machine editor before saving if you want to test the banner/auth flow explicitly.
|
||||
- The first successful SSH connection uses trust-on-first-use: the backend records that machine's host key into its managed `known_hosts` file automatically, then continues verifying it strictly on later connects.
|
||||
|
||||
3. Open **Monitoring** to see one section per configured machine. Each section uses its own collector state, disk path, metrics queries, and recent action history, which are populated automatically by the backend poller.
|
||||
|
||||
@@ -88,6 +88,42 @@ def _resolve_ssh_client(
|
||||
return client, host, port
|
||||
|
||||
|
||||
def _raise_ssh_validation_error(host: str, port: int, exc: Exception) -> None:
|
||||
message = str(exc)
|
||||
lowered = message.lower()
|
||||
if "protocol banner" in lowered:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||
detail=(
|
||||
f"SSH banner not received from {host}:{port}; the backend could not complete the SSH handshake."
|
||||
),
|
||||
) from exc
|
||||
if "no authentication methods available" in lowered or "authentication failed" in lowered:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail=(
|
||||
f"SSH authentication failed for {host}:{port}. "
|
||||
"Check the selected SSH key, passphrase, username, or password."
|
||||
),
|
||||
) from exc
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_502_BAD_GATEWAY,
|
||||
detail=f"SSH validation failed for {host}:{port}: {message}",
|
||||
) from exc
|
||||
|
||||
|
||||
def _validate_saved_machine_ssh(machine: MonitoringMachineInput, store: SettingsStore) -> None:
|
||||
if str(machine.mode or "").strip().lower() != "ssh":
|
||||
return
|
||||
client, host, port = _resolve_ssh_client(machine, store)
|
||||
try:
|
||||
client.connect()
|
||||
except Exception as exc:
|
||||
_raise_ssh_validation_error(host, port, exc)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
@router.post("/machines/test-ssh")
|
||||
def test_machine_ssh(
|
||||
machine: MonitoringMachineInput,
|
||||
@@ -146,8 +182,11 @@ def post_machine(
|
||||
) -> dict[str, Any]:
|
||||
saved = store.upsert_machine(machine.model_dump(exclude_none=True), machine.id)
|
||||
poller = get_monitoring_poller()
|
||||
poller.start()
|
||||
poller.kick()
|
||||
try:
|
||||
_validate_saved_machine_ssh(MonitoringMachineInput.model_validate(saved), store)
|
||||
finally:
|
||||
poller.start()
|
||||
poller.kick()
|
||||
return saved
|
||||
|
||||
|
||||
@@ -161,8 +200,11 @@ def put_machine(
|
||||
raise HTTPException(status_code=404, detail="Machine not found")
|
||||
saved = store.upsert_machine(machine.model_dump(exclude_none=True), machine_id)
|
||||
poller = get_monitoring_poller()
|
||||
poller.start()
|
||||
poller.kick()
|
||||
try:
|
||||
_validate_saved_machine_ssh(MonitoringMachineInput.model_validate(saved), store)
|
||||
finally:
|
||||
poller.start()
|
||||
poller.kick()
|
||||
return saved
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user