Files
hrynco-notification-service/docker/kubernetes/shared-notification/prepare-storage.py
T
agrynco 27f3603d8f chore: preserve current workspace changes
Save pending configuration, deployment, and documentation work.
2026-10-03 00:56:28 +03:00

89 lines
4.3 KiB
Python
Executable File

#!/usr/bin/env python3
"""Owner-run provisioning/verification of Notification Service storage. No deletion."""
import json
import os
from pathlib import Path
import stat
import subprocess
import sys
BASE = Path("/mnt/shared/notification-service")
UUID = "c2c31640-a247-4988-bc7b-9ee9789464d1"
DIRECTORIES = (("postgres", 999, 999), ("rabbitmq", 100, 101))
def main():
if sys.platform != "linux" or os.geteuid() != 0:
raise RuntimeError("run this script as root on mini-pc-2")
for location in (Path("/mnt/files"), Path("/mnt/shared")):
if not location.is_dir() or location.is_symlink():
raise RuntimeError("expected real storage mount directory is missing")
result = subprocess.run(["findmnt", "--json", "--target", str(location), "--output", "TARGET,FSTYPE,UUID"],
check=True, capture_output=True, timeout=20)
filesystems = json.loads(result.stdout)["filesystems"]
if len(filesystems) != 1 or filesystems[0]["target"] != str(location) or filesystems[0]["fstype"] != "xfs" or filesystems[0]["uuid"] != UUID:
raise RuntimeError("storage mount/UUID/XFS verification failed; no directories created")
if BASE.is_symlink():
raise RuntimeError("notification-service path must not be a symlink")
if BASE.exists():
info = BASE.stat()
if not BASE.is_dir() or (info.st_uid, info.st_gid, stat.S_IMODE(info.st_mode)) != (0, 0, 0o755):
raise RuntimeError("existing parent path has unexpected type/ownership/permissions")
if set(child.name for child in BASE.iterdir()) - {name for name, _, _ in DIRECTORIES}:
raise RuntimeError("unexpected contents in notification-service parent")
# Inspect both directories before creating anything. Never adopt arbitrary data.
for name, uid, gid in DIRECTORIES:
path = BASE / name
if path.is_symlink():
raise RuntimeError("service data path must not be a symlink")
if not path.exists():
continue
info = path.stat()
if not path.is_dir() or (info.st_uid, info.st_gid, stat.S_IMODE(info.st_mode)) != (uid, gid, 0o700):
raise RuntimeError("existing service directory has unexpected ownership/permissions")
marker = path / ".invemory-storage.json"
if marker.is_symlink():
raise RuntimeError("marker must not be a symlink")
if marker.exists():
marker_info = marker.stat()
if (marker_info.st_uid, marker_info.st_gid, stat.S_IMODE(marker_info.st_mode)) != (0, 0, 0o644):
raise RuntimeError("existing marker has unexpected ownership/permissions")
if json.loads(marker.read_text(encoding="utf-8")) != {"uuid": UUID, "role": "shared-notification/" + name}:
raise RuntimeError("existing marker mismatch; never overwritten")
elif any(path.iterdir()):
raise RuntimeError("unmarked service directory is not empty; never adopted")
os.umask(0o077)
if not BASE.exists():
BASE.mkdir(mode=0o755)
os.chown(BASE, 0, 0)
os.chmod(BASE, 0o755)
for name, uid, gid in DIRECTORIES:
path = BASE / name
if not path.exists():
path.mkdir(mode=0o700)
os.chown(path, uid, gid)
marker = path / ".invemory-storage.json"
if not marker.exists():
fd = os.open(marker, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644)
with os.fdopen(fd, "w", encoding="utf-8") as stream:
json.dump({"uuid": UUID, "role": "shared-notification/" + name}, stream)
stream.write("\n")
os.chown(marker, 0, 0)
os.chmod(marker, 0o644)
info = path.stat()
marker_info = marker.stat()
if (info.st_uid, info.st_gid, stat.S_IMODE(info.st_mode)) != (uid, gid, 0o700):
raise RuntimeError("directory ownership verification failed")
if (marker_info.st_uid, marker_info.st_gid, stat.S_IMODE(marker_info.st_mode)) != (0, 0, 0o644):
raise RuntimeError("marker ownership verification failed")
print("Provisioned two Notification Service directories; XFS UUID and permissions verified.")
if __name__ == "__main__":
try:
main()
except (RuntimeError, OSError, ValueError, subprocess.SubprocessError) as error:
print("STOP: " + str(error), file=sys.stderr)
sys.exit(1)