deployment fixes

This commit is contained in:
2026-05-04 16:03:39 +02:00
parent ee61007939
commit 26f6f3d41f
11 changed files with 34 additions and 30 deletions
+3 -4
View File
@@ -24,12 +24,11 @@ SMTP_TIMEOUT=30
SSH_HOST=media-server.example.com
SSH_USERNAME=username
SSH_PORT=22
# In Docker Compose, mount ./secrets/ssh to /root/.ssh inside the backend container.
# The app uses SSH_KEY_DIRECTORY + SSH_KEY_NAME to build the full key path.
# Host-side directory mounted into the backend container at /root/.ssh.
SSH_KEY_HOST_DIR=/absolute/path/to/your/ssh-dir
# Container-side path assembled by the app:
SSH_KEY_DIRECTORY=/root/.ssh
SSH_KEY_NAME=id_ed25519
# Optional legacy override if you want to provide the full path directly.
# SSH_KEY_FILENAME=/root/.ssh/id_ed25519
# SSH_PASSWORD=optional-password-or-key-passphrase
REMOTE_MEDIA_ROOT=/mnt/media
# Optional fallback prefix when REMOTE_MEDIA_ROOT mapping is not enough.
-3
View File
@@ -40,9 +40,6 @@ env/
*.log
tmp/
# SSH secrets used by docker compose
secrets/ssh/
# Frontend
frontend/node_modules/
frontend/dist/
+3 -4
View File
@@ -83,12 +83,11 @@ LOG_LEVEL=INFO
SSH_HOST=media-server.example.com
SSH_USERNAME=username
SSH_PORT=22
# In Docker Compose, mount ./secrets/ssh to /root/.ssh inside the backend container.
# The app uses SSH_KEY_DIRECTORY + SSH_KEY_NAME to build the full key path.
# Host-side directory mounted into the backend container at /root/.ssh.
SSH_KEY_HOST_DIR=/absolute/path/to/your/ssh-dir
# Container-side path assembled by the app:
SSH_KEY_DIRECTORY=/root/.ssh
SSH_KEY_NAME=id_ed25519
# Optional legacy override if you want to provide the full path directly.
# SSH_KEY_FILENAME=/root/.ssh/id_ed25519
SSH_PASSWORD=
REMOTE_MEDIA_ROOT=/srv/media
+3 -4
View File
@@ -71,12 +71,11 @@ OIDC_CLOCK_SKEW_SECONDS=30
SSH_HOST=media-server.example.com
SSH_USERNAME=username
SSH_PORT=22
# In Docker Compose, mount ./secrets/ssh to /root/.ssh inside the backend container.
# The app uses SSH_KEY_DIRECTORY + SSH_KEY_NAME to build the full key path.
# Host-side directory mounted into the backend container at /root/.ssh.
SSH_KEY_HOST_DIR=/absolute/path/to/your/ssh-dir
# Container-side path assembled by the app:
SSH_KEY_DIRECTORY=/root/.ssh
SSH_KEY_NAME=id_ed25519
# Optional legacy override if you want to provide the full path directly.
# SSH_KEY_FILENAME=/root/.ssh/id_ed25519
SSH_PASSWORD=
REMOTE_MEDIA_ROOT=/srv/media
@@ -55,9 +55,8 @@ class Settings(BaseSettings):
ssh_host: str = ""
ssh_username: str = ""
ssh_port: int = 22
ssh_key_directory: str = str(Path.home() / ".ssh")
ssh_key_name: str = "id_rsa"
ssh_key_filename: str = "" # legacy direct override (full path)
ssh_key_directory: str = ""
ssh_key_name: str = ""
ssh_password: str = ""
# Remote paths
@@ -75,8 +74,8 @@ class Settings(BaseSettings):
@property
def ssh_key_path(self) -> str:
if self.ssh_key_filename:
return self.ssh_key_filename
if not self.ssh_key_directory or not self.ssh_key_name:
return ""
return str(Path(self.ssh_key_directory) / self.ssh_key_name)
model_config = {"env_file": ".env", "env_file_encoding": "utf-8", "extra": "ignore"}
@@ -46,18 +46,21 @@ def get_ssh_client() -> RemoteSSHClient:
"""Return a cached SSH client (connects on first use)."""
settings = get_settings()
logger.info(
"Creating SSH client host=%s user=%s port=%s key=%s password=%s",
"Creating SSH client host=%s user=%s port=%s key_dir=%s key_name=%s password=%s",
settings.ssh_host or "<unset>",
settings.ssh_username or "<unset>",
settings.ssh_port,
settings.ssh_key_path or "<unset>",
settings.ssh_key_directory or "<unset>",
settings.ssh_key_name or "<unset>",
"set" if settings.ssh_password else "missing",
)
if not settings.ssh_key_path:
raise RuntimeError("SSH_KEY_DIRECTORY and SSH_KEY_NAME must be configured")
client = RemoteSSHClient(
host=settings.ssh_host,
username=settings.ssh_username,
port=settings.ssh_port,
key_filename=settings.ssh_key_path or None,
key_filename=settings.ssh_key_path,
password=settings.ssh_password or None,
)
try:
@@ -54,7 +54,6 @@ def describe_settings(settings: object) -> dict[str, str]:
"ssh_port": str(getattr(settings, "ssh_port", 22) or 22),
"ssh_key_directory": getattr(settings, "ssh_key_directory", "") or "<unset>",
"ssh_key_name": getattr(settings, "ssh_key_name", "") or "<unset>",
"ssh_key_filename": "set" if getattr(settings, "ssh_key_filename", "") else "missing",
"ssh_password": "set" if getattr(settings, "ssh_password", "") else "missing",
"smtp_host": _sanitize_url(getattr(settings, "smtp_host", "")),
"smtp_port": str(getattr(settings, "smtp_port", 587) or 587),
+9
View File
@@ -15,6 +15,9 @@ class TestSettings:
assert settings.jellyseerr_url == ""
assert settings.log_level == "INFO"
assert settings.remote_media_root == ""
assert settings.ssh_key_directory == ""
assert settings.ssh_key_name == ""
assert settings.ssh_key_path == ""
def test_from_env(self):
env = {
@@ -51,3 +54,9 @@ class TestSettings:
with patch.dict(os.environ, env, clear=True):
settings = Settings(_env_file=None)
assert settings.path_prefix == "/srv"
def test_ssh_key_path_joins_directory_and_name(self):
env = {"SSH_KEY_DIRECTORY": "/root/.ssh", "SSH_KEY_NAME": "id_ed25519"}
with patch.dict(os.environ, env, clear=True):
settings = Settings(_env_file=None)
assert settings.ssh_key_path == "/root/.ssh/id_ed25519"
+2 -2
View File
@@ -10,12 +10,12 @@ services:
environment:
AUTH_ENABLED: "false"
SSH_KEY_DIRECTORY: /root/.ssh
SSH_KEY_NAME: ${SSH_KEY_NAME:-id_ed25519}
SSH_KEY_NAME: ${SSH_KEY_NAME}
ports:
- "8000:8000"
volumes:
- ./backend:/app/backend
- ./secrets/ssh:/root/.ssh:ro
- ${SSH_KEY_HOST_DIR}:/root/.ssh:ro
restart: unless-stopped
frontend:
+2 -2
View File
@@ -8,9 +8,9 @@ services:
environment:
AUTH_ENABLED: "true"
SSH_KEY_DIRECTORY: /root/.ssh
SSH_KEY_NAME: ${SSH_KEY_NAME:-id_ed25519}
SSH_KEY_NAME: ${SSH_KEY_NAME}
volumes:
- ${SSH_KEY_DIRECTORY:-./secrets/ssh}:/root/.ssh:ro
- ${SSH_KEY_HOST_DIR}:/root/.ssh:ro
restart: unless-stopped
networks:
- web
+2 -2
View File
@@ -128,7 +128,7 @@ Phase 1: Jellyfin media index, SSH-based remote filesystem inspection, server mo
- Provide Docker Compose deployment files at the repository root for production and local development.
- Backend Docker deployment should mount a private SSH key and a known_hosts file into the container rather than baking them into the image.
- Production compose should also pass the root `.env` into the backend container so runtime auth settings like `OIDC_ISSUER_URL` are available there, not just at compose interpolation time.
- The SSH key configuration should support separate directory/name inputs so Docker Compose can mount an entire `./secrets/ssh` directory into `/root/.ssh` while the app assembles the full key path.
- The SSH key configuration should support separate directory/name inputs so Docker Compose can mount an arbitrary host SSH directory into `/root/.ssh` while the app assembles the full key path.
- Show Jellyfin media counts for movies, series, and series episodes on the dashboard.
- Show dashboard session activity from Jellyfin, including both currently playing sessions and logged-in idle sessions.
- Activity rows should include user, media title (or `(idle)`), playback state (`playing`/`paused`/`idle`), and whether transcoding is active.
@@ -171,4 +171,4 @@ Phase 1: Jellyfin media index, SSH-based remote filesystem inspection, server mo
- 2026-05-03: Confirmed the shared session table should keep the compact overall status summary line above the rows.
- 2026-05-03: Updated the dashboard monitoring cards to show 10-minute averages with high/low subtext instead of only the latest sample.
- 2026-05-03: Added OIDC/JWT auth support plus root-level Docker Compose deployment files for production and dev workflows.
- 2026-05-04: Backend Docker Compose now mounts the entire `./secrets/ssh` directory into `/root/.ssh` so Paramiko can use a private key and strict host-key checking without baking secrets into the image.
- 2026-05-04: Backend Docker Compose now mounts a host SSH directory into `/root/.ssh` so Paramiko can use a private key and strict host-key checking without baking secrets into the image.