(ops) Expand native install self checks and recovery docs
Extend the runtime self check with native install diagnostics for the environment file, service user, backup root ownership, and SQLite database path. Export install metadata from the systemd units and pobsync-manage wrapper so custom env files and service users are visible to Django checks. Document restart, journal log inspection, and rollback steps in the README so production updates have a clear recovery path.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import pwd
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -28,6 +29,7 @@ class SelfCheck:
|
||||
def collect_self_checks() -> list[SelfCheck]:
|
||||
checks: list[SelfCheck] = []
|
||||
checks.extend(_django_checks())
|
||||
checks.extend(_install_checks())
|
||||
checks.extend(_path_checks())
|
||||
checks.extend(_binary_checks())
|
||||
checks.extend(_database_checks())
|
||||
@@ -36,6 +38,10 @@ def collect_self_checks() -> list[SelfCheck]:
|
||||
return checks
|
||||
|
||||
|
||||
def _native_runtime_available() -> bool:
|
||||
return Path("/run/systemd/system").exists() and shutil.which("systemctl") is not None
|
||||
|
||||
|
||||
def summarize_self_checks(checks: list[SelfCheck]) -> dict[str, int]:
|
||||
return {
|
||||
"ok": sum(1 for check in checks if check.status == "ok"),
|
||||
@@ -91,18 +97,105 @@ def _path_checks() -> list[SelfCheck]:
|
||||
)
|
||||
db_settings = settings.DATABASES["default"]
|
||||
if db_settings["ENGINE"] == "django.db.backends.sqlite3":
|
||||
sqlite_path = Path(str(db_settings["NAME"]))
|
||||
checks.append(
|
||||
_path_check(
|
||||
"SQLite directory",
|
||||
Path(str(db_settings["NAME"])).parent,
|
||||
sqlite_path.parent,
|
||||
must_be_absolute=True,
|
||||
must_exist=True,
|
||||
must_be_writable=True,
|
||||
)
|
||||
)
|
||||
checks.append(_sqlite_database_check(sqlite_path))
|
||||
return checks
|
||||
|
||||
|
||||
def _install_checks() -> list[SelfCheck]:
|
||||
if not _native_runtime_available() and not Path(settings.POBSYNC_ENV_FILE).exists():
|
||||
return [
|
||||
SelfCheck(
|
||||
"Environment file",
|
||||
"skipped",
|
||||
"Native environment file is not configured in this runtime.",
|
||||
"This is expected inside Docker or local development.",
|
||||
),
|
||||
SelfCheck(
|
||||
"Service user",
|
||||
"skipped",
|
||||
"Native service user check is not available in this runtime.",
|
||||
"This is expected inside Docker or local development.",
|
||||
),
|
||||
SelfCheck(
|
||||
"Backup root owner",
|
||||
"skipped",
|
||||
"Native backup root ownership check is not available in this runtime.",
|
||||
"This is expected inside Docker or local development.",
|
||||
),
|
||||
]
|
||||
|
||||
checks = [_env_file_check(Path(settings.POBSYNC_ENV_FILE)), _service_user_check()]
|
||||
checks.append(_backup_root_owner_check(Path(settings.POBSYNC_BACKUP_ROOT)))
|
||||
return checks
|
||||
|
||||
|
||||
def _env_file_check(path: Path) -> SelfCheck:
|
||||
if not path.is_absolute():
|
||||
return SelfCheck("Environment file", "failed", f"{path} is not absolute.")
|
||||
if not path.exists():
|
||||
return SelfCheck("Environment file", "failed", f"{path} does not exist.")
|
||||
if not path.is_file():
|
||||
return SelfCheck("Environment file", "failed", f"{path} is not a regular file.")
|
||||
if not os.access(path, os.R_OK):
|
||||
return SelfCheck("Environment file", "failed", f"{path} is not readable by this process.")
|
||||
return SelfCheck("Environment file", "ok", str(path))
|
||||
|
||||
|
||||
def _service_user_check() -> SelfCheck:
|
||||
expected_user = settings.POBSYNC_SERVICE_USER
|
||||
try:
|
||||
current_user = pwd.getpwuid(os.geteuid()).pw_name
|
||||
except KeyError:
|
||||
return SelfCheck("Service user", "failed", f"Current uid {os.geteuid()} has no passwd entry.")
|
||||
if current_user != expected_user:
|
||||
return SelfCheck(
|
||||
"Service user",
|
||||
"warning",
|
||||
f"Current process runs as {current_user}, expected {expected_user}.",
|
||||
"Run terminal checks with sudo -u <service-user> pobsync-manage check_pobsync_install.",
|
||||
)
|
||||
return SelfCheck("Service user", "ok", current_user)
|
||||
|
||||
|
||||
def _backup_root_owner_check(path: Path) -> SelfCheck:
|
||||
if not path.exists():
|
||||
return SelfCheck("Backup root owner", "failed", f"{path} does not exist.")
|
||||
expected_user = settings.POBSYNC_SERVICE_USER
|
||||
try:
|
||||
owner = pwd.getpwuid(path.stat().st_uid).pw_name
|
||||
except KeyError:
|
||||
return SelfCheck("Backup root owner", "warning", f"{path} owner uid {path.stat().st_uid} has no passwd entry.")
|
||||
if owner != expected_user:
|
||||
return SelfCheck(
|
||||
"Backup root owner",
|
||||
"warning",
|
||||
f"{path} is owned by {owner}, expected {expected_user}.",
|
||||
)
|
||||
return SelfCheck("Backup root owner", "ok", f"{path} owner={owner}")
|
||||
|
||||
|
||||
def _sqlite_database_check(path: Path) -> SelfCheck:
|
||||
if not path.is_absolute():
|
||||
return SelfCheck("SQLite database", "failed", f"{path} is not absolute.")
|
||||
if not path.exists():
|
||||
return SelfCheck("SQLite database", "warning", f"{path} does not exist yet.")
|
||||
if not path.is_file():
|
||||
return SelfCheck("SQLite database", "failed", f"{path} is not a regular file.")
|
||||
if not os.access(path, os.R_OK | os.W_OK):
|
||||
return SelfCheck("SQLite database", "failed", f"{path} is not readable and writable by this process.")
|
||||
return SelfCheck("SQLite database", "ok", str(path))
|
||||
|
||||
|
||||
def _path_check(
|
||||
name: str,
|
||||
path: Path,
|
||||
@@ -178,7 +271,7 @@ def _config_checks() -> list[SelfCheck]:
|
||||
|
||||
|
||||
def _systemd_checks() -> list[SelfCheck]:
|
||||
if not Path("/run/systemd/system").exists() or shutil.which("systemctl") is None:
|
||||
if not _native_runtime_available():
|
||||
return [
|
||||
SelfCheck(
|
||||
"Systemd services",
|
||||
|
||||
Reference in New Issue
Block a user