(feature) Generate filesystem-backed SSH credentials
Add filesystem-backed SSH credentials for the native systemd deployment path. Generated keys are stored below POBSYNC_HOME with 0600 permissions, while Django keeps the public key, fingerprint, path, and selection metadata. Add a Django SSH key generation view, delete action for unused generated keys, and a management command used by the installer to ensure a default backup key exists. Update runtime config to use generated key paths directly as IdentityFile, extend host checks to verify key readability, and keep legacy uploaded keys available for compatibility.
This commit is contained in:
@@ -7,6 +7,7 @@ from pobsync.snapshot_meta import resolve_host_root
|
||||
|
||||
from .models import GlobalConfig, HostConfig
|
||||
from .self_check import SelfCheck
|
||||
from .ssh_keys import identity_path
|
||||
|
||||
|
||||
HOST_BACKUP_SUBDIRS = ("scheduled", "manual", ".incomplete")
|
||||
@@ -43,13 +44,15 @@ def collect_host_checks(host: HostConfig, global_config: GlobalConfig | None = N
|
||||
)
|
||||
|
||||
credential = host.ssh_credential or global_config.default_ssh_credential
|
||||
checks.append(
|
||||
SelfCheck(
|
||||
"Host SSH credential",
|
||||
"ok" if credential else "warning",
|
||||
str(credential) if credential else "No host or global SSH credential selected.",
|
||||
)
|
||||
)
|
||||
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)
|
||||
)
|
||||
|
||||
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))
|
||||
@@ -58,7 +61,14 @@ def collect_host_checks(host: HostConfig, global_config: GlobalConfig | None = N
|
||||
return checks
|
||||
|
||||
|
||||
def _host_path_check(name: str, path: Path, *, must_exist: bool, must_be_writable: bool) -> SelfCheck:
|
||||
def _host_path_check(
|
||||
name: str,
|
||||
path: Path,
|
||||
*,
|
||||
must_exist: bool,
|
||||
must_be_writable: bool,
|
||||
must_be_readable: bool = False,
|
||||
) -> SelfCheck:
|
||||
if must_exist and not path.exists():
|
||||
return SelfCheck(name, "failed", f"{path} does not exist.")
|
||||
target = path if path.exists() else path.parent
|
||||
@@ -66,4 +76,6 @@ def _host_path_check(name: str, path: Path, *, must_exist: bool, must_be_writabl
|
||||
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.")
|
||||
if must_be_readable and not os.access(target, os.R_OK):
|
||||
return SelfCheck(name, "failed", f"{target} is not readable by this process.")
|
||||
return SelfCheck(name, "ok", str(path))
|
||||
|
||||
Reference in New Issue
Block a user