27f3603d8f
Save pending configuration, deployment, and documentation work.
89 lines
4.3 KiB
Python
Executable File
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)
|