release: v0.9.33rc52

This commit is contained in:
Nick Sweeting 2026-05-31 01:24:43 -07:00
parent 83d5161b3e
commit c1e792fee6
No known key found for this signature in database
10 changed files with 204 additions and 77 deletions

View File

@ -39,7 +39,7 @@ jobs:
- name: Install dependencies with uv
run: |
uv sync --all-extras --all-groups --no-sources --no-cache
uv sync --extra sonic --extra debug --all-groups --no-sources --no-cache
uv pip install -e ./abxpkg -e ./abx-plugins[dev] -e ./abx-dl
- name: Run prek

View File

@ -15,6 +15,8 @@ from contextlib import contextmanager
#############################################################################################
DATA_DIR = Path(os.getcwd())
if os.environ.get("DATA_DIR") and Path(os.environ["DATA_DIR"]).resolve() != DATA_DIR.resolve():
raise SystemExit(f"[X] DATA_DIR={os.environ['DATA_DIR']} must equal cwd={DATA_DIR}; cd into the data dir before running archivebox")
try:
DATA_DIR_STAT = DATA_DIR.stat()

View File

@ -324,20 +324,62 @@ def result_list_tag(parser, token):
)
@register.inclusion_tag("security_mode_banner.html", takes_context=True)
def security_mode_banner(context):
"""Render the top-of-page warning banner for one of two conditions:
_LOW_DISK_THRESHOLD_GB = 1.0
_HIGH_MEMORY_THRESHOLD_PCT = 95.0
_HIGH_LOAD_MULTIPLE = 3 # 15-min loadavg > 3 * cpu_count
_HEALTH_CHECK_INTERVAL_SECONDS = 30
_health_cache: dict = {"checked_at": 0.0, "stats": {}}
1. ``mode="unconfigured"`` ``BASE_URL`` is empty. The server is running
on whatever host the operator happens to be hitting; CSRF auto-derive
and the request-host fallback in ``get_base_url`` keep things working,
but the operator should pin ``BASE_URL`` explicitly so links stay
stable across hosts (and the misconfig banner goes away).
2. ``mode="unsafe"`` ``SERVER_SECURITY_MODE`` is a non-subdomain mode.
Archived pages share an origin with privileged routes.
Both conditions can hold; we show the ``unconfigured`` banner first
because pinning ``BASE_URL`` is the more immediately actionable fix.
def _machine_health_stats() -> dict:
"""Cached wrapper around the Machine-admin stats util.
The Machine list/change pages already render disk / mem / load via
``archivebox.machine.detect.get_host_stats()`` we reuse the same call so
the banner thresholds line up 1:1 with what's shown on /admin/machine/.
Cached for 30s because the inclusion tag fires on every page render and
``get_host_stats`` shells into several psutil probes.
"""
import time
now = time.monotonic()
if _health_cache["stats"] and (now - _health_cache["checked_at"]) < _HEALTH_CHECK_INTERVAL_SECONDS:
return _health_cache["stats"]
try:
from archivebox.machine.detect import get_host_stats
stats = get_host_stats() or {}
except Exception:
stats = {}
_health_cache["checked_at"] = now
_health_cache["stats"] = stats
return stats
@register.inclusion_tag("system_warnings_banner.html", takes_context=True)
def system_warnings_banner(context):
"""Render the top-of-page warning banner for one of the conditions below,
in priority order (highest first):
1. ``mode="unconfigured"`` ``BASE_URL`` is empty. Security/correctness
issue: until it's pinned, generated URLs can echo any Host the client
sends, and admin/web/api routing has no canonical anchor.
2. ``mode="unsafe"`` ``SERVER_SECURITY_MODE`` is a non-subdomain
mode. Archived pages share an origin with privileged routes.
3. ``mode="low_disk"`` ``DATA_DIR`` has <1 GiB free; new archive
jobs will start failing on ENOSPC.
4. ``mode="high_memory"`` virtual memory utilization at/above 95%; the
host is one OOM-kill from a crash.
5. ``mode="high_load"`` 15-minute load average exceeds 3 × CPU count
(the kernel's own sustained-load EMA, so no rolling buffer of ours is
needed).
Config/security warnings come first because they affect correctness +
security and need explicit operator action; host-health warnings come
after and reuse ``machine.detect.get_host_stats`` (the same function that
populates the Machine admin page), cached for 30s.
"""
config = context.get("CONFIG")
if config is None:
@ -349,6 +391,30 @@ def security_mode_banner(context):
return _unconfigured_banner_context(context.get("request"))
if not config.USES_SUBDOMAIN_ROUTING:
return {"mode": "unsafe"}
stats = _machine_health_stats()
free_gb = stats.get("disk_data_free_gb")
if isinstance(free_gb, (int, float)) and free_gb < _LOW_DISK_THRESHOLD_GB:
return {"mode": "low_disk", "free_gb": f"{free_gb:.2f}"}
mem_pct = stats.get("mem_virt_used_pct")
if isinstance(mem_pct, (int, float)) and mem_pct >= _HIGH_MEMORY_THRESHOLD_PCT:
return {"mode": "high_memory", "mem_pct": f"{mem_pct:.1f}"}
cpu_load = stats.get("cpu_load") or ()
cpu_count = stats.get("cpu_count") or 1
# ``cpu_load`` is the (1min, 5min, 15min) tuple from psutil.getloadavg();
# we take the 15-min figure because the operator's threshold was
# "sustained for 15min" and the kernel already maintains that EMA.
load_15 = cpu_load[2] if isinstance(cpu_load, (list, tuple)) and len(cpu_load) >= 3 else None
if isinstance(load_15, (int, float)) and load_15 > _HIGH_LOAD_MULTIPLE * cpu_count:
return {
"mode": "high_load",
"load_15": f"{load_15:.2f}",
"cpu_count": cpu_count,
"load_threshold": _HIGH_LOAD_MULTIPLE * cpu_count,
}
return {"mode": ""}

View File

@ -1678,7 +1678,7 @@
<body class="{% if is_popup %}popup {% endif %}{% block bodyclass %}{% endblock %}" data-admin-utc-offset="{% now "Z" %}">
{% security_mode_banner %}
{% system_warnings_banner %}
{% include 'progressbar.html' %}
<div id="container">

View File

@ -19,7 +19,7 @@
{% endblock %}
</head>
<body>
{% security_mode_banner %}
{% system_warnings_banner %}
<div id="container">
<div id="header">
<div id="branding">

View File

@ -1,56 +0,0 @@
{% comment %}
Fixed red badge that hangs off the top center of the page. Two trigger
conditions (see ``security_mode_banner`` in core_tags.py):
* mode="unsafe" — server is in a non-subdomain SERVER_SECURITY_MODE.
Archived content shares an origin with the admin UI.
* mode="unconfigured" — BASE_URL is not set. Always shown until the
operator pins it explicitly, even when CSRF
auto-derive or request-host fallback are keeping
the server functional.
{% endcomment %}
{% if mode == "unsafe" %}
<div id="archivebox-security-banner" role="alert" aria-live="polite"
style="position:fixed;top:-10px;left:50%;transform:translateX(-50%);
z-index:2147483647;background:#aa1e55;color:#fff;
font:600 11px/1.3 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;
padding:14px 14px 4px 14px;border-radius:0 0 6px 6px;
box-shadow:0 2px 10px rgba(0,0,0,0.35);pointer-events:auto;
white-space:nowrap;letter-spacing:0.3px;text-transform:uppercase;">
<span style="display:inline-block;background:#fff;color:#aa1e55;
border-radius:3px;padding:1px 5px;margin-right:6px;font-weight:800;">
⚠ unsafe
</span>
ArchiveBox single-domain mode &mdash; archived pages share an origin with this site
<a href="https://github.com/ArchiveBox/ArchiveBox/wiki/Security-Modes"
target="_blank" rel="noopener noreferrer"
style="color:#fff;text-decoration:underline;margin-left:6px;">why?</a>
</div>
{% elif mode == "unconfigured" %}
<div id="archivebox-security-banner" role="alert" aria-live="polite"
style="position:fixed;top:-10px;left:50%;transform:translateX(-50%);
z-index:2147483647;background:#dc2626;color:#fff;
font:600 11px/1.3 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;
padding:14px 14px 4px 14px;border-radius:0 0 6px 6px;
box-shadow:0 2px 10px rgba(0,0,0,0.35);pointer-events:auto;
max-width:90vw;letter-spacing:0.3px;text-transform:uppercase;">
<span style="display:inline-block;background:#fff;color:#dc2626;
border-radius:3px;padding:1px 5px;margin-right:6px;font-weight:800;">
⚠ base_url not set
</span>
To prevent unauthorized requests, you must set your intended server URL in env or config:
<code style="background:#000;color:#fff;padding:1px 6px;border-radius:3px;font:700 11px/1.3 ui-monospace,Menlo,Consolas,monospace;text-transform:none;">
BASE_URL={% if suggested_base_url %}{{ suggested_base_url }}{% else %}http://*.archivebox.localhost:8000{% endif %}
</code>
{% if machine_admin_url %}
<a href="{{ machine_admin_url }}#BASE_URL"
style="display:inline-block;background:#fff;color:#dc2626;padding:1px 6px;border-radius:3px;
font-weight:800;text-decoration:none;margin-left:6px;text-transform:uppercase;">
pin via admin →
</a>
{% endif %}
<a href="https://github.com/ArchiveBox/ArchiveBox/wiki/Security-Modes#base_url"
target="_blank" rel="noopener noreferrer"
style="color:#fff;text-decoration:underline;margin-left:6px;">docs</a>
</div>
{% endif %}

View File

@ -0,0 +1,115 @@
{% comment %}
Fixed red badge that hangs off the top center of the page. Five trigger
conditions (see ``system_warnings_banner`` in core_tags.py); precedence is
config/security first, then host-health:
* mode="unconfigured" — BASE_URL is not set. Always shown until the
operator pins it explicitly, even when CSRF
auto-derive or request-host fallback are keeping
the server functional.
* mode="unsafe" — server is in a non-subdomain SERVER_SECURITY_MODE.
Archived content shares an origin with the admin UI.
* mode="low_disk" — free space on DATA_DIR's filesystem is below 1GiB.
Archive jobs will fail until the operator frees space.
* mode="high_memory" — virtual memory utilization above 95%; one OOM-kill
from a crash.
* mode="high_load" — 15-min loadavg > 3 × cpu_count; saturated host.
{% endcomment %}
{% if mode == "low_disk" %}
<div id="archivebox-system-warning-banner" role="alert" aria-live="assertive"
style="position:fixed;top:-10px;left:50%;transform:translateX(-50%);
z-index:2147483647;background:#dc2626;color:#fff;
font:600 11px/1.3 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;
padding:14px 14px 4px 14px;border-radius:0 0 6px 6px;
box-shadow:0 2px 10px rgba(0,0,0,0.35);pointer-events:auto;
max-width:90vw;letter-spacing:0.3px;text-transform:uppercase;">
<span style="display:inline-block;background:#fff;color:#dc2626;
border-radius:3px;padding:1px 5px;margin-right:6px;font-weight:800;">
⚠ low disk
</span>
Only <code style="background:#000;color:#fff;padding:1px 6px;border-radius:3px;font:700 11px/1.3 ui-monospace,Menlo,Consolas,monospace;text-transform:none;">{{ free_gb }} GiB</code>
free on <code style="background:#000;color:#fff;padding:1px 6px;border-radius:3px;font:700 11px/1.3 ui-monospace,Menlo,Consolas,monospace;text-transform:none;">DATA_DIR</code>
— new archive jobs will fail until space is reclaimed.
<a href="https://github.com/ArchiveBox/ArchiveBox/wiki/Disk-Usage"
target="_blank" rel="noopener noreferrer"
style="color:#fff;text-decoration:underline;margin-left:6px;">docs</a>
</div>
{% elif mode == "high_memory" %}
<div id="archivebox-system-warning-banner" role="alert" aria-live="assertive"
style="position:fixed;top:-10px;left:50%;transform:translateX(-50%);
z-index:2147483647;background:#dc2626;color:#fff;
font:600 11px/1.3 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;
padding:14px 14px 4px 14px;border-radius:0 0 6px 6px;
box-shadow:0 2px 10px rgba(0,0,0,0.35);pointer-events:auto;
max-width:90vw;letter-spacing:0.3px;text-transform:uppercase;">
<span style="display:inline-block;background:#fff;color:#dc2626;
border-radius:3px;padding:1px 5px;margin-right:6px;font-weight:800;">
⚠ high memory
</span>
Virtual memory at
<code style="background:#000;color:#fff;padding:1px 6px;border-radius:3px;font:700 11px/1.3 ui-monospace,Menlo,Consolas,monospace;text-transform:none;">{{ mem_pct }}%</code>
— the host is close to OOM. Consider stopping crawls or scaling up.
</div>
{% elif mode == "high_load" %}
<div id="archivebox-system-warning-banner" role="alert" aria-live="polite"
style="position:fixed;top:-10px;left:50%;transform:translateX(-50%);
z-index:2147483647;background:#dc2626;color:#fff;
font:600 11px/1.3 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;
padding:14px 14px 4px 14px;border-radius:0 0 6px 6px;
box-shadow:0 2px 10px rgba(0,0,0,0.35);pointer-events:auto;
max-width:90vw;letter-spacing:0.3px;text-transform:uppercase;">
<span style="display:inline-block;background:#fff;color:#dc2626;
border-radius:3px;padding:1px 5px;margin-right:6px;font-weight:800;">
⚠ high load
</span>
15-min loadavg
<code style="background:#000;color:#fff;padding:1px 6px;border-radius:3px;font:700 11px/1.3 ui-monospace,Menlo,Consolas,monospace;text-transform:none;">{{ load_15 }}</code>
exceeds
<code style="background:#000;color:#fff;padding:1px 6px;border-radius:3px;font:700 11px/1.3 ui-monospace,Menlo,Consolas,monospace;text-transform:none;">{{ load_threshold }}</code>
({{ cpu_count }} cores × 3) — the host is saturated; crawls are queuing faster than the runner can finish them.
</div>
{% elif mode == "unsafe" %}
<div id="archivebox-system-warning-banner" role="alert" aria-live="polite"
style="position:fixed;top:-10px;left:50%;transform:translateX(-50%);
z-index:2147483647;background:#aa1e55;color:#fff;
font:600 11px/1.3 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;
padding:14px 14px 4px 14px;border-radius:0 0 6px 6px;
box-shadow:0 2px 10px rgba(0,0,0,0.35);pointer-events:auto;
white-space:nowrap;letter-spacing:0.3px;text-transform:uppercase;">
<span style="display:inline-block;background:#fff;color:#aa1e55;
border-radius:3px;padding:1px 5px;margin-right:6px;font-weight:800;">
⚠ unsafe
</span>
ArchiveBox single-domain mode &mdash; archived pages share an origin with this site
<a href="https://github.com/ArchiveBox/ArchiveBox/wiki/Security-Modes"
target="_blank" rel="noopener noreferrer"
style="color:#fff;text-decoration:underline;margin-left:6px;">why?</a>
</div>
{% elif mode == "unconfigured" %}
<div id="archivebox-system-warning-banner" role="alert" aria-live="polite"
style="position:fixed;top:-10px;left:50%;transform:translateX(-50%);
z-index:2147483647;background:#dc2626;color:#fff;
font:600 11px/1.3 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;
padding:14px 14px 4px 14px;border-radius:0 0 6px 6px;
box-shadow:0 2px 10px rgba(0,0,0,0.35);pointer-events:auto;
max-width:90vw;letter-spacing:0.3px;text-transform:uppercase;">
<span style="display:inline-block;background:#fff;color:#dc2626;
border-radius:3px;padding:1px 5px;margin-right:6px;font-weight:800;">
⚠ base_url not set
</span>
To prevent unauthorized requests, you must set your intended server URL in env or config:
<code style="background:#000;color:#fff;padding:1px 6px;border-radius:3px;font:700 11px/1.3 ui-monospace,Menlo,Consolas,monospace;text-transform:none;">
BASE_URL={% if suggested_base_url %}{{ suggested_base_url }}{% else %}http://*.archivebox.localhost:8000{% endif %}
</code>
{% if machine_admin_url %}
<a href="{{ machine_admin_url }}#BASE_URL"
style="display:inline-block;background:#fff;color:#dc2626;padding:1px 6px;border-radius:3px;
font-weight:800;text-decoration:none;margin-left:6px;text-transform:uppercase;">
pin via admin →
</a>
{% endif %}
<a href="https://github.com/ArchiveBox/ArchiveBox/wiki/Security-Modes#base_url"
target="_blank" rel="noopener noreferrer"
style="color:#fff;text-decoration:underline;margin-left:6px;">docs</a>
</div>
{% endif %}

View File

@ -328,7 +328,7 @@ wait_for_pypi() {
}
run_checks() {
uv sync --all-extras --all-groups --no-cache --upgrade
uv sync --extra sonic --extra debug --all-groups --no-cache --upgrade
uv build --all
}

View File

@ -1,6 +1,6 @@
{
"name": "archivebox",
"version": "0.9.33rc51",
"version": "0.9.33rc52",
"repository": "github:ArchiveBox/ArchiveBox",
"license": "MIT",
"dependencies": {

View File

@ -1,6 +1,6 @@
[project]
name = "archivebox"
version = "0.9.33rc51"
version = "0.9.33rc52"
requires-python = ">=3.13"
description = "Self-hosted internet archiving solution."
authors = [{name = "Nick Sweeting", email = "pyproject.toml@archivebox.io"}]
@ -79,9 +79,9 @@ dependencies = [
### Extractor dependencies (optional binaries detected at runtime via shutil.which)
### Binary/Package Management
"abxbus==2.5.8", # EventBus API
"abxpkg>=1.11.80", # for: detecting, versioning, and installing binaries via apt/brew/pip/npm
"abx-plugins>=1.11.86", # shared ArchiveBox plugin package with Chrome/Puppeteer dependency wiring
"abx-dl>=1.11.86", # shared ArchiveBox downloader package with blocking install preflight
"abxpkg>=1.11.81", # for: detecting, versioning, and installing binaries via apt/brew/pip/npm
"abx-plugins>=1.11.87", # shared ArchiveBox plugin package with Chrome/Puppeteer dependency wiring
"abx-dl>=1.11.87", # shared ArchiveBox downloader package with blocking install preflight
### UUID7 backport for Python <3.14
"uuid7>=0.1.0; python_version < '3.14'", # provides the uuid_extensions module on Python 3.13
]