Publish curated Unraid-AppOps-Gateway source from 4f159a7e60d5becc5218ef4c541e2005d733a537

This commit is contained in:
Public Source Publisher committed 2026-10-03 18:14:21 +00:00
1 parent ec6f5b559f
commit 235d8fd493
7 files changed
+783 -50

No files matched your search

@@ -21,12 +21,11 @@ _MAX_CONTAINERS = 32
# Fixed application-owned mutation. Callers never provide a command or host
# path. Repository-derived topology is validated before it reaches this script,
# then re-bound to the exact immutable Git revision inside the controller.
_REGISTER_SCRIPT = r'''
_REGISTER_SCRIPT = r"""
import fcntl
import json
import os
from pathlib import Path
import shutil
import stat
import subprocess
import sys
@@ -38,11 +37,35 @@ lock_path = Path("/mnt/cache/appdata/gitea-deploy-runners/deploy.lock")
repository, revision, branch, raw_project = sys.argv[1:5]
project = json.loads(raw_project)
if path.is_symlink() or not path.is_file():
raise SystemExit(20)
info = path.stat()
if not stat.S_ISREG(info.st_mode):
raise SystemExit(21)
def _validation_compatible(repository, validation):
expected_validation = {
"validation_gate_version": 1,
"required": True,
"contexts": ["Managed validation / full (push)"],
"timeout_seconds": 3600 if repository == "jens/mediarr" else 1800,
"poll_interval_seconds": 5,
"max_poll_interval_seconds": 30,
}
if not isinstance(validation, dict) or validation.get("required") is not True or any(
type(validation.get(key)) is not int
for key in ("validation_gate_version", "timeout_seconds", "poll_interval_seconds", "max_poll_interval_seconds")
):
return False
if validation == expected_validation:
return True
return repository == "jens/mediarr" and validation == {
**expected_validation,
"contexts": ["CI / gates (push)", "Managed validation / full (push)"],
}
for directory in (path.parent, host_path.parent, host_path.parent.parent):
info = directory.lstat()
if not stat.S_ISDIR(info.st_mode) or info.st_uid != 0 or info.st_mode & 0o022:
raise SystemExit(21)
for registry_path in (path, host_path):
info = registry_path.lstat()
if not stat.S_ISREG(info.st_mode) or info.st_uid != 0 or stat.S_IMODE(info.st_mode) != 0o600:
raise SystemExit(21)
try:
if host_path.is_symlink() or not host_path.is_file() or not os.path.samefile(path, host_path):
@@ -117,7 +140,18 @@ with lock_handle as lock:
raise SystemExit(30)
try:
projects = json.loads(mutation_path.read_text(encoding="utf-8"))
registry_info = mutation_path.lstat()
registry_identity = (registry_info.st_dev, registry_info.st_ino, registry_info.st_mode,
registry_info.st_uid, registry_info.st_gid, registry_info.st_mtime_ns,
registry_info.st_ctime_ns, registry_info.st_size)
if not stat.S_ISREG(registry_info.st_mode) or registry_info.st_uid != 0 or stat.S_IMODE(registry_info.st_mode) != 0o600:
raise SystemExit(31)
descriptor = os.open(mutation_path, os.O_RDONLY | os.O_NOFOLLOW)
with os.fdopen(descriptor, "rb") as stream:
original = stream.read(4 * 1024 * 1024 + 1)
if len(original) > 4 * 1024 * 1024:
raise SystemExit(31)
projects = json.loads(original)
except Exception:
raise SystemExit(31)
if not isinstance(projects, dict):
@@ -185,23 +219,32 @@ with lock_handle as lock:
raise SystemExit(23)
validation = current.get("validation")
if validation is not None:
expected_validation = {
"validation_gate_version": 1,
"required": True,
"contexts": ["Managed validation / full (push)"],
"timeout_seconds": 3600 if repository == "jens/mediarr" else 1800,
"poll_interval_seconds": 5,
"max_poll_interval_seconds": 30,
}
if validation != expected_validation:
if not _validation_compatible(repository, validation):
raise SystemExit(23)
if changed:
backup = mutation_path.with_name(
f"projects.json.appops-backup-{int(time.time())}-{os.getpid()}"
)
try:
shutil.copy2(mutation_path, backup)
backup_root = Path("/mnt/cache/appdata/gitea-deploy-runners/state/registry-backups")
for directory in (backup_root.parent, backup_root.parent.parent):
directory_info = directory.lstat()
if not stat.S_ISDIR(directory_info.st_mode) or directory_info.st_uid != 0 or directory_info.st_mode & 0o022:
raise SystemExit(32)
backup_root.mkdir(mode=0o700, exist_ok=True)
directory_info = backup_root.lstat()
if not stat.S_ISDIR(directory_info.st_mode) or directory_info.st_uid != 0 or stat.S_IMODE(directory_info.st_mode) != 0o700:
raise SystemExit(32)
backup = backup_root / f"projects.json.appops-backup-{time.time_ns()}-{os.getpid()}"
fd = os.open(backup, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
with os.fdopen(fd, "wb") as handle:
handle.write(original)
handle.flush()
os.fsync(handle.fileno())
for directory in (backup_root.parent, backup_root):
fd = os.open(directory, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
try:
os.fsync(fd)
finally:
os.close(fd)
except Exception:
raise SystemExit(32)
@@ -210,11 +253,11 @@ with lock_handle as lock:
temporary = mutation_path.with_name(f".{mutation_path.name}.{os.getpid()}.tmp")
payload = json.dumps(updated, indent=2, sort_keys=True) + "\n"
try:
with temporary.open("w", encoding="utf-8") as handle:
descriptor = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
handle.write(payload)
handle.flush()
os.fsync(handle.fileno())
os.chmod(temporary, stat.S_IMODE(info.st_mode))
except Exception:
try:
temporary.unlink(missing_ok=True)
@@ -223,6 +266,11 @@ with lock_handle as lock:
raise SystemExit(33)
try:
latest = mutation_path.lstat()
latest_identity = (latest.st_dev, latest.st_ino, latest.st_mode, latest.st_uid,
latest.st_gid, latest.st_mtime_ns, latest.st_ctime_ns, latest.st_size)
if latest_identity != registry_identity or mutation_path.read_bytes() != original or not os.path.samefile(path, mutation_path):
raise SystemExit(34)
os.replace(temporary, mutation_path)
except Exception:
try:
@@ -242,7 +290,7 @@ with lock_handle as lock:
os.close(directory_fd)
print("registered" if changed else "already-registered", flush=True)
'''
"""
def _safe_compose_file(value: str) -> str:
@@ -314,10 +362,14 @@ class DeploymentTargetRegistry:
normalized_revision = str(revision or "").strip().lower()
normalized_branch = str(branch or "").strip()
if not _REPOSITORY.fullmatch(normalized_repository):
raise ValueError("Repository must be a canonical Jens owner/name identifier")
raise ValueError(
"Repository must be a canonical Jens owner/name identifier"
)
slug = DeploymentTargetRegistry._slug(normalized_repository)
if not _SLUG.fullmatch(slug):
raise ValueError("Repository name cannot be used as a bounded deployment slug")
raise ValueError(
"Repository name cannot be used as a bounded deployment slug"
)
if not _REVISION.fullmatch(normalized_revision):
raise ValueError("Revision must be a full lowercase 40-character Git SHA")
if (
@@ -328,15 +380,25 @@ class DeploymentTargetRegistry:
):
raise ValueError("Default branch is outside the bounded Git ref contract")
normalized_compose_file = _safe_compose_file(compose_file)
normalized_project = str(
compose_project
or DeploymentTargetRegistry.default_compose_project(normalized_repository)
).strip().lower()
normalized_project = (
str(
compose_project
or DeploymentTargetRegistry.default_compose_project(
normalized_repository
)
)
.strip()
.lower()
)
if not _COMPOSE_PROJECT.fullmatch(normalized_project):
raise ValueError("Compose project is outside the bounded naming contract")
normalized_containers = [str(item or "").strip() for item in (containers or [slug])]
normalized_containers = [
str(item or "").strip() for item in (containers or [slug])
]
if not normalized_containers or len(normalized_containers) > _MAX_CONTAINERS:
raise ValueError("Deployment target must contain between 1 and 32 containers")
raise ValueError(
"Deployment target must contain between 1 and 32 containers"
)
if any(not _CONTAINER.fullmatch(item) for item in normalized_containers):
raise ValueError("Container name is outside the bounded naming contract")
if len(set(normalized_containers)) != len(normalized_containers):
@@ -396,7 +458,10 @@ class DeploymentTargetRegistry:
),
}
controller = dict(status.get("container") or {})
if controller.get("name") != _CONTROLLER or controller.get("state") != "running":
if (
controller.get("name") != _CONTROLLER
or controller.get("state") != "running"
):
return {
"status": "controller_unavailable",
"reason": "deployment controller is not running",