From e73772cdf2fc71caf806ac5d8dbd06bf6d3e2418 Mon Sep 17 00:00:00 2001 From: Public Source Publisher Date: Sun, 4 Oct 2026 07:14:28 +0000 Subject: [PATCH] Publish curated Unraid-AppOps-Gateway source from 7144ea3c4cb735745046e4e1c2d3a3e88fa1a302 --- PUBLIC_SOURCE_EXPORT.md | 2 +- PUBLIC_SOURCE_MANIFEST.json | 10 +- .../test_controller_artifact_bootstrap.py | 46 +++ .../test_controller_hub_release_volume.py | 271 ++++++++++++++++++ 4 files changed, 323 insertions(+), 6 deletions(-) diff --git a/PUBLIC_SOURCE_EXPORT.md b/PUBLIC_SOURCE_EXPORT.md index 082057a..d690733 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 `451646fee806a47e3256731766980a05a262dcdd`. 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 `7144ea3c4cb735745046e4e1c2d3a3e88fa1a302`. 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 a26fd79..14d1e47 100644 --- a/PUBLIC_SOURCE_MANIFEST.json +++ b/PUBLIC_SOURCE_MANIFEST.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "sourceRevision": "451646fee806a47e3256731766980a05a262dcdd", + "sourceRevision": "7144ea3c4cb735745046e4e1c2d3a3e88fa1a302", "files": [ { "path": ".dockerignore", @@ -889,8 +889,8 @@ }, { "path": "backend/tests/test_controller_artifact_bootstrap.py", - "sha256": "2694d9e601bb06aaabc9491c85fd7a17e796f20ea089792cb964c4a6fe93aaf6", - "bytes": 60349 + "sha256": "da2ff9e9ec07c15f2fed076d0d7c76017496252c5584a8383d6c3132bdbaf25f", + "bytes": 62434 }, { "path": "backend/tests/test_controller_artifact_promoter.py", @@ -914,8 +914,8 @@ }, { "path": "backend/tests/test_controller_hub_release_volume.py", - "sha256": "2b89660fe44b8c5a46a4cad6b9cb68ac34f0716b4e72ac171830dcf61b0ac2c8", - "bytes": 73499 + "sha256": "c6f8304f8f3fbbf42900c07a5b01fb40edeb3f6657849b55f9bf4fd59cdb79f6", + "bytes": 84733 }, { "path": "backend/tests/test_controller_root_procedure_contract.py", diff --git a/backend/tests/test_controller_artifact_bootstrap.py b/backend/tests/test_controller_artifact_bootstrap.py index 98828cb..482fde5 100644 --- a/backend/tests/test_controller_artifact_bootstrap.py +++ b/backend/tests/test_controller_artifact_bootstrap.py @@ -236,6 +236,14 @@ def _modern_controller_mounts(source_revision: str) -> list[dict[str, Any]]: "RW": False, "Propagation": "rprivate", }, + { + "Type": "volume", + "Name": "itworx-forge-master-key-backup", + "Source": "/var/lib/docker/volumes/itworx-forge-master-key-backup/_data", + "Destination": "/forge-master-key-backup", + "RW": True, + "Propagation": "", + }, ] @@ -1244,6 +1252,16 @@ def test_completed_child_uses_new_health_after_controller_recreation(): "post-volume-mode", "post-config-bind-source", "post-config-bind-mode", + "post-code-bind-propagation", + "post-config-bind-propagation", + "post-missing-backup-volume", + "post-backup-volume-type", + "post-backup-volume-name", + "post-backup-volume-source", + "post-backup-volume-target", + "post-backup-volume-mode", + "post-backup-volume-propagation", + "post-duplicate-backup-volume", "post-extra-mount", ], ) @@ -1281,6 +1299,26 @@ def test_postactivation_controller_requires_exact_compose_runtime(mutation: str) controller["Mounts"][9]["Source"] = "/tmp/config" elif mutation == "post-config-bind-mode": controller["Mounts"][9]["RW"] = True + elif mutation == "post-code-bind-propagation": + controller["Mounts"][3]["Propagation"] = "rslave" + elif mutation == "post-config-bind-propagation": + controller["Mounts"][9]["Propagation"] = "shared" + elif mutation == "post-missing-backup-volume": + controller["Mounts"].pop(10) + elif mutation == "post-backup-volume-type": + controller["Mounts"][10]["Type"] = "bind" + elif mutation == "post-backup-volume-name": + controller["Mounts"][10]["Name"] = "foreign-volume" + elif mutation == "post-backup-volume-source": + controller["Mounts"][10]["Source"] = "/tmp/foreign-volume" + elif mutation == "post-backup-volume-target": + controller["Mounts"][10]["Destination"] = "/tmp/backup" + elif mutation == "post-backup-volume-mode": + controller["Mounts"][10]["RW"] = False + elif mutation == "post-backup-volume-propagation": + controller["Mounts"][10]["Propagation"] = "rprivate" + elif mutation == "post-duplicate-backup-volume": + controller["Mounts"].append(dict(controller["Mounts"][10])) elif mutation == "post-extra-mount": controller["Mounts"].append(dict(controller["Mounts"][0])) @@ -1296,6 +1334,14 @@ def test_postactivation_accepts_well_formed_compose_hash(): assert bootstrap._verified_live_controller(docker, source_revision=REVISION) +def test_postactivation_accepts_exact_forge_backup_mount_in_eleven_mount_inventory(): + docker = FakeDocker(_legacy_helper_spec()) + controller = _modern_controller_inspect(REVISION) + docker.containers["gitea-deploy-control"] = controller + + assert bootstrap._verified_live_controller(docker, source_revision=REVISION) + + def test_successful_child_requires_exact_source_tree_version_and_health(): docker = FakeDocker(_legacy_helper_spec()) git = FakeProtectedGit("") diff --git a/backend/tests/test_controller_hub_release_volume.py b/backend/tests/test_controller_hub_release_volume.py index 1a94df1..bdcaff3 100644 --- a/backend/tests/test_controller_hub_release_volume.py +++ b/backend/tests/test_controller_hub_release_volume.py @@ -1,6 +1,7 @@ from __future__ import annotations import ast +import hashlib import importlib.util import io import json @@ -443,6 +444,7 @@ def test_compose_uses_protected_code_and_two_config_views(compose): "source": module._expected_revision_path(REVISION).as_posix(), "target": module.EXECUTABLE_TARGET, "read_only": True, + "bind": {"propagation": "rprivate"}, } in volumes assert { "type": "volume", @@ -461,6 +463,7 @@ def test_compose_uses_protected_code_and_two_config_views(compose): "source": module._expected_config_path().as_posix(), "target": module.CONFIG_TARGET, "read_only": True, + "bind": {"propagation": "rprivate"}, } in volumes assert len(volumes) == 5 volumes.remove(next(item for item in volumes if item["target"] == module.EXECUTABLE_TARGET)) @@ -487,6 +490,150 @@ def test_compose_uses_protected_code_and_two_config_views(compose): assert calls[0][1] == ORIGINAL_COMPOSE +def test_compose_migrates_legacy_config_alias_to_explicit_private_bind(compose): + baseline, _calls = compose + baseline["services"][module.SERVICE]["volumes"].append( + { + "type": "bind", + "source": module._expected_config_path().as_posix(), + "target": module.CONFIG_TARGET, + "read_only": True, + } + ) + + rendered = json.loads(module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID)) + aliases = [ + mount for mount in rendered["services"][module.SERVICE]["volumes"] + if mount.get("target") == module.CONFIG_TARGET + ] + assert aliases == [ + { + "type": "bind", + "source": module._expected_config_path().as_posix(), + "target": module.CONFIG_TARGET, + "read_only": True, + "bind": {"propagation": "rprivate"}, + } + ] + + +def test_compose_accepts_empty_bind_normalization_and_canonicalizes_both_managed_binds(compose): + baseline, _calls = compose + volumes = baseline["services"][module.SERVICE]["volumes"] + next(item for item in volumes if item["target"] == module.EXECUTABLE_TARGET)["bind"] = {} + volumes.append( + { + "type": "bind", + "source": module._expected_config_path().as_posix(), + "target": module.CONFIG_TARGET, + "read_only": True, + "bind": {}, + } + ) + + rendered = json.loads(module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID)) + managed = [ + mount for mount in rendered["services"][module.SERVICE]["volumes"] + if mount.get("target") in {module.EXECUTABLE_TARGET, module.CONFIG_TARGET} + ] + assert {item["target"]: item["bind"] for item in managed} == { + module.EXECUTABLE_TARGET: {"propagation": "rprivate"}, + module.CONFIG_TARGET: {"propagation": "rprivate"}, + } + + +@pytest.mark.parametrize( + ("target", "bind"), + [ + (module.EXECUTABLE_TARGET, {"propagation": "rslave"}), + (module.EXECUTABLE_TARGET, {"propagation": "shared"}), + (module.EXECUTABLE_TARGET, {"propagation": "rprivate", "create_host_path": False}), + (module.CONFIG_TARGET, {"propagation": "rslave"}), + (module.CONFIG_TARGET, {"propagation": "shared"}), + (module.CONFIG_TARGET, {"propagation": "rprivate", "create_host_path": False}), + ], +) +def test_compose_rejects_noncanonical_controller_bind_propagation(compose, target, bind): + baseline, _calls = compose + volumes = baseline["services"][module.SERVICE]["volumes"] + if target == module.EXECUTABLE_TARGET: + mount = next(item for item in volumes if item["target"] == target) + mount["bind"] = bind + else: + volumes.append( + { + "type": "bind", + "source": module._expected_config_path().as_posix(), + "target": target, + "read_only": True, + "bind": bind, + } + ) + + with pytest.raises(module.VolumeTransitionError): + module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID) + + +def test_compose_accepts_already_canonical_controller_mounts_without_duplicates(compose): + baseline, _calls = compose + volumes = baseline["services"][module.SERVICE]["volumes"] + next(item for item in volumes if item["target"] == module.EXECUTABLE_TARGET)["bind"] = { + "propagation": "rprivate" + } + volumes.extend( + [ + { + "type": "volume", + "source": module.VOLUME, + "target": module.TARGET, + "volume": {"nocopy": True}, + }, + { + "type": "volume", + "source": module.BACKUP_VOLUME, + "target": module.BACKUP_TARGET, + "volume": {"nocopy": True}, + }, + { + "type": "bind", + "source": module._expected_config_path().as_posix(), + "target": module.CONFIG_TARGET, + "read_only": True, + "bind": {"propagation": "rprivate"}, + }, + ] + ) + baseline["volumes"] = { + module.VOLUME: {"name": module.VOLUME, "external": True}, + module.BACKUP_VOLUME: {"name": module.BACKUP_VOLUME, "external": True}, + } + + rendered = json.loads(module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID)) + final_mounts = rendered["services"][module.SERVICE]["volumes"] + assert len(final_mounts) == len(volumes) + assert sum(item.get("target") == module.EXECUTABLE_TARGET for item in final_mounts) == 1 + assert sum(item.get("target") == module.TARGET for item in final_mounts) == 1 + assert sum(item.get("target") == module.BACKUP_TARGET for item in final_mounts) == 1 + assert sum(item.get("target") == module.CONFIG_TARGET for item in final_mounts) == 1 + + +def test_activation_and_rollback_models_both_pin_managed_bind_propagation(compose): + _baseline, _calls = compose + old_model = json.loads(module.rendered_model(ORIGINAL_COMPOSE, "b" * 40, IMAGE_ID)) + next_model = json.loads(module.rendered_model(ORIGINAL_COMPOSE, "c" * 40, IMAGE_ID)) + + for rendered in (old_model, next_model): + mounts = rendered["services"][module.SERVICE]["volumes"] + managed = { + mount["target"]: mount + for mount in mounts + if mount.get("target") in {module.EXECUTABLE_TARGET, module.CONFIG_TARGET} + } + assert set(managed) == {module.EXECUTABLE_TARGET, module.CONFIG_TARGET} + assert managed[module.EXECUTABLE_TARGET]["bind"] == {"propagation": "rprivate"} + assert managed[module.CONFIG_TARGET]["bind"] == {"propagation": "rprivate"} + + def test_compose_allows_only_the_fixed_managed_appdata_volume_mount(compose): baseline, _calls = compose mount = { @@ -1699,3 +1846,127 @@ def test_legacy_crlf_compatibility_is_limited_to_one_attested_handoff( ): identity = {"sourceRevision": revision, "controllerVersion": version} assert module._matches_legacy_crlf_source(name, captured, source, identity) is accepted + + +def test_current_e854_crlf_capture_requires_exact_full_release_identity(): + checked_out_source = (ROOT / "scripts/run_repopulse_adoption.py").read_bytes() + source = checked_out_source.replace(b"\r\n", b"\n") + captured = source.replace(b"\n", b"\r\n") + assert hashlib.sha256(source).hexdigest() == ( + "9c66fae21f1eec27215af08f7bb7d24bafc6f10757393b56c42721fa99dccc66" + ) + assert hashlib.sha256(captured).hexdigest() == ( + "e3036cef0f6e1934297de2d35c0381f93441bbf6f28bd18c94c3f47fc116e4e1" + ) + identity = { + "sourceRevision": "e854334923cca574482d22b8424ba3ce46ec6aa1", + "sourceTree": "cf2a117f9222b952deb9902c646116612f3a7ea0", + "controllerVersion": "2.1.14", + "artifactSha256": "e88739bf091c8bac77ea0db5b97300666a1baa8ba984bbf6ae91e3bf25719e08", + } + + assert module._matches_legacy_crlf_source( + "run_repopulse_adoption.py", captured, source, identity + ) + + +@pytest.mark.parametrize( + ("name", "field", "value", "change_capture"), + [ + ("run_repopulse_adoption.py", "sourceRevision", "f" * 40, False), + ("run_repopulse_adoption.py", "sourceTree", "f" * 40, False), + ("run_repopulse_adoption.py", "controllerVersion", "2.1.15", False), + ("run_repopulse_adoption.py", "artifactSha256", "f" * 64, False), + ("other.py", "", "", False), + ("run_repopulse_adoption.py", "", "", True), + ], +) +def test_current_e854_crlf_compatibility_rejects_near_misses( + name, field, value, change_capture, +): + checked_out_source = (ROOT / "scripts/run_repopulse_adoption.py").read_bytes() + source = checked_out_source.replace(b"\r\n", b"\n") + captured = source.replace(b"\n", b"\r\n") + identity = { + "sourceRevision": "e854334923cca574482d22b8424ba3ce46ec6aa1", + "sourceTree": "cf2a117f9222b952deb9902c646116612f3a7ea0", + "controllerVersion": "2.1.14", + "artifactSha256": "e88739bf091c8bac77ea0db5b97300666a1baa8ba984bbf6ae91e3bf25719e08", + } + if field: + identity[field] = value + if change_capture: + captured = captured.replace(b"\r\n", b"\n", 1) + + assert not module._matches_legacy_crlf_source(name, captured, source, identity) + + +def test_current_e854_crlf_exception_integrates_with_git_source_verification(monkeypatch): + revision = "e854334923cca574482d22b8424ba3ce46ec6aa1" + tree = "cf2a117f9222b952deb9902c646116612f3a7ea0" + artifact_sha = "e88739bf091c8bac77ea0db5b97300666a1baa8ba984bbf6ae91e3bf25719e08" + files = module.LEGACY_RELEASE_FILES_BEFORE_FORGE + assert len(files) == 30 + checked_out_script = (ROOT / "scripts/run_repopulse_adoption.py").read_bytes() + runner_source = checked_out_script.replace(b"\r\n", b"\n") + runner_capture = runner_source.replace(b"\n", b"\r\n") + assert hashlib.sha256(runner_source).hexdigest() == ( + "9c66fae21f1eec27215af08f7bb7d24bafc6f10757393b56c42721fa99dccc66" + ) + assert hashlib.sha256(runner_capture).hexdigest() == ( + "e3036cef0f6e1934297de2d35c0381f93441bbf6f28bd18c94c3f47fc116e4e1" + ) + source_by_name = {} + entries = {} + for name in files: + path = (ROOT / "scripts/run_repopulse_adoption.py" if name == "run_repopulse_adoption.py" + else ROOT / "ops/gitea-deploy-broker" / name) + source = runner_source if name == "run_repopulse_adoption.py" else path.read_bytes() + source = source.replace(b"\r\n", b"\n") + source_by_name[name] = source + entries[name] = ( + runner_capture if name == "run_repopulse_adoption.py" else source, + 0o644, + ) + identity_data = { + "apiVersion": "itworx.controller-artifact/v1", + "controllerVersion": "2.1.14", + "sourceRepository": "jens/unraid-appops-gateway", + "sourceRevision": revision, + "sourceTree": tree, + "artifactSha256": artifact_sha, + "builtAt": "2026-09-30T00:00:00Z", + } + entries["controller-release.json"] = ( + json.dumps(identity_data, separators=(",", ":")).encode(), 0o644 + ) + identity = {key: str(value) for key, value in identity_data.items()} + git_outputs = {path: source for name, source in source_by_name.items() for path in [ + "scripts/run_repopulse_adoption.py" + if name == "run_repopulse_adoption.py" + else f"ops/gitea-deploy-broker/{name}" + ]} + controller_id = "c" * 64 + + def select_attested_files(captured, expected): + assert expected == artifact_sha + assert all(name in captured for name in files) + return files + + def run(arguments, **_kwargs): + assert arguments[:5] == [ + "docker", "exec", controller_id, "git", f"--git-dir={module.SOURCE_GIT_DIR}" + ] + if arguments[-2] == "rev-parse": + return f"{tree}\n".encode() + return git_outputs[arguments[-1].split(":", 1)[-1]] + + monkeypatch.setattr(module, "_matching_release_files", select_attested_files) + monkeypatch.setattr(module, "_run", run) + module._verify_git_source_identity(entries, identity, controller_id=controller_id) + + other_file = next(name for name in files if name != "run_repopulse_adoption.py") + other_path = f"ops/gitea-deploy-broker/{other_file}" + git_outputs[other_path] = b"tampered protected Git source" + with pytest.raises(module.VolumeTransitionError, match="source_mismatch"): + module._verify_git_source_identity(entries, identity, controller_id=controller_id)