2026-05-19 19:11:57 +02:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
from pobsync.snapshot_meta import resolve_host_root
|
|
|
|
|
|
|
|
|
|
from .models import GlobalConfig, HostConfig
|
|
|
|
|
from .self_check import SelfCheck
|
2026-05-19 19:41:40 +02:00
|
|
|
from .ssh_keys import identity_path
|
2026-05-19 19:11:57 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
HOST_BACKUP_SUBDIRS = ("scheduled", "manual", ".incomplete")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ensure_host_directories(host: HostConfig, global_config: GlobalConfig | None = None) -> Path:
|
|
|
|
|
global_config = global_config or GlobalConfig.objects.get(name="default")
|
|
|
|
|
host_root = resolve_host_root(global_config.backup_root, host.host)
|
|
|
|
|
for subdir in HOST_BACKUP_SUBDIRS:
|
|
|
|
|
(host_root / subdir).mkdir(parents=True, exist_ok=True)
|
|
|
|
|
return host_root
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def collect_host_checks(host: HostConfig, global_config: GlobalConfig | None = None) -> list[SelfCheck]:
|
|
|
|
|
checks: list[SelfCheck] = []
|
|
|
|
|
try:
|
|
|
|
|
global_config = global_config or GlobalConfig.objects.get(name="default")
|
|
|
|
|
except GlobalConfig.DoesNotExist:
|
|
|
|
|
return [SelfCheck("Host global config", "failed", "Default global config does not exist.")]
|
|
|
|
|
|
|
|
|
|
checks.append(
|
|
|
|
|
SelfCheck(
|
|
|
|
|
"Host enabled",
|
|
|
|
|
"ok" if host.enabled else "warning",
|
|
|
|
|
"Host is enabled." if host.enabled else "Host is disabled.",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
checks.append(
|
|
|
|
|
SelfCheck(
|
|
|
|
|
"Host address",
|
|
|
|
|
"ok" if host.address.strip() else "failed",
|
|
|
|
|
host.address.strip() or "Host address is empty.",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
credential = host.ssh_credential or global_config.default_ssh_credential
|
2026-05-19 19:41:40 +02:00
|
|
|
if credential is None:
|
|
|
|
|
checks.append(SelfCheck("Host SSH credential", "warning", "No host or global SSH credential selected."))
|
|
|
|
|
else:
|
|
|
|
|
checks.append(SelfCheck("Host SSH credential", "ok", str(credential)))
|
|
|
|
|
if credential.key_path:
|
|
|
|
|
key_path = identity_path(credential)
|
|
|
|
|
checks.append(
|
|
|
|
|
_host_path_check("Host SSH key file", key_path, must_exist=True, must_be_writable=False, must_be_readable=True)
|
|
|
|
|
)
|
2026-05-19 19:49:33 +02:00
|
|
|
elif credential.private_key:
|
|
|
|
|
checks.append(
|
|
|
|
|
SelfCheck(
|
|
|
|
|
"Host SSH key storage",
|
|
|
|
|
"warning",
|
|
|
|
|
"Selected credential stores private key material in the database.",
|
|
|
|
|
"Generated filesystem keys are recommended for native systemd installs.",
|
|
|
|
|
)
|
|
|
|
|
)
|
2026-05-19 19:11:57 +02:00
|
|
|
|
|
|
|
|
host_root = resolve_host_root(global_config.backup_root, host.host)
|
|
|
|
|
checks.append(_host_path_check("Host backup root", host_root, must_exist=True, must_be_writable=True))
|
|
|
|
|
for subdir in HOST_BACKUP_SUBDIRS:
|
|
|
|
|
checks.append(_host_path_check(f"Host directory: {subdir}", host_root / subdir, must_exist=True, must_be_writable=True))
|
|
|
|
|
return checks
|
|
|
|
|
|
|
|
|
|
|
2026-05-19 19:41:40 +02:00
|
|
|
def _host_path_check(
|
|
|
|
|
name: str,
|
|
|
|
|
path: Path,
|
|
|
|
|
*,
|
|
|
|
|
must_exist: bool,
|
|
|
|
|
must_be_writable: bool,
|
|
|
|
|
must_be_readable: bool = False,
|
|
|
|
|
) -> SelfCheck:
|
2026-05-19 19:11:57 +02:00
|
|
|
if must_exist and not path.exists():
|
|
|
|
|
return SelfCheck(name, "failed", f"{path} does not exist.")
|
|
|
|
|
target = path if path.exists() else path.parent
|
|
|
|
|
if not target.exists():
|
|
|
|
|
return SelfCheck(name, "failed", f"{target} does not exist.")
|
|
|
|
|
if must_be_writable and not os.access(target, os.W_OK):
|
|
|
|
|
return SelfCheck(name, "failed", f"{target} is not writable by this process.")
|
2026-05-19 19:41:40 +02:00
|
|
|
if must_be_readable and not os.access(target, os.R_OK):
|
|
|
|
|
return SelfCheck(name, "failed", f"{target} is not readable by this process.")
|
2026-05-19 19:11:57 +02:00
|
|
|
return SelfCheck(name, "ok", str(path))
|