From 5ec6478b12d73ba4d5594de33d41e9a72bd35c86 Mon Sep 17 00:00:00 2001 From: Nick Sweeting Date: Sat, 15 Aug 2026 03:50:13 -0700 Subject: [PATCH] Require ArchiveBox health in setup probes --- archivebox/core/views.py | 6 +++++- archivebox/templates/static/setup_wizard.js | 16 +++++++++------- .../tests/test_ui_admin_config_widget.py | 18 +++++++++++++++++- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/archivebox/core/views.py b/archivebox/core/views.py index d7922156..3a1adfa3 100644 --- a/archivebox/core/views.py +++ b/archivebox/core/views.py @@ -1801,7 +1801,11 @@ class HealthCheckView(View): """ Handle a GET request """ - return HttpResponse("OK", content_type="text/plain", status=200) + response = HttpResponse("OK", content_type="text/plain", status=200) + response["Access-Control-Allow-Origin"] = "*" + response["Access-Control-Expose-Headers"] = "X-ArchiveBox-Health" + response["X-ArchiveBox-Health"] = "OK" + return response @render_with_table_view diff --git a/archivebox/templates/static/setup_wizard.js b/archivebox/templates/static/setup_wizard.js index 86626473..25e03569 100644 --- a/archivebox/templates/static/setup_wizard.js +++ b/archivebox/templates/static/setup_wizard.js @@ -262,21 +262,23 @@ document.getElementById('archivebox-setup-wildcard-help').hidden = dnsMode === 'localhost'; } - function probeUrl(url, generation) { + function probeUrl(url, generation, requireArchiveBoxHealth) { var controller = new AbortController(); var timeout = window.setTimeout(function() { controller.abort(); }, 5000); var target = new URL(url); target.searchParams.set('archivebox_setup_probe', String(generation)); return fetch(target.toString(), { method: 'GET', - mode: 'no-cors', + mode: requireArchiveBoxHealth ? 'cors' : 'no-cors', credentials: 'omit', cache: 'no-store', redirect: 'follow', signal: controller.signal, - }).then(function() { + }).then(function(response) { window.clearTimeout(timeout); - return true; + return requireArchiveBoxHealth + ? response.ok && response.headers.get('X-ArchiveBox-Health') === 'OK' + : true; }).catch(function() { window.clearTimeout(timeout); return false; @@ -378,9 +380,9 @@ admin: probeUrl(preview.adminUrl, generation), api: probeUrl(preview.apiUrl, generation), index: probeUrl(preview.indexUrl, generation), - web: probeUrl(preview.webHealthUrl, generation), - snapshot: probeUrl(preview.snapshotHealthUrl, generation), - original: probeUrl(preview.originalHealthUrl, generation), + web: probeUrl(preview.webHealthUrl, generation, true), + snapshot: probeUrl(preview.snapshotHealthUrl, generation, true), + original: probeUrl(preview.originalHealthUrl, generation, true), }; Promise.all(Object.keys(checks).map(function(key) { return checks[key].then(function(ok) { return [key, ok]; }); })).then(function(entries) { if (generation !== probeGeneration || preview !== currentPreview) return; diff --git a/archivebox/tests/test_ui_admin_config_widget.py b/archivebox/tests/test_ui_admin_config_widget.py index fe9a2fbf..d901cf9b 100644 --- a/archivebox/tests/test_ui_admin_config_widget.py +++ b/archivebox/tests/test_ui_admin_config_widget.py @@ -12,6 +12,7 @@ from archivebox.config.common import get_config from archivebox.core.middleware import AdminCookieIsolationMiddleware from archivebox.core.setup_wizard import get_base_url_mismatch_context, get_setup_wizard_context from archivebox.core.templatetags.core_tags import system_warnings_banner +from archivebox.core.views import HealthCheckView STATIC_DIR = Path(__file__).parents[1] / "templates" / "static" SETUP_WIZARD_CSS = (STATIC_DIR / "setup_wizard.css").read_text() @@ -213,13 +214,28 @@ def test_setup_wizard_assets_enforce_selection_and_access_requirements(): assert "Waiting for a matching browser URL and valid setup options" in SETUP_WIZARD_JS assert "Finish the selected DNS, ingress, and TLS setup" in SETUP_WIZARD_JS - for target in ("adminUrl", "apiUrl", "indexUrl", "webHealthUrl", "snapshotHealthUrl", "originalHealthUrl"): + for target in ("adminUrl", "apiUrl", "indexUrl"): assert f"probeUrl(preview.{target}" in SETUP_WIZARD_JS assert "wildcardHealthUrl" not in SETUP_WIZARD_JS assert "results.wildcard" not in SETUP_WIZARD_JS assert "var coreReachable = results.admin && results.api && results.index && results.web && results.snapshot;" in SETUP_WIZARD_JS assert "probeUrl(webOrigin + '/web/https://example.com'" not in SETUP_WIZARD_JS assert "credentials: 'omit'" in SETUP_WIZARD_JS + assert "function probeUrl(url, generation, requireArchiveBoxHealth)" in SETUP_WIZARD_JS + assert "mode: requireArchiveBoxHealth ? 'cors' : 'no-cors'" in SETUP_WIZARD_JS + assert "response.ok && response.headers.get('X-ArchiveBox-Health') === 'OK'" in SETUP_WIZARD_JS + for target in ("webHealthUrl", "snapshotHealthUrl", "originalHealthUrl"): + assert f"probeUrl(preview.{target}, generation, true)" in SETUP_WIZARD_JS + + +def test_health_check_is_identifiable_across_ingress_origins(): + response = HealthCheckView.as_view()(RequestFactory().get("/health/")) + + assert response.status_code == 200 + assert response.content == b"OK" + assert response["Access-Control-Allow-Origin"] == "*" + assert response["Access-Control-Expose-Headers"] == "X-ArchiveBox-Health" + assert response["X-ArchiveBox-Health"] == "OK" @pytest.mark.parametrize(