mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-10-08 22:46:41 +00:00
fix(oci): add nginx runtime contract for Stremio
This commit is contained in:
@@ -797,8 +797,9 @@ class Catalog:
|
||||
item.update(environment_overrides[item["name"]])
|
||||
from .stack import apply_stack_support
|
||||
apply_stack_support(template)
|
||||
from .gpu import apply_gpu_contract
|
||||
from .gpu import apply_gpu_contract, apply_nginx_runtime_contract
|
||||
apply_gpu_contract(template)
|
||||
apply_nginx_runtime_contract(template)
|
||||
# Last, so the stack compiler does not reset it.
|
||||
self._apply_verification(app_id, template)
|
||||
|
||||
|
||||
@@ -8,6 +8,32 @@ from .i18n import translate
|
||||
|
||||
LSIO_DEVICE_INIT = {"boinc", "emby", "jellyfin", "plex", "tvheadend"}
|
||||
|
||||
# Images that run their own bare nginx (no s6-overlay/LinuxServer init and no
|
||||
# Selkies profile) to serve their web UI. LXC's volatile /run hides the
|
||||
# /run/nginx directory shipped in the OCI rootfs, so nginx fails to start
|
||||
# with "open() '/run/nginx/nginx.pid' failed (2: No such file or directory)".
|
||||
# Add repositories here as they are discovered; see apply_selkies_contract
|
||||
# and the linuxserver/libreoffice case in converter.py for the same fix
|
||||
# applied through other code paths.
|
||||
BARE_NGINX_RUNTIME_INIT = {"tsaridas/stremio-docker"}
|
||||
|
||||
|
||||
def apply_nginx_runtime_contract(template: dict[str, Any]) -> None:
|
||||
"""Ensure /run/nginx exists for images with a bare nginx that does not
|
||||
create it itself (see BARE_NGINX_RUNTIME_INIT)."""
|
||||
profile = template.get("proxmox", {}).get("installer_profile", {})
|
||||
if profile.get("selkies"):
|
||||
return # already handled by apply_selkies_contract
|
||||
image = template.get("container_contract", {}).get("image", {}).get("reference", "")
|
||||
repository = image.split("@", 1)[0].split(":", 1)[0]
|
||||
if repository not in BARE_NGINX_RUNTIME_INIT:
|
||||
return
|
||||
mounts = profile.setdefault("tmpfs_mounts", [])
|
||||
if not any(m.get("container_path") == "/run/nginx" for m in mounts):
|
||||
mounts.append({"id": "nginx-runtime", "container_path": "/run/nginx",
|
||||
"default_size_mb": 1, "minimum_size_mb": 1, "prompt_size": False,
|
||||
"mount_options": ["rw", "nosuid", "nodev", "mode=0755"]})
|
||||
|
||||
|
||||
def apply_gpu_contract(template: dict[str, Any]) -> None:
|
||||
profile = template.get("proxmox", {}).get("installer_profile", {})
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
"""Regression tests for apply_nginx_runtime_contract (BARE_NGINX_RUNTIME_INIT).
|
||||
|
||||
Covers the Stremio /run/nginx fix: images that run a bare nginx without an
|
||||
s6-overlay/LinuxServer init or a Selkies profile need /run/nginx recreated
|
||||
as a tmpfs mount, because LXC's volatile /run hides the directory shipped in
|
||||
the OCI rootfs (nginx: [emerg] open() "/run/nginx/nginx.pid" failed).
|
||||
"""
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
|
||||
from proxmenux_oci.catalog import Catalog
|
||||
from proxmenux_oci.gpu import apply_nginx_runtime_contract
|
||||
|
||||
|
||||
class BareNginxRuntimeContractTests(unittest.TestCase):
|
||||
def test_bare_nginx_app_gets_run_mount(self):
|
||||
"""Stremio (curated, tsaridas/stremio-docker) gets exactly one
|
||||
/run/nginx tmpfs mount in proxmox.installer_profile.tmpfs_mounts."""
|
||||
template = Catalog(ROOT).compose("stremio")
|
||||
|
||||
mounts = template["proxmox"]["installer_profile"]["tmpfs_mounts"]
|
||||
nginx_mounts = [m for m in mounts if m.get("container_path") == "/run/nginx"]
|
||||
self.assertEqual(len(nginx_mounts), 1)
|
||||
self.assertEqual(nginx_mounts[0]["id"], "nginx-runtime")
|
||||
|
||||
def test_non_target_app_unaffected(self):
|
||||
"""Jellyfin Official (curated, image jellyfin/jellyfin — not in
|
||||
BARE_NGINX_RUNTIME_INIT) must not get a new tmpfs mount."""
|
||||
template = Catalog(ROOT).compose("jellyfin-official")
|
||||
|
||||
profile = template["proxmox"]["installer_profile"]
|
||||
self.assertNotIn("tmpfs_mounts", profile)
|
||||
|
||||
def test_contract_is_idempotent_on_stremio_template(self):
|
||||
"""Re-applying apply_nginx_runtime_contract() a second time on top
|
||||
of an already-processed Stremio template (as compose() would do if
|
||||
called again, or if the fix runs more than once in a pipeline) must
|
||||
not duplicate the mount — exactly one /run/nginx entry survives.
|
||||
This does not simulate a hand-authored /run/nginx entry or 2FAuth's
|
||||
own pre-existing catalog mount; it only covers repeated application
|
||||
of this specific contract function on its own prior output."""
|
||||
template = Catalog(ROOT).compose("stremio")
|
||||
|
||||
# Call the contract function again on its own already-processed
|
||||
# output, simulating repeated application in a pipeline.
|
||||
apply_nginx_runtime_contract(template)
|
||||
apply_nginx_runtime_contract(template)
|
||||
|
||||
mounts = template["proxmox"]["installer_profile"]["tmpfs_mounts"]
|
||||
nginx_mounts = [m for m in mounts if m.get("container_path") == "/run/nginx"]
|
||||
self.assertEqual(len(nginx_mounts), 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user