From b95b8e98cd9d1997aad38bbd9272b8efbf4b73a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A1=E5=8F=A3=E6=B5=AA=E4=BA=BA?= Date: Sat, 17 Oct 2026 06:10:44 +0800 Subject: [PATCH] Fix Server release checks for the LAN-only listener [skip ci] --- scripts/server-release.py | 41 +++++++++++++++++++++++------------- tests/test_server_release.py | 18 ++++++++++++++++ 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/scripts/server-release.py b/scripts/server-release.py index 2d34e38..b5310e8 100644 --- a/scripts/server-release.py +++ b/scripts/server-release.py @@ -16,6 +16,7 @@ from pathlib import Path APP = Path("/srv/pigway/apps/pigway-frame-flow") RECOVERY = Path("/srv/pigway/recovery/frame-flow") CONTAINER = "pigway-frame-flow" +ORIGIN = "http://192.0.2.10:81" def run(*args): @@ -62,11 +63,16 @@ def archive_staging(backup, ctx): for tag in [ "pigway-frame-flow:history-ui-local-base", "pigway-frame-flow:final-audit-local-base", + "pigway-frame-flow:0.1.9-5d2171b9be3e", ]: tags = run( "docker", "image", "ls", "--format", "{{.Repository}}:{{.Tag}}" ).splitlines() - if tag in tags and tag not in references: + if ( + tag in tags + and tag not in references + and not run("docker", "ps", "-aq", "--filter", "ancestor=" + tag) + ): run("docker", "image", "rm", tag) removed.append(tag) return { @@ -76,6 +82,18 @@ def archive_staging(backup, ctx): } +def wait_ready(): + for attempt in range(30): + try: + with urllib.request.urlopen(ORIGIN + "/ready", timeout=4) as r: + assert json.load(r)["status"] == "ready" + break + except Exception: + if attempt == 29: + raise + time.sleep(2) + + def verify_runtime(commit): c = inspect() env = dict(s.split("=", 1) for s in c["Config"]["Env"]) @@ -163,18 +181,9 @@ def release(lock): try: changed = True (APP / "compose.json").write_text(json.dumps(compose, indent=2) + "\n") + print("Restarting Frame Flow; checking " + ORIGIN + "/ready", flush=True) run("systemctl", "restart", "pigway-frame-flow.service") - for attempt in range(30): - try: - with urllib.request.urlopen( - "http://127.0.0.1:81/ready", timeout=4 - ) as r: - assert json.load(r)["status"] == "ready" - break - except Exception: - if attempt == 29: - raise - time.sleep(2) + wait_ready() current = verify_runtime(commit) assert current["Image"] == run( "docker", "image", "inspect", "--format", "{{.Id}}", image @@ -202,9 +211,7 @@ def release(lock): "index.html", "style.css", ]: - url = "http://127.0.0.1:81/" + ( - "" if name == "index.html" else "ui/" + name - ) + url = ORIGIN + "/" + ("" if name == "index.html" else "ui/" + name) with urllib.request.urlopen(url, timeout=10) as response: assert ( response.read() @@ -212,6 +219,7 @@ def release(lock): ) run("systemctl", "is-active", "pigway-frame-flow.service") except BaseException: + print("Release verification failed; restoring previous Compose", flush=True) if changed: shutil.copy2(backup / "compose.before.json", APP / "compose.json") run("systemctl", "restart", "pigway-frame-flow.service") @@ -236,6 +244,9 @@ def release(lock): owner = ctx.stat() os.chown(output, owner.st_uid, owner.st_gid) shutil.copy2(output, backup / "release-verification.json") + public_receipt = ctx.parent / "frame-release-verification.json" + shutil.copy2(output, public_receipt) + os.chown(public_receipt, owner.st_uid, owner.st_gid) shutil.rmtree(ctx) print(json.dumps(receipt, indent=2)) diff --git a/tests/test_server_release.py b/tests/test_server_release.py index 651284a..f9f714f 100644 --- a/tests/test_server_release.py +++ b/tests/test_server_release.py @@ -53,3 +53,21 @@ def test_release_rejects_unsafe_runtime(release_tool, monkeypatch, unsafe): monkeypatch.setattr(release_tool, "inspect", lambda: container) with pytest.raises(AssertionError): release_tool.verify_runtime("test") + + +def test_release_readiness_retries_on_actual_listener(release_tool, monkeypatch): + import io + import urllib.error + + calls = [] + + def listener(url, timeout): + calls.append(url) + if url != "http://192.0.2.10:81/ready" or len(calls) == 1: + raise urllib.error.URLError("Connection refused during restart") + return io.BytesIO(b'{"status":"ready"}') + + monkeypatch.setattr(release_tool.urllib.request, "urlopen", listener) + monkeypatch.setattr(release_tool.time, "sleep", lambda _: None) + release_tool.wait_ready() + assert len(calls) == 2