From a0d1a1224a268d05d0e67d2e4a6547b4b4cb2308 Mon Sep 17 00:00:00 2001 From: Public Source Publisher Date: Fri, 9 Oct 2026 18:15:18 +0000 Subject: [PATCH] Publish curated Unraid-AppOps-Gateway source from 0f89e1506065c5365c48968b1d7845d666a18be8 --- PUBLIC_SOURCE_EXPORT.md | 2 +- PUBLIC_SOURCE_MANIFEST.json | 20 ++-- .../controller_artifact_helper_spec.py | 2 +- .../test_controller_artifact_bootstrap.py | 20 ++-- .../test_controller_artifact_promoter.py | 6 +- .../test_controller_hub_release_volume.py | 8 +- backend/tests/test_runner_fleet_specialize.py | 111 ++++++++++++++++++ .../tests/test_runner_resource_envelope.py | 14 ++- 8 files changed, 152 insertions(+), 31 deletions(-) diff --git a/PUBLIC_SOURCE_EXPORT.md b/PUBLIC_SOURCE_EXPORT.md index 560cf5e..ccb0391 100644 --- a/PUBLIC_SOURCE_EXPORT.md +++ b/PUBLIC_SOURCE_EXPORT.md @@ -1,3 +1,3 @@ # Public source export -This source snapshot was generated from the private canonical repository at revision `25057d17d4995d4bd407ff0516ef69ddaec878c6`. It contains no private Git history or operational evidence. Changes are published from the canonical repository. +This source snapshot was generated from the private canonical repository at revision `0f89e1506065c5365c48968b1d7845d666a18be8`. It contains no private Git history or operational evidence. Changes are published from the canonical repository. diff --git a/PUBLIC_SOURCE_MANIFEST.json b/PUBLIC_SOURCE_MANIFEST.json index fb92ec4..9f5cfb1 100644 --- a/PUBLIC_SOURCE_MANIFEST.json +++ b/PUBLIC_SOURCE_MANIFEST.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "sourceRevision": "25057d17d4995d4bd407ff0516ef69ddaec878c6", + "sourceRevision": "0f89e1506065c5365c48968b1d7845d666a18be8", "files": [ { "path": ".dockerignore", @@ -524,7 +524,7 @@ }, { "path": "backend/app/services/controller_artifact_helper_spec.py", - "sha256": "232048b69a06e5fe4edc1c5fab27c3032bdd829bbbe9589b7d47b1a288207ca8", + "sha256": "e345e51a81eb483ffe8532592deb197296bd28db99e1e5bc358c590a59f1b94d", "bytes": 6029 }, { @@ -949,7 +949,7 @@ }, { "path": "backend/tests/test_controller_artifact_bootstrap.py", - "sha256": "46ceb18a80b1f413da98325bea69c80422057354fb54569cc5563fccfffdda64", + "sha256": "6774b7fdf9fd91c463661970512b93027af93979fd4b6d61d89476f0465cc48b", "bytes": 82644 }, { @@ -964,7 +964,7 @@ }, { "path": "backend/tests/test_controller_artifact_promoter.py", - "sha256": "284389dc63da8305927e80bf4ed4120e1acac6b0eb86b2b29f5cbbc360fa3778", + "sha256": "1a274fb06503ce5568e2096898e1a98ded10dee333f49bd45060bbc82adf00cb", "bytes": 7570 }, { @@ -989,8 +989,8 @@ }, { "path": "backend/tests/test_controller_hub_release_volume.py", - "sha256": "e13f1f01799fe74821eab9622d7eb4cf9add65196a67bde386f1c11231e04c5d", - "bytes": 161342 + "sha256": "c3de3b47673a5fa365bf20aedc020a2ca733804adc9cd05d15b26aca2a1a2b05", + "bytes": 161657 }, { "path": "backend/tests/test_controller_root_procedure_contract.py", @@ -1624,8 +1624,8 @@ }, { "path": "backend/tests/test_runner_fleet_specialize.py", - "sha256": "9de45904f4a4fd254d5e3b4c502b3d2c235f2ce220fd8f122a5ed840388b2eb1", - "bytes": 113743 + "sha256": "6771e7ae22ddb0d39e19272d311f9c77b898273c7106eef02ba0c76fc6e48873", + "bytes": 119270 }, { "path": "backend/tests/test_runner_fleet_v2_execution_surface.py", @@ -1639,8 +1639,8 @@ }, { "path": "backend/tests/test_runner_resource_envelope.py", - "sha256": "a0e469bc980dbec76cedda9a1ae5011fc1444d49ffed4c067268a10f1905a5a1", - "bytes": 40504 + "sha256": "9bb731508ac3cc4d36ba5eae7839c0f73b411880b92a45118dfaa4b62c4e019f", + "bytes": 40972 }, { "path": "backend/tests/test_runner_scheduler_liveness_recovery.py", diff --git a/backend/app/services/controller_artifact_helper_spec.py b/backend/app/services/controller_artifact_helper_spec.py index 1efa6f7..b3599f7 100644 --- a/backend/app/services/controller_artifact_helper_spec.py +++ b/backend/app/services/controller_artifact_helper_spec.py @@ -5,7 +5,7 @@ from typing import Any SOURCE_REPOSITORY = "jens/unraid-appops-gateway" HELPER_NAME = "itworx-controller-artifact-promoter-modern" -CONTROLLER_VERSION = "2.1.18" +CONTROLLER_VERSION = "2.1.19" HELPER_LABEL = "itworx.controller.promoter" _REVISION = re.compile(r"^[0-9a-f]{40}$") _IMAGE_ID = re.compile(r"^sha256:[0-9a-f]{64}$") diff --git a/backend/tests/test_controller_artifact_bootstrap.py b/backend/tests/test_controller_artifact_bootstrap.py index 9a3fed3..35d8ef1 100644 --- a/backend/tests/test_controller_artifact_bootstrap.py +++ b/backend/tests/test_controller_artifact_bootstrap.py @@ -589,7 +589,7 @@ def _health( *, revision: str = REVISION, tree: str = TREE, - version: str = "2.1.18", + version: str = "2.1.19", artifact_sha256: str = "1" * 64, ) -> dict[str, Any]: return { @@ -1347,7 +1347,7 @@ def test_current_214_baseline_is_verified_and_bound_to_new_helper(): "itworx.controller.promoter": "true", "itworx.source.repository": "jens/unraid-appops-gateway", "itworx.source.revision": REVISION, - "itworx.controller.version": "2.1.18", + "itworx.controller.version": "2.1.19", "itworx.controller.bootstrap.target.id": MODERN_CONTROLLER_CONTAINER_ID, "itworx.controller.bootstrap.target.image": CONTROLLER_IMAGE_ID, "itworx.controller.bootstrap.target.revision": MODERN_CONTROLLER_REVISION, @@ -1826,7 +1826,7 @@ def test_successful_child_requires_exact_source_tree_version_and_health(): assert result["status"] == "promoted" assert result["revision"] == REVISION - assert result["controllerVersion"] == "2.1.18" + assert result["controllerVersion"] == "2.1.19" assert result["artifactSha256"] == "1" * 64 assert docker.started == [MODERN_HELPER_ID] assert docker.created[0]["Image"] == CONTROLLER_IMAGE_ID @@ -2086,32 +2086,32 @@ def test_modern_upgrade_path_still_requires_private_archive_and_image_id( def test_current_helper_version_is_distinct_from_fixed_controller_baselines(): source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_bytes() namespace = bootstrap._safe_modern_spec_namespace(source) - assert namespace["CONTROLLER_VERSION"] == "2.1.18" - assert bootstrap._verified_historical_helper_version(source) == "2.1.18" + assert namespace["CONTROLLER_VERSION"] == "2.1.19" + assert bootstrap._verified_historical_helper_version(source) == "2.1.19" assert [baseline.version for baseline in bootstrap.CONTROLLER_BASELINES] == ["2.1.12", "2.1.14"] with pytest.raises(bootstrap.BootstrapError, match="controller_promoter_modern_spec_identity_invalid"): bootstrap._safe_modern_spec_namespace(RETIRED_MODERN_HELPER_SOURCE.encode("utf-8")) assert bootstrap._verified_historical_helper_version(RETIRED_MODERN_HELPER_SOURCE.encode("utf-8")) == "2.1.14" -@pytest.mark.parametrize("version", ["2.1.16", "2.1.19", "9.9.9"]) +@pytest.mark.parametrize("version", ["2.1.16", "2.1.20", "9.9.9"]) def test_current_and_historical_helper_reject_unreviewed_target_versions(version): source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_bytes() - foreign = source.replace(b'CONTROLLER_VERSION = "2.1.18"', f'CONTROLLER_VERSION = "{version}"'.encode()) + foreign = source.replace(b'CONTROLLER_VERSION = "2.1.19"', f'CONTROLLER_VERSION = "{version}"'.encode()) with pytest.raises(bootstrap.BootstrapError, match="controller_promoter_modern_spec_identity_invalid"): bootstrap._safe_modern_spec_namespace(foreign) with pytest.raises(bootstrap.BootstrapError, match="controller_promoter_modern_spec_identity_invalid"): bootstrap._verified_historical_helper_version(foreign) -@pytest.mark.parametrize("version", ["2.1.14", "2.1.16", "2.1.19"]) +@pytest.mark.parametrize("version", ["2.1.14", "2.1.16", "2.1.20"]) def test_promoted_health_cannot_substitute_a_baseline_or_unknown_version(version): assert not bootstrap._health_matches(_health(version=version), REVISION, TREE) def test_previous_reviewed_helper_is_historical_only(): source = (ROOT / "backend/app/services/controller_artifact_helper_spec.py").read_bytes() - prior = source.replace(b'CONTROLLER_VERSION = "2.1.18"', b'CONTROLLER_VERSION = "2.1.17"') + prior = source.replace(b'CONTROLLER_VERSION = "2.1.19"', b'CONTROLLER_VERSION = "2.1.18"') with pytest.raises(bootstrap.BootstrapError, match="controller_promoter_modern_spec_identity_invalid"): bootstrap._safe_modern_spec_namespace(prior) - assert bootstrap._verified_historical_helper_version(prior) == "2.1.17" + assert bootstrap._verified_historical_helper_version(prior) == "2.1.18" diff --git a/backend/tests/test_controller_artifact_promoter.py b/backend/tests/test_controller_artifact_promoter.py index a3de29b..4d4a05b 100644 --- a/backend/tests/test_controller_artifact_promoter.py +++ b/backend/tests/test_controller_artifact_promoter.py @@ -46,7 +46,7 @@ def test_controller_promoter_helper_spec_is_fixed_and_bounded(): spec = helper_container_spec(revision=revision, image_id=image_id) assert HELPER_NAME == "itworx-controller-artifact-promoter-modern" - assert CONTROLLER_VERSION == "2.1.18" + assert CONTROLLER_VERSION == "2.1.19" assert re.fullmatch(r"2\.1\.\d+", CONTROLLER_VERSION) assert spec["Image"] == image_id assert spec["HostConfig"]["Privileged"] is False @@ -79,7 +79,7 @@ def test_controller_promoter_helper_spec_is_fixed_and_bounded(): assert revision in command assert "--image-id" in command assert CONTROLLER_VERSION in command - assert "version='2.1.18'" in command + assert "version='2.1.19'" in command assert "/gitea-repositories/jens/unraid-appops-gateway.git" in command archive_start = command.index('git --git-dir="$repo" archive') archive_end = command.index('> "$work/controller.tar"', archive_start) @@ -110,7 +110,7 @@ def test_controller_promoter_bootstrap_child_spec_binds_legacy_target_identity() "itworx.controller.promoter": "true", "itworx.source.repository": "jens/unraid-appops-gateway", "itworx.source.revision": revision, - "itworx.controller.version": "2.1.18", + "itworx.controller.version": "2.1.19", "itworx.controller.bootstrap.target.id": target_id, "itworx.controller.bootstrap.target.image": image_id, "itworx.controller.bootstrap.target.revision": target_revision, diff --git a/backend/tests/test_controller_hub_release_volume.py b/backend/tests/test_controller_hub_release_volume.py index e3fa09e..ccea065 100644 --- a/backend/tests/test_controller_hub_release_volume.py +++ b/backend/tests/test_controller_hub_release_volume.py @@ -34,8 +34,9 @@ def compose_config_root(monkeypatch, tmp_path): @pytest.mark.parametrize("operation", ["config", "stop", "up"]) +@pytest.mark.parametrize("source_directory", [False, True]) def test_compose_uses_private_ephemeral_cli_config_with_readonly_root( - monkeypatch, tmp_path, operation, + monkeypatch, tmp_path, operation, source_directory, ): directories = [] @@ -57,12 +58,15 @@ def test_compose_uses_private_ephemeral_cli_config_with_readonly_root( assert arguments[-1] == operation assert arguments[arguments.index("--env-file") + 1] == "/dev/null" assert arguments[arguments.index("-f") + 1] == "-" + assert ("--profile" in arguments) is (source_directory and operation == "config") + if "--profile" in arguments: + assert arguments[arguments.index("--profile") + 1] == "runner" (directory / "generated-client-state").write_bytes(b"private-client-state") return b"ok" monkeypatch.setattr(module, "_run", run) for _ in range(2): - assert module._compose([operation], b"captured-private-compose") == b"ok" + assert module._compose([operation], b"captured-private-compose", source_directory=source_directory) == b"ok" assert directories[0] != directories[1] assert all(not directory.exists() for directory in directories) diff --git a/backend/tests/test_runner_fleet_specialize.py b/backend/tests/test_runner_fleet_specialize.py index 527948b..c2952a7 100644 --- a/backend/tests/test_runner_fleet_specialize.py +++ b/backend/tests/test_runner_fleet_specialize.py @@ -13,6 +13,113 @@ from pathlib import Path import pytest + +@pytest.fixture +def controller_only_source(specialize, tmp_path, monkeypatch): + image = "sha256:" + "a" * 64 + code = f"{specialize.RELEASE_VOLUME_HOST_ROOT}/controller-artifacts/{'b' * 40}" + model = {"name": specialize.PROJECT, "services": {"deploy-control": { + "container_name": specialize.CONTROLLER_CONTAINER, "image": image, + "volumes": [{"type": "bind", "source": code, "target": "/opt/gitea-deploy", + "read_only": True, "bind": {"propagation": "rslave"}}], + }}} + compose = tmp_path / "docker-compose.yml" + archive_dir = tmp_path / "backups" / "controller-v2-fixed" + archive_dir.mkdir(parents=True, mode=0o700) + archive_dir.parent.chmod(0o700) + archive = archive_dir / "compose-before.bin" + archived = b"reviewed complete legacy v1 source" + archive.write_bytes(archived) + archive.chmod(0o600) + monkeypatch.setattr(specialize, "ROOT", tmp_path) + monkeypatch.setattr(specialize, "COMPOSE_PATH", compose) + monkeypatch.setattr(specialize, "LEGACY_V1_COMPOSE_BACKUP", archive) + monkeypatch.setattr(specialize, "_KNOWN_V1_COMPOSE_SHA256", hashlib.sha256(archived).hexdigest()) + monkeypatch.setattr(specialize, "_controller_runtime_identity", lambda: (image, code)) + return model, compose, archive, archived + + +def test_controller_only_source_uses_fixed_pinned_archive_without_rewriting_live_compose( + specialize, controller_only_source, +): + model, compose, _archive, archived = controller_only_source + current = json.dumps(model).encode() + compose.write_bytes(current) + result, current_sha = specialize._read_fleet_base_compose() + assert result == archived + assert current_sha == hashlib.sha256(current).hexdigest() + assert compose.read_bytes() == current + + +@pytest.mark.parametrize("mutation", ["project", "image", "container", "code", "writable", "shared", "extra_code"]) +def test_controller_only_source_rejects_identity_and_code_binding_drift( + specialize, controller_only_source, mutation, +): + model, compose, _archive, _archived = controller_only_source + controller = model["services"]["deploy-control"] + mount = controller["volumes"][0] + if mutation == "project": + model["name"] = "other" + elif mutation == "image": + controller["image"] = "sha256:" + "c" * 64 + elif mutation == "container": + controller["container_name"] = "other" + elif mutation == "code": + mount["source"] = "/mnt/shared/code" + elif mutation == "writable": + mount["read_only"] = False + elif mutation == "shared": + mount["bind"]["propagation"] = "rshared" + else: + controller["volumes"].append(copy.deepcopy(mount)) + compose.write_text(json.dumps(model)) + with pytest.raises(specialize.RunnerFleetSpecializeError, match="controller_compose_untrusted"): + specialize._read_fleet_base_compose() + + +def test_controller_only_source_rejects_modified_fixed_archive(specialize, controller_only_source): + model, compose, archive, _archived = controller_only_source + compose.write_text(json.dumps(model)) + archive.write_bytes(b"different legacy source") + with pytest.raises(specialize.RunnerFleetSpecializeError, match="legacy_backup_changed"): + specialize._read_fleet_base_compose() + + +def test_legacy_live_source_does_not_open_promotion_backup(specialize, monkeypatch): + current = b"services:\n runner: {}\n" + monkeypatch.setattr(specialize, "_read_source_file", lambda *_args, **_kwargs: current) + monkeypatch.setattr(specialize, "_trusted_private_file", lambda *_args, **_kwargs: pytest.fail("archive must remain unopened")) + assert specialize._read_fleet_base_compose() == (current, None) + + +@pytest.mark.parametrize("separate_controller_hash", [False, True]) +def test_controller_compose_cas_blocks_rollback_before_any_lifecycle_action( + specialize, controller_only_source, monkeypatch, separate_controller_hash, +): + model, compose, _archive, _archived = controller_only_source + compose.write_text(json.dumps(model)) + original_sha = hashlib.sha256(compose.read_bytes()).hexdigest() + journal = {"baseComposeSha256": original_sha} + if separate_controller_hash: + journal["controllerComposeSha256"] = original_sha + compose.write_bytes(compose.read_bytes() + b"\n") + monkeypatch.setattr(specialize, "_guard_state_unchanged", lambda *_args: pytest.fail("must reject before lifecycle")) + assert specialize._rollback_before_activation(journal) is False + + +@pytest.mark.parametrize("reviewed,user,image,expected", [ + (True, None, True, "rootless"), (False, None, True, None), + (True, "root", True, "root"), (True, None, False, None), +]) +def test_only_byte_pinned_v1_pinned_image_default_user_is_normalized( + specialize, monkeypatch, reviewed, user, image, expected, +): + source = b"reviewed v1 source" if reviewed else b"different source" + monkeypatch.setattr(specialize, "_KNOWN_V1_COMPOSE_SHA256", hashlib.sha256(b"reviewed v1 source").hexdigest()) + model = {"services": {"runner-dind": {"image": specialize._DIND_IMAGE_REFERENCE if image else "unknown", "user": user}}} + monkeypatch.setattr(specialize, "_run_compose", lambda *_args: json.dumps(model)) + assert specialize._compose_model(source)["services"]["runner-dind"]["user"] == expected + ROOT = Path(__file__).resolve().parents[2] BROKER = ROOT / "ops" / "gitea-deploy-broker" SPECIALIZE_PATH = BROKER / "runner_fleet_specialize.py" @@ -2145,6 +2252,10 @@ def test_migration_failure_restores_captured_dind_state_before_cgroup_cleanup( base_bytes = json.dumps(base_model, sort_keys=True, separators=(",", ":")).encode() specialized_bytes = json.dumps(specialized_model, sort_keys=True, separators=(",", ":")).encode() + live_compose = tmp_path / "docker-compose.yml" + live_compose.write_bytes(base_bytes) + monkeypatch.setattr(specialize, "COMPOSE_PATH", live_compose) + journal["baseComposeSha256"] = hashlib.sha256(base_bytes).hexdigest() monkeypatch.setattr(specialize, "_guard_state_unchanged", lambda _journal: True) monkeypatch.setattr( specialize, diff --git a/backend/tests/test_runner_resource_envelope.py b/backend/tests/test_runner_resource_envelope.py index 14c512a..87d2e11 100644 --- a/backend/tests/test_runner_resource_envelope.py +++ b/backend/tests/test_runner_resource_envelope.py @@ -478,7 +478,7 @@ def test_host_probe_contract_is_bounded_and_never_mounts_host_proc_or_docker_soc assert "target=/host-cgroup" in mount assert "readonly" in mount assert mounts[1] == ( - f"type=bind,source={code_source},target=/opt/gitea-deploy,readonly,bind-propagation=rprivate" + f"type=bind,source={code_source},target=/opt/gitea-deploy,readonly,bind-propagation=rslave" ) assert not any("/proc" in item or "docker.sock" in item for item in args) assert args[-3:] == [image_id, "/opt/gitea-deploy/runner_resource_envelope.py", "--host-probe"] @@ -528,7 +528,7 @@ def test_host_probe_inspection_rejects_namespace_mount_and_resource_drift(): "Source": code_source, "Target": "/opt/gitea-deploy", "ReadOnly": True, - "BindOptions": {"Propagation": "rprivate"}, + "BindOptions": {"Propagation": "rslave"}, }, ], }, @@ -547,7 +547,7 @@ def test_host_probe_inspection_rejects_namespace_mount_and_resource_drift(): "Destination": "/opt/gitea-deploy", "Mode": "", "RW": False, - "Propagation": "rprivate", + "Propagation": "rslave", }, ], } @@ -585,6 +585,12 @@ def test_host_probe_inspection_rejects_namespace_mount_and_resource_drift(): ), lambda item: item["Mounts"][0].update(RW=True), lambda item: item["Mounts"][1].update(RW=True), + lambda item: item["Mounts"][0].update(Propagation="rslave"), + lambda item: item["Mounts"][1].update(Propagation="rprivate"), + lambda item: item["Mounts"][1].update(Propagation="rshared"), + lambda item: item["Mounts"][1].update(Propagation="slave"), + lambda item: item["HostConfig"]["Mounts"][0]["BindOptions"].update(Propagation="rslave"), + lambda item: item["HostConfig"]["Mounts"][1]["BindOptions"].update(Propagation="rprivate"), lambda item: item["Mounts"][0].update(Mode="ro"), lambda item: item["Mounts"][1].update(Mode="ro"), lambda item: item["Mounts"][1].update(Source="/mnt/shared/code"), @@ -734,7 +740,7 @@ def test_host_probe_rw_modes_are_fixed_and_limited_to_ensure_or_rollback(): ) assert all( code_mount - == f"type=bind,source={code_source},target=/opt/gitea-deploy,readonly,bind-propagation=rprivate" + == f"type=bind,source={code_source},target=/opt/gitea-deploy,readonly,bind-propagation=rslave" for code_mount in (snapshot_code_mount, ensure_code_mount, rollback_code_mount, family_code_mount) ) for args in (snapshot, ensure, rollback, family):