fixes and improvements

This commit is contained in:
2026-05-07 15:43:22 +02:00
parent 1d1f052d18
commit 6d8b08d30f
6 changed files with 11 additions and 8 deletions
+1
View File
@@ -126,6 +126,7 @@ 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.
- 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.
@@ -20,6 +20,8 @@ from typing import Any
import paramiko
from media_library_viewer_api.services.known_hosts import ensure_known_host
logger = logging.getLogger(__name__)
@@ -64,12 +66,14 @@ class RemoteSSHClient:
def connect(self) -> paramiko.SSHClient:
"""Create or reuse the Paramiko connection.
Unknown host keys are rejected. The application can synthesize a managed
known_hosts file under its cache directory so users do not need to mount
their local SSH directory into the container.
Unknown host keys are recorded on first contact in the managed
known_hosts file when one is configured. After that, strict checking
remains in effect so host key changes are still rejected.
"""
if self._client:
return self._client
if self.known_hosts_path:
ensure_known_host(self.host, self.port, Path(self.known_hosts_path), strict=True)
client = paramiko.SSHClient()
client.load_system_host_keys()
if self.known_hosts_path and Path(self.known_hosts_path).is_file():
@@ -18,7 +18,6 @@ from media_library_viewer_api.clients.jellyfin import JellyfinClient
from media_library_viewer_api.clients.jellyseerr import JellyseerrClient
from media_library_viewer_api.clients.local import LocalCommandClient
from media_library_viewer_api.clients.ssh import RemoteSSHClient
from media_library_viewer_api.services.known_hosts import ensure_known_host
from media_library_viewer_api.config import get_settings
from media_library_viewer_api.services.mail_queue import MailQueue, get_mail_queue as _get_mail_queue
from media_library_viewer_api.services.monitoring_poller import (
@@ -153,7 +152,6 @@ def get_ssh_client(request: Request = None):
return LocalCommandClient()
if machine and machine.get("host") and machine.get("username"):
known_hosts_path = get_settings().ssh_known_hosts_file
ensure_known_host(str(machine.get("host")), int(machine.get("port") or 22), known_hosts_path)
key_data = None
key_passphrase = None
ssh_key_id = str(machine.get("ssh_key_id") or "").strip()
@@ -190,7 +188,6 @@ def get_ssh_client(request: Request = None):
)
if not settings.ssh_key_path:
raise RuntimeError("No SSH machine is configured and SSH key settings must be configured")
ensure_known_host(settings.ssh_host, settings.ssh_port, settings.ssh_known_hosts_file)
return _ssh_client_for(("legacy", settings.ssh_host, settings.ssh_username, settings.ssh_port, settings.ssh_key_path, settings.ssh_password or None, None, None, str(settings.ssh_known_hosts_file)))
@@ -14,7 +14,6 @@ from media_library_viewer_api.clients.local import LocalCommandClient
from media_library_viewer_api.clients.ssh import RemoteSSHClient
from media_library_viewer_api.config import get_settings
from media_library_viewer_api.dependencies import get_settings_store
from media_library_viewer_api.services.known_hosts import ensure_known_host
from media_library_viewer_api.services.settings_store import SettingsStore
logger = logging.getLogger(__name__)
@@ -67,7 +66,6 @@ def _client_for_machine(store: SettingsStore, machine: dict[str, Any]):
raise HTTPException(status_code=400, detail="SSH machine is missing host or username")
settings = get_settings()
ensure_known_host(host, int(machine.get("port") or 22), settings.ssh_known_hosts_file)
private_key = str(machine.get("ssh_private_key") or "")
passphrase = str(machine.get("ssh_private_key_passphrase") or "")
@@ -41,12 +41,14 @@ def build_machine_client(machine: dict[str, Any]):
key_directory = str(machine.get("key_directory") or "").strip()
key_name = str(machine.get("key_name") or "").strip()
key_path = f"{key_directory}/{key_name}" if key_directory and key_name else None
settings = get_settings()
return RemoteSSHClient(
host=str(machine.get("host") or ""),
username=str(machine.get("username") or ""),
port=int(machine.get("port") or 22),
key_filename=key_path,
password=str(machine.get("password") or "") or None,
known_hosts_path=str(settings.ssh_known_hosts_file),
)
+1
View File
@@ -275,3 +275,4 @@ Phase 1: Jellyfin media index, SSH-based remote filesystem inspection, server mo
- 2026-05-06: Library stats, Jellyfin activity, and Monitoring overview now use shared section-container patterns so subcontainers stay consistent across the app.
- 2026-05-07: The app versioning scheme should be hybrid: auto-detect package/build metadata when available, but allow explicit overrides for deployments that need fixed labels.
- 2026-05-07: The shell should display both frontend and backend version labels so deployed builds are easy to identify without opening a separate diagnostics screen.
- 2026-05-07: SSH host verification should use trust-on-first-use for new machines by recording the first observed host key into the backend-managed known_hosts file, while still rejecting later key mismatches.