Files
RustProxy/scripts/release_smoke.py
T
2026-10-07 14:50:13 +02:00

1308 lines
91 KiB
Python
Executable File

#!/usr/bin/env python3
"""Offline release checks that do not require Rust/Cargo or network access.
Exercises the shipped SQLite migration/backup and stopped-source upgrade path,
then checks the 0.24.1 shared-statistics/runtime/settings contracts and retained legacy-WebSocket and retained control-plane/cache-UX/live-log contracts plus retained transport contracts.
"""
from __future__ import annotations
import hashlib
import importlib.util
import json
import os
import re
from pathlib import Path
import sqlite3
import subprocess
import tempfile
import tomllib
import zipfile
import backup as backup_module
import deploy
import migrate_npm
import upgrade as upgrade_module
from rust_source import rust_source
from web_assets import read_app_js, read_vhosts_js
ROOT = Path(__file__).resolve().parents[1]
def read_cache_source() -> str:
return rust_source(ROOT, "src/cache/mod.rs")
def read_deploy_source() -> str:
return "\n".join((ROOT / name).read_text(encoding="utf-8") for name in ("scripts/deploy_core.py", "scripts/deploy_runtime.py", "scripts/deploy.py"))
def read_docker_agent_source() -> str:
return "\n".join(path.read_text(encoding="utf-8") for path in sorted((ROOT / "docker-agent").glob("*.go")) if not path.name.endswith("_test.go"))
def sql_and_backup_smoke(tmp: Path) -> None:
data = tmp / "data"
data.mkdir(mode=0o700, parents=True)
db = data / "config.sqlite3"
connection = sqlite3.connect(db)
try:
connection.executescript((ROOT / "migrations/0002_admin.sql").read_text(encoding="utf-8"))
connection.executescript((ROOT / "migrations/0003_certificate_renewals.sql").read_text(encoding="utf-8"))
document = json.dumps({"listeners": {"endpoints": []}, "vhosts": []})
connection.execute(
"INSERT INTO configuration(id, revision, document) VALUES(1, 4, ?)",
(document,),
)
connection.execute(
"INSERT INTO administrator(id,username,password_hash,generation,created_at) VALUES(1,'owner','test-hash',1,1)"
)
connection.execute(
"INSERT INTO certificate_renewals(certificate_id,last_checked,next_check,last_renewed,not_after,failures,status,message) VALUES(?,?,?,?,?,?,?,?)",
("site-cert", 100, 200, 50, 500, 1, "retry", "saved observation"),
)
connection.execute("PRAGMA user_version = 3")
connection.commit()
# Simulate additive startup migrations through the current schema-v8.
connection.executescript((ROOT / "migrations/0004_admin_preferences.sql").read_text(encoding="utf-8"))
connection.executescript((ROOT / "migrations/0005_certificate_api.sql").read_text(encoding="utf-8"))
connection.executescript((ROOT / "migrations/0006_certificate_api_credentials.sql").read_text(encoding="utf-8"))
connection.executescript((ROOT / "migrations/0007_secret_store.sql").read_text(encoding="utf-8"))
connection.executescript((ROOT / "migrations/0008_config_history.sql").read_text(encoding="utf-8"))
connection.execute(
"INSERT INTO admin_preferences(administrator_id,document) VALUES(1,?)",
(json.dumps({"live_log_columns": ["time", "status"]}),),
)
connection.execute("PRAGMA user_version = 8")
connection.commit()
assert connection.execute("PRAGMA quick_check").fetchone()[0] == "ok"
tables = {
row[0]
for row in connection.execute(
"SELECT name FROM sqlite_master WHERE type='table'"
)
}
for table in ("configuration", "administrator", "admin_sessions", "admin_audit", "auth_throttle", "certificate_renewals", "admin_preferences", "certificate_api_credential", "certificate_api_credentials", "secret_store", "configuration_history"):
assert table in tables, table
finally:
connection.close()
os.chmod(db, 0o600)
destination = tmp / "backup"
copied = backup_module.backup(data, destination)
assert copied.is_file()
metadata = json.loads((destination / "backup.json").read_text(encoding="utf-8"))
assert metadata["project"] == "RustProxy"
assert metadata["schema"] == 8
assert metadata["configuration_revision"] == 4
restored = sqlite3.connect(copied)
try:
assert restored.execute("PRAGMA quick_check").fetchone()[0] == "ok"
assert restored.execute("SELECT revision FROM configuration WHERE id=1").fetchone()[0] == 4
assert restored.execute("SELECT status, message FROM certificate_renewals WHERE certificate_id='site-cert'").fetchone() == ("retry", "saved observation")
assert json.loads(restored.execute("SELECT document FROM admin_preferences WHERE administrator_id=1").fetchone()[0]) == {"live_log_columns": ["time", "status"]}
assert restored.execute("SELECT username FROM administrator WHERE id=1").fetchone()[0] == "owner"
finally:
restored.close()
def upgrade_smoke(tmp: Path) -> None:
target = tmp / "installed"
target.mkdir(parents=True)
(target / "Cargo.toml").write_text(
'[package]\nname = "rust-proxy"\nversion = "0.8.1"\n', encoding="utf-8"
)
(target / "src").mkdir()
(target / "src/obsolete.rs").write_text("obsolete\n", encoding="utf-8")
(target / "langs").mkdir()
custom_pack = {"meta": {"code": "zz_ZZ", "name": "Site test"}, "strings": {}}
(target / "langs/site.json").write_text(json.dumps(custom_pack), encoding="utf-8")
(target / "data").mkdir(mode=0o700)
(target / "data/keep.txt").write_text("preserve\n", encoding="utf-8")
legacy = target / "data/certs/renewals.json"
legacy.parent.mkdir(parents=True)
legacy.write_text('{"legacy":true}\n', encoding="utf-8")
(target / ".env").write_text("RUST_PROXY_DEMO=0\n", encoding="utf-8")
backup = upgrade_module.upgrade(ROOT, target, stopped=True)
assert backup.is_dir()
assert (backup / "src/obsolete.rs").is_file()
assert not (target / "src/obsolete.rs").exists()
assert 'version = "0.27.2"' in (target / "Cargo.toml").read_text(encoding="utf-8")
assert (target / "data/keep.txt").read_text(encoding="utf-8") == "preserve\n"
assert (target / ".env").read_text(encoding="utf-8") == "RUST_PROXY_DEMO=0\n"
assert (target / "langs/site.json").is_file()
assert (target / "docker-agent/main.go").is_file()
assert (target / "docker-agent/model.go").is_file()
assert (target / "docker-agent/docker.go").is_file()
assert (target / "docker-agent/network.go").is_file()
assert (target / "docker-agent/certificate.go").is_file()
assert (target / "docker-agent/server.go").is_file()
manifest = json.loads((backup / "upgrade.json").read_text(encoding="utf-8"))
assert manifest["to_version"] == "0.27.2"
assert manifest["removed_obsolete_runtime_state"] is True
assert not legacy.exists()
assert not (backup / "legacy-data").exists()
# Legacy cleanup must never follow a symlinked certs directory outside the data root.
unsafe = tmp / "unsafe-installed"
unsafe.mkdir()
(unsafe / "Cargo.toml").write_text('[package]\nname = "rust-proxy"\nversion = "0.8.1"\n', encoding="utf-8")
(unsafe / "src").mkdir(); (unsafe / "src/old.rs").write_text("old\n", encoding="utf-8")
(unsafe / "data").mkdir()
outside = tmp / "outside-certs"; outside.mkdir()
outside_legacy = outside / "renewals.json"; outside_legacy.write_text('{"outside":true}\n', encoding="utf-8")
(unsafe / "data/certs").symlink_to(outside, target_is_directory=True)
try:
upgrade_module.upgrade(ROOT, unsafe, stopped=True)
except ValueError as error:
assert "must not be a symlink" in str(error)
else:
raise AssertionError("symlinked certs directory was accepted during legacy cleanup")
assert outside_legacy.read_text(encoding="utf-8") == '{"outside":true}\n'
assert 'version = "0.8.1"' in (unsafe / "Cargo.toml").read_text(encoding="utf-8")
assert (unsafe / "src/old.rs").is_file()
def deployment_gate_smoke(tmp: Path) -> None:
assert deploy.admin_address("127.0.0.1:9090") == ("127.0.0.1:9090", False)
assert deploy.admin_address("0.0.0.0:9090", True) == ("0.0.0.0:9090", True)
try:
deploy.admin_address("0.0.0.0:9090", False)
except ValueError:
pass
else:
raise AssertionError("remote administration bind accepted without --allow-remote-admin")
assert deploy.admin_address("192.0.2.10:9090", True) == ("192.0.2.10:9090", True)
remote_unit = deploy.service_unit({"prefix":"/opt/rustproxy", "data_dir":"/var/lib/rustproxy", "user":"rustproxy", "group":"rustproxy", "admin":"0.0.0.0:9090", "remote_admin":True, "http":"0.0.0.0:80", "https":"0.0.0.0:443"})
assert "ExecStart=/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy --admin-listen 0.0.0.0:9090 serve --proxy-listen 0.0.0.0:80 --tls-listen 0.0.0.0:443 --allow-remote-admin" in remote_unit
assert "ReadWritePaths=/var/lib/rustproxy /var/log/rustproxy" in remote_unit
assert "TimeoutStopSec=120" in remote_unit
for marker in ["PrivateDevices=true", "ProtectKernelLogs=true", "RestrictNamespaces=true", "RestrictRealtime=true", "SystemCallArchitectures=native"]:
assert marker in remote_unit, marker
deploy_source = read_deploy_source()
assert "def stop_for_managed_update" in deploy_source and ".managed-update" in deploy_source
assert "systemctl', 'stop', SERVICE" in deploy_source
assert "def ensure_service_data_access" in deploy_source
assert "status=200/CHDIR" in deploy_source
assert "runuser', '-u', account.pw_name, '--', 'test', '-x'" in deploy_source
assert deploy_source.index("ensure_service_data_access(data, account)") < deploy_source.index("stop_for_managed_update(data)")
assert deploy_source.index("stop_for_managed_update(data)") < deploy_source.index("backup = backup_stopped(record, prefix)")
data = tmp / "admin-health"
data.mkdir(parents=True)
database = data / "config.sqlite3"
connection = sqlite3.connect(database)
connection.execute("CREATE TABLE configuration(id INTEGER PRIMARY KEY, revision INTEGER NOT NULL, document TEXT NOT NULL)")
connection.execute(
"INSERT INTO configuration VALUES(1, 7, ?)",
(json.dumps({"administration": {"listen": "0.0.0.0:9443", "tls": True, "allowed_hosts": ["panel.example.test"]}}),),
)
connection.commit(); connection.close()
record = {"admin": "127.0.0.1:9090", "data_dir": str(data)}
assert deploy.admin_health_endpoint(record) == ("127.0.0.1:9443", True, "panel.example.test:9443")
def npm_migration_smoke(tmp: Path) -> None:
database = tmp / "npm.sqlite"
connection = sqlite3.connect(database)
connection.execute(
"CREATE TABLE proxy_host("
"id INTEGER, domain_names TEXT, forward_scheme TEXT, forward_host TEXT, forward_port INTEGER, "
"allow_websocket_upgrade INTEGER, http2_support INTEGER, enabled INTEGER, certificate_id INTEGER, "
"ssl_forced INTEGER, access_list_id INTEGER, advanced_config TEXT, locations TEXT, "
"caching_enabled INTEGER, block_exploits INTEGER, meta TEXT)"
)
connection.execute(
"INSERT INTO proxy_host VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
(12, json.dumps(["App.Example.test", "*.apps.example.test"]), "http", "10.20.30.40", 8080,
1, 1, 1, 9, 0, 4, "proxy_read_timeout 90s;", "[]", 1, 0, "{}"),
)
connection.execute(
"INSERT INTO proxy_host VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
(13, json.dumps(["secure.example.test"]), "http", "10.20.30.41", 8081,
1, 1, 1, 10, 1, 0, "", "[]", 0, 0, "{}"),
)
connection.commit(); connection.close()
rows = migrate_npm.load_npm_hosts(database)
config = {
"listeners": {"endpoints": [
{"id": "public-http", "enabled": True, "binding": {"address": "0.0.0.0:80", "tls": False}},
{"id": "public-https", "enabled": True, "binding": {"address": "0.0.0.0:443", "tls": True}},
]},
"vhosts": [],
}
merged, decisions = migrate_npm.build_migration(rows, config)
assert len(decisions) == 2 and all(item.action == "import" for item in decisions)
migrated = merged["vhosts"][0]
assert migrated["frontend"]["hosts"] == ["app.example.test", "*.apps.example.test"]
assert migrated["frontend"]["listener_ids"] == ["public-http", "public-https"]
assert migrated["backends"][0]["url"] == "http://10.20.30.40:8080"
assert migrated["backends"][0]["allow_private"] is True
assert migrated["websockets"] is True
assert migrated["force_ssl"] is False
assert migrated["forward_client_ip"] is True
assert any("access_list_id=4" in warning for warning in decisions[0].warnings or [])
assert any("advanced_config" in warning for warning in decisions[0].warnings or [])
secure = merged["vhosts"][1]
assert secure["force_ssl"] is True
assert secure["frontend"]["listener_ids"] == ["public-http", "public-https"]
assert any("Force SSL" in warning for warning in decisions[1].warnings or [])
def ui_contract_smoke() -> None:
html = (ROOT / "src/admin/web/index.html").read_text(encoding="utf-8")
app = read_app_js(ROOT)
settings = (ROOT / "src/admin/web/settings.js").read_text(encoding="utf-8")
vhosts = read_vhosts_js(ROOT)
streams = (ROOT / "src/admin/web/streams.js").read_text(encoding="utf-8")
redirects = (ROOT / "src/admin/web/redirects.js").read_text(encoding="utf-8")
host404 = (ROOT / "src/admin/web/host404.js").read_text(encoding="utf-8")
integrations = (ROOT / "src/admin/web/integrations.js").read_text(encoding="utf-8")
acme_ui = (ROOT / "src/admin/web/acme.js").read_text(encoding="utf-8")
traffic = (ROOT / "src/admin/web/traffic-panel.js").read_text(encoding="utf-8")
cache = (ROOT / "src/admin/web/cache-panel.js").read_text(encoding="utf-8")
transform_panel = (ROOT / "src/admin/web/transform-panel.js").read_text(encoding="utf-8")
css = (ROOT / "src/admin/web/style.css").read_text(encoding="utf-8")
required_html = [
'id="panel-traffic"',
'id="traffic-scope"',
'id="traffic-chart-countries"',
'id="traffic-chart-client-ips"',
'id="traffic-tabs"',
'data-traffic-tab="overview"',
'data-traffic-tab="clients"',
'data-traffic-tab="requests"',
'data-traffic-tab="delivery"',
'id="traffic-chart-user-agents"',
'id="traffic-chart-paths"',
'id="traffic-chart-routes"',
'id="traffic-chart-volume"',
'id="log-capture-user-agent"',
'id="log-capture-country"',
'id="cache-auto-trim-enabled"',
'id="settings-tab-logging"',
'id="settings-page-logging"',
'id="logging-form"',
'id="error-log-form"',
'id="settings-tab-administration"',
'id="settings-tab-tls-policy"',
'id="admin-tls"',
'id="tls-policy-12"',
'id="tls-policy-13"',
'id="cache-resident-age-header"',
'id="cache-cold-enabled"',
'id="cache-transient-memory"',
'id="cache-cold-queue-bytes"',
'id="cache-eviction-limit"',
'data-cache-memory-preset="recommended"',
'id="cache-cold-path"',
'id="cache-cold-bytes"',
'id="cache-cold-entries"',
'id="cp-hot-entries-meter"',
'id="cp-memory"',
'id="cp-cold-entries-meter"',
'id="cp-cold-disk"',
'id="cache-cold-after"',
'id="log-format-preset"',
'id="log-format-open"',
'id="logging-format-dialog"',
'id="logging-field-rows"',
'id="panel-acl-profiles"',
'id="acl-profile-form"',
'id="acl-profile-list"',
'id="acl-profile-rule-rows"',
'id="acl-profile-rule-add"',
'id="acl-profile-import-rules"',
'id="vhost-search"',
'id="panel-redirects"',
'id="redirect-search"',
'id="redirect-list"',
'id="redirect-new"',
'id="redirect-dialog"',
'id="redirect-form"',
'id="redirect-rules"',
'id="redirect-rule-add"',
'id="panel-notfound"',
'id="notfound-search"',
'id="notfound-list"',
'id="notfound-new"',
'id="notfound-dialog"',
'id="notfound-form"',
'id="notfound-hosts-editor"',
'id="notfound-listeners"',
'id="notfound-certificate"',
'id="notfound-mode"',
'id="notfound-status"',
'id="panel-streams"',
'id="stream-search"',
'id="stream-list"',
'id="stream-new"',
'id="stream-listener-add"',
'id="stream-dialog"',
'id="stream-form"',
'class="vhost-dialog logging-format-dialog"',
]
for marker in required_html:
assert marker in html, marker
assert '/api/traffic' in traffic and 'rustProxyTrafficPanel' in traffic
for marker in ['function selectTab(', 'function renderDonut(', 'function renderBars(', 'renderBars("client-ips"', 'renderBars("user-agents"', 'renderBars("paths"', 'renderBars("routes"', 'renderBars("volume"']:
assert marker in traffic, marker
assert 'traffic: "/traffic"' in app and 'window.rustProxyTrafficPanel?.refresh()' in app
for marker in ['id="settings-tab-manual-config"', 'id="settings-page-manual-config"', 'id="config-editor"', 'id="save-json"', 'id="export-config"']:
assert marker in html, marker
assert 'data-panel="configuration"' not in html and 'id="panel-configuration"' not in html
assert html.index('data-panel="vhosts"') < html.index('data-panel="redirects"') < html.index('data-panel="notfound"') < html.index('data-panel="streams"') < html.index('data-panel="acl-profiles"')
assert "matching_resident_bytes" in cache and "matching_body_bytes" in cache
for marker in ["data.settings?.max_entries", "data.settings?.cold_max_entries", "data.settings?.cold_max_bytes", "setMeter("]:
assert marker in cache, marker
for marker in ["cache-memory-breakdown", "cache-transient-breakdown", "cache-cold-storage", "cache-transient-sparkline", "cache-fill-sparkline", "cache-queue-sparkline", "cache-io-sparkline", "cache-cold-sparkline", "metric-info", "process-rss-total", "memory-cache-hot", "memory-cache-transient", "memory-transform", "memory-transform-inflight", "memory-non-cache-rss", "process-rss-anon", "process-rss-file", "process-data-swap", "memoryAccountingHint"]:
assert marker in html, marker
for marker in ["transient_bytes", "fill_bytes_active", "cold_queue_bytes", "cold_write_bytes_active", "cold_read_bytes_active", "process_rss_anon_bytes", "process_rss_file_bytes"]:
assert marker in app or marker in cache, marker
assert "matching_cold_bytes" in cache and "cold_promotions" in cache and "hot_demotions" in cache
assert "next.logging" in app and "logstash:" in app and "loki:" in app and "file:" in app
assert "LOGGING_PRESETS" in app and "selectedLoggingMapping" in app and "renderLoggingPreview" in app
assert "administration" in settings and "tls-policy" in settings
assert "additional_addresses" in integrations
assert "addressTagEditor" in integrations and "listener-address-chip-editor" in integrations
assert 'const h2 = field(grid, "listener-h2", "vhHttp2"' in integrations
assert 'h2Configure' in integrations and 'selectHttpTab?.("http2")' in integrations
assert 'renderHttp2Settings' in integrations and 'collectHttp2Listeners' in integrations
assert 'const h3 = field(grid, "listener-h3", "h3Enable"' in integrations
assert 'h3Configure' in integrations and 'selectHttpTab?.("http3")' in integrations
h3_tuning = integrations.split('function http3SettingsControls', 1)[1].split('function addRow', 1)[0]
assert 'h3Enable' not in h3_tuning
assert 'data-i18n="processDiagnostics">Process<' in html
for marker in ['function setOptionalMetric', 'setOptionalMetric("process-rss-anon"', 'setOptionalMetric("system-swap"', 'setOptionalMetric("system-network"']:
assert marker in app, marker
assert 'const http80 = prefer(false, 80);' in integrations
assert 'if (http80) autoIds.add(http80.id);' in integrations and 'candidates.find(endpoint => !endpoint.binding?.tls)' in integrations
assert 'https443' not in integrations
assert 'window.rustProxyIntegrations.bindings(holder, selected, !editing)' in redirects
assert 'window.rustProxyIntegrations.hostnames(hostHolder' in redirects
assert 'function hostnames(parent, initial = [], options = {})' in integrations
assert 'class="protocol-toggle-row"' in html
assert "stream-host-rows" not in integrations and 'id="settings-page-streams"' not in html and 'id="streams-form"' not in html
for marker in ["rustProxyStreams", "streamTrafficChart", "streamPendingRestart", "chartHistory", "sessions_total", "bytes_in", "bytes_out"]:
assert marker in streams, marker
assert '$("stream-listener-add").addEventListener("click", () => { setPanel("settings"); window.rustProxySettings?.select("listeners"); });' in streams
assert "/assets/streams.js" in html
assert "/assets/redirects.js" in html
assert "/assets/host404.js" in html
for marker in ["rustProxyRedirects", 'saveSection("redirects"', "redirectTargetType", "redirectPathMode", "redirectQueryMode", "redirectCacheSeconds"]:
assert marker in redirects, marker
for marker in ["rustProxyNotFound", 'saveSection("not_found_hosts"', "rustProxyIntegrations.hostnames", "rustProxyIntegrations.bindings", "notfound-mode", "notfound-status", "BUILTIN_STATUSES", "ERROR_STATUSES", 'rustProxyDrafts.register("notfound"', 'rustProxyEnableControl($("notfound-enabled"))']:
assert marker in host404, marker
assert 'for (let code = 200; code <= 599; code++)' not in host404
assert 'id="docker-search"' in html and 'id="docker-show-hidden"' in html
assert 'preserve_host: true' in vhosts
assert "tls_policy" in vhosts and "vhost-live-grid" in vhosts
assert '"force-ssl", "vhForceSsl"' in vhosts and 'force_ssl: yes("force-ssl")' in vhosts
assert '"forward-client-ip", "vhForwardClientIp"' in vhosts and 'forward_client_ip: yes("forward-client-ip")' in vhosts
assert "vhForceSslNeedsTls" in vhosts and "vhForceSslNeedsHttp" in vhosts and "vhForceSslPortConflict" in vhosts
assert "tlsPorts.has(443)" in vhosts
assert '$("vhost-search").addEventListener("input", render)' in vhosts and "vhSearchEmpty" in vhosts
for marker in ['toggleVhostEnabled', 'vhQuickEnable', 'vhQuickDisable', 'backend_table_columns', 'applyPoolTableView', 'rustProxyEnableControl']:
assert marker in vhosts, marker
assert 'rustproxy-backend-table-v1' not in vhosts and 'poolTablePreferenceKey' not in vhosts
assert 'window.rustProxyPreferences.update({' in vhosts and 'backend_table_sort' in vhosts and 'backend_table_descending' in vhosts
for marker in ['id="vhost-sort"', 'id="vhost-sort-direction"', 'id="docker-vhost-sort"', 'id="docker-vhost-sort-direction"', 'id="redirect-sort"', 'id="redirect-sort-direction"', 'id="notfound-sort"', 'id="notfound-sort-direction"', 'id="stream-sort"', 'id="stream-sort-direction"']:
assert marker in html, marker
preferences_source = (ROOT / 'src/admin/preferences.rs').read_text(encoding='utf-8')
for marker in ['vhost_list_sort', 'docker_vhost_sort', 'redirect_list_sort', 'notfound_list_sort', 'stream_list_sort']:
assert marker in preferences_source, marker
assert 'vhost_list_sort' in vhosts and 'vhost_list_descending' in vhosts
list_sort_sources = {
'docker': (ROOT / 'src/admin/web/docker.js').read_text(encoding='utf-8'),
'redirects': (ROOT / 'src/admin/web/redirects.js').read_text(encoding='utf-8'),
'notfound': (ROOT / 'src/admin/web/host404.js').read_text(encoding='utf-8'),
'streams': (ROOT / 'src/admin/web/streams.js').read_text(encoding='utf-8'),
}
assert 'docker_vhost_sort' in list_sort_sources['docker'] and 'sortServiceRows' in list_sort_sources['docker']
assert 'redirect_list_sort' in list_sort_sources['redirects']
assert 'notfound_list_sort' in list_sort_sources['notfound']
assert 'stream_list_sort' in list_sort_sources['streams']
for marker in ['id="pool-search"', 'id="pool-sort"', 'data-pool-column-toggle="vhost"']:
assert marker in html, marker
for marker in ['https://git.linuxiarz.pl/gru/RustProxy', 'https://rustproxy.linuxiarz.pl/', 'id="about-python-runtime"', 'id="runtime-python"', 'id="acme-runtime-components"']:
assert marker in html, marker
for language in ['langs/en_US.json', 'langs/pl_pl.json']:
text = (ROOT / language).read_text(encoding='utf-8')
assert './dev.sh tls deploy' not in text
assert '/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy tls deploy' in text
startup_source = (ROOT / 'src/main/admin_runtime.rs').read_text(encoding='utf-8')
for marker in ['https://git.linuxiarz.pl/gru/RustProxy', 'https://rustproxy.linuxiarz.pl/', 'join("acme-python")', 'certificate_runtime']:
assert marker in startup_source, marker
for document in ['docs/RENEWAL.md', 'docs/INSTALL.md', 'docs/API.md']:
document_text = (ROOT / document).read_text(encoding='utf-8')
assert './dev.sh tls deploy' not in document_text
assert '/opt/rustproxy/current/rust-proxy --data-dir /var/lib/rustproxy tls deploy' in document_text
style = (ROOT / "src/admin/web/style.css").read_text(encoding="utf-8")
for marker in ['--scrollbar-thumb', '*::-webkit-scrollbar-thumb', '.backend-table-tools', '.vhost-quick-toggle', '.dashboard-widget-toggle', '--toolbar-control-height: min(var(--button-min-height), var(--ui-field-min-height))', 'height: var(--toolbar-control-height)']:
assert marker in style, marker
assert "pool-vhost-disabled" in vhosts
for marker in ["locationMatchRegex", "headersVisualBuilder", "headersAdvancedJson", "openLocationHelp", "cloudflare-client-ip", "timing-headers"]:
assert marker in vhosts, marker
for marker in ["location-help-dialog", "cache-memory-sparkline", "network-rx-sparkline", "latency-sparkline", "sourceIp"]:
assert marker in html, marker
for marker in ["lastApiLatencyMs", "cacheMemorySamples", "networkRxSamples", "responseLatencySamples"]:
assert marker in app, marker
assert '<details class="mini-live-chart mini-live-chart-collapsible">' not in html
assert 'data-i18n="responseLatencyHistory"' in html and 'data-i18n="responseLatencyHint"' in html and 'data-i18n-aria="responseLatencyInfo"' in html
assert 'bindSettingsForm("error-log-form"' in app and '}, "logging", selectLoggingSection);' in app
assert 'viewBox: "0 0 300 60"' in app and 'M0 15H300 M0 30H300 M0 45H300' in app
assert '.network-interface-row {' in css and 'background: transparent' in css
assert "hostTagEditor" in vhosts and "vhost-chart-line" in vhosts and "vhLifetimeChart" in vhosts
for marker in ["refreshCertificateInventory", "certificateDetails", "certificate-domain-pills", "certificateCoverageUnknown", "async function revalidateSession", 'Object.values(PANEL_PATHS), "/configuration"', "recoverInvalidOrigin", "auth.invalid_origin", "ORIGIN_RECOVERY_KEY"]:
assert marker in app, marker
for marker in ['data-certificate-tab="installed"', 'data-certificate-tab="renewal"', 'data-certificate-tab="generate"', 'id="certificate-page-installed"', 'id="certificate-page-renewal"', 'id="certificate-page-generate"']:
assert marker in html, marker
assert 'id="certificate-dialog"' not in html and 'id="certificate-close"' not in html
for marker in ['function selectTab(name, focus = false, persist = true)', 'log_directories', 'certbotLogs', 'value?.kind?.startsWith("renewal")']:
assert marker in acme_ui, marker
for marker in ['id="acme-runtime-card"', 'id="acme-runtime-message"', 'id="acme-runtime-state"', 'id="acme-runtime-path"', 'rust-proxy --data-dir ./data admin setup-token', 'rust-proxy --data-dir /var/lib/rustproxy admin setup-token']:
assert marker in html, marker
for marker in ['function renderRuntime(value)', 'runtimeAvailable', 'certificateRuntimeUnavailable']:
assert marker in acme_ui, marker
for marker in ['.certificate-operation-card', '.renewal-meta', '#panel-tls .certificate-tabs']:
assert marker in css, marker
login = (ROOT / "src/admin/web/login.js").read_text(encoding="utf-8")
settings_ui = (ROOT / "src/admin/web/settings.js").read_text(encoding="utf-8")
assert "async function redirectExistingSession" in login and "/api/auth/session" in login
assert '["/settings", "/configuration", "/logging"].includes(location.pathname)' in settings_ui
assert 'new URLSearchParams(location.search).get("tab")' in settings_ui and 'function syncLocation()' in settings_ui and 'history.replaceState' in settings_ui
assert '"/docker-vhosts"' in login and 'settingsTabs = new Set' in login and '`/settings?tab=${encodeURIComponent(tab)}`' in login
login_html = (ROOT / 'src/admin/web/login.html').read_text(encoding='utf-8')
assert 'class="auth-page auth-loading"' in login_html and 'document.body.classList.remove("auth-loading")' in login
assert 'class="app-loading"' in html and 'document.documentElement.classList.remove("app-loading")' in app
assert 'url.pathname = "/settings"' in settings_ui and 'url.searchParams.set("tab", active)' in settings_ui
assert 'function withFormSubmitState(form, submitter, task)' in app and 'button.textContent = t("saving")' in app
assert 'function fieldError(target, message)' in app and 'function showFieldError(error, form = null)' in app
assert 'function authReturnDestination()' in app and 'pendingAuthDestination || authReturnDestination()' in app
assert '$("login").hidden = navigating' in app and 'id="auth-message"' in html
assert 'settings-tabs button.has-unsaved::after' in css and 'function syncSettingsDirtyIndicators()' in app
assert 'setSettingsDirty("config-editor")' in app and '!settingsDirty.has("config-editor")' in app
assert 'throw fieldError($("tls-policy-12")' in settings_ui
assert 'input[type="checkbox"],\ninput[type="radio"] {' in css and 'width: 15px;' in css and '.log-column-option input[type="checkbox"]' not in css and 'overflow: visible' in css and 'max-height: min(38dvh, 220px)' not in css
assert '.log-tools input:not([type="checkbox"]):not([type="radio"])' in css
assert '.log-tools input {\n' not in css
footer = app.split('function renderFooter(s) {', 1)[1].split('let lastLogs', 1)[0]
assert 'SQLite / HTTP/1.1' not in footer and 'coldQ:' not in footer and 'streamFallback' not in footer
assert '`${apiLatency} / ${t("logsDropped")}: ${s.logs_dropped}`' in footer
assert 'update?.update_available && update.latest_version' in footer and 'footer-update' in footer
assert '#docker-agents-form>.form-actions {' in css and 'justify-content: flex-start' in css
for marker in ['prom-trusted-no-token', 'prom-streams', 'prom-logging']:
assert marker in html and marker in settings_ui, marker
for marker in [".certificate-details", ".certificate-domain-pills", ".certificate-card .pill"]:
assert marker in css, marker
integrations_0194 = (ROOT / "src/admin/web/integrations.js").read_text(encoding="utf-8")
docker_0194 = (ROOT / "src/admin/web/docker.js").read_text(encoding="utf-8")
assert 'readAddresses.input' in integrations_0194
assert 'form.reset(); window.rustProxyCertificates?.close();' in integrations_0194
assert '$("cert-import-pem").value = ""' not in integrations_0194 and '$("cert-import-key").value = ""' not in integrations_0194
assert 'throw fieldError(row.querySelector(".docker-agent-listener-choices")' in docker_0194
management = (ROOT / "src/admin/web/management.js").read_text(encoding="utf-8")
assert "rustProxyAclProfiles" in management and "acl_profiles" in management
for marker in ["collectRuleRows", "parseBulkRules", "allow_exceptions", "block_exceptions", "renderPreview", "normalizeNetwork"]:
assert marker in management, marker
assert "function refreshTranslations()" in management and "refreshTranslations }" in management
assert "window.rustProxyAclProfiles?.refreshTranslations?.();" in app
assert "normalizeAclRuleText" in app and "ruleSyntaxLine" in app
assert ".settings-tabs" in css and "grid-template-columns" in css and "width: 100%" in css
assert ".logging-grid fieldset" not in css
assert ".pool-vhost-disabled" in css and ".acl-profile-rule" in css
assert ".stream-live-grid" in css and ".stream-chart-rx" in css and ".listener-address-chip-editor" in css
for marker in ['.vh-backend-card', '.auth-subcard', '.vh-cache-options[hidden]', '.protocol-toggle-row', '.listener-choices']:
assert marker in css, marker
assert html.count('<h3 class="subheading"') >= 3
def source_contract_smoke() -> None:
config = rust_source(ROOT, "src/config.rs")
tls = rust_source(ROOT, "src/tls.rs")
main = rust_source(ROOT, "src/main.rs")
server = rust_source(ROOT, "src/server.rs")
logging = rust_source(ROOT, "src/logging.rs")
secret_store = rust_source(ROOT, "src/secret_store.rs")
backup = rust_source(ROOT, "src/backup.rs")
acme_worker = (ROOT / "scripts/acme_provider.py").read_text(encoding="utf-8")
for feature_marker in ["AES_256_GCM", "master.key", "O_NOFOLLOW", "migrate_acme_credentials"]:
assert feature_marker in secret_store, feature_marker
for feature_marker in ["Argon2", "RPROXYB1", "secret-store-key.enc", "validate_backup_version"]:
assert feature_marker in backup, feature_marker
for feature_marker in ["ErrorLogSettings", "ERROR_LOG_QUEUE", "RedactingStderr", "open_error_file", "ErrorLogLayer"]:
assert feature_marker in logging, feature_marker
assert "credential_ini_b64" in acme_worker and "credential_file.unlink" in acme_worker
cache = read_cache_source()
settings = rust_source(ROOT, "src/settings.rs")
websocket = rust_source(ROOT, "src/proxy/websocket.rs")
proxy = rust_source(ROOT, "src/proxy/mod.rs")
assert '.insert("x-rustproxy-backend-time", value)' in proxy and '.insert("x-rust-proxy-backend-time", value)' not in proxy
pages = rust_source(ROOT, "src/pages.rs")
admin = rust_source(ROOT, "src/admin/mod.rs")
stream = rust_source(ROOT, "src/stream.rs")
runtime = rust_source(ROOT, "src/runtime.rs")
acl = rust_source(ROOT, "src/acl.rs")
policies = (ROOT / "tests/policies.rs").read_text(encoding="utf-8")
http3 = rust_source(ROOT, "src/http3.rs")
redirect = rust_source(ROOT, "src/redirect.rs")
not_found = rust_source(ROOT, "src/not_found.rs")
prometheus = rust_source(ROOT, "src/prometheus.rs")
auth = rust_source(ROOT, "src/auth/mod.rs")
auth_store = rust_source(ROOT, "src/auth/store.rs")
upstream = rust_source(ROOT, "src/upstream.rs")
vhosts_js = read_vhosts_js(ROOT)
docker_source = rust_source(ROOT, "src/docker.rs")
docker_js = (ROOT / "src/admin/web/docker.js").read_text(encoding="utf-8")
docker_agent = read_docker_agent_source()
html = (ROOT / "src/admin/web/index.html").read_text(encoding="utf-8")
cache_panel_ui = (ROOT / "src/admin/web/cache-panel.js").read_text(encoding="utf-8")
transform_panel = (ROOT / "src/admin/web/transform-panel.js").read_text(encoding="utf-8")
app_js = read_app_js(ROOT)
# 0.24.1: shared library statistics, authenticated runtime metadata and Logging in Settings.
manifest = tomllib.loads((ROOT / "Cargo.toml").read_text(encoding="utf-8"))
system_stats = rust_source(ROOT, "src/system_stats/mod.rs")
linux_stats = rust_source(ROOT, "src/system_stats/linux.rs")
settings_ui = (ROOT / "src/admin/web/settings.js").read_text(encoding="utf-8")
openapi_0241 = (ROOT / "src/admin/openapi.json").read_text(encoding="utf-8")
assert not (ROOT / "src/system_stats/bsd.rs").exists()
metrics_process = manifest["dependencies"]["metrics-process"]
assert metrics_process["version"] == "=2.4.3"
assert metrics_process["default-features"] is False
assert metrics_process["features"] == ["dummy"]
sysinfo_network = manifest["dependencies"]["sysinfo"]
assert sysinfo_network["version"] == "=0.38.4"
assert sysinfo_network["default-features"] is False
assert sysinfo_network["features"] == ["network"]
target_key = 'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "macos", target_os = "windows"))'
assert manifest["target"][target_key]["dependencies"]["systemstat"] == "=0.2.7"
for marker in [
"systemstat::{Platform, System}",
"metrics_process::collector::collect",
"sysinfo::Networks::new_with_refreshed_list",
"collect_network_systemstat",
"merge_network_interfaces",
"system_swap_total_bytes",
"system_uptime_seconds",
"process_virtual_memory_bytes",
"process_start_time_seconds",
"max_fds",
]:
assert marker in system_stats, marker
for forbidden in ["sysctl", "netstat", "Command::new"]:
assert forbidden not in system_stats, forbidden
for marker in ["/proc/stat", "/proc/meminfo", "RssAnon", "VmData", "VmSwap", "pub(crate) fn augment"]:
assert marker in linux_stats, marker
for marker in [
"LinuxEnvironment::Lxc", "process_start_ticks", "CLOCK_BOOTTIME", "/proc/1/stat",
"/proc/loadavg", "lxc_load_average", "view.environment = Some(environment.label())",
"if environment == LinuxEnvironment::Lxc",
]:
assert marker in linux_stats, marker
assert 'const processPlatform = [d.platform, d.environment].filter(Boolean).join(" / ");' in app_js
for marker in ["installed_data_dir_candidates", "resolve_installed_data_dir", '"/var/lib/rustproxy"', '"/var/db/rustproxy"']:
assert marker in main, marker
for marker in ['payload["runtime"]', '"statistics_source"', '"data_directory"', '"database_path"']:
assert marker in admin, marker
anonymous_block = admin.split("fn anonymous_allowed", 1)[1].split("const CERTIFICATE_API_KEY_HEADER", 1)[0]
assert '"/api/status"' not in anonymous_block
assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.27.2"' in openapi_0241
assert 'id="panel-logging"' not in html
for marker in ['id="settings-tab-logging"', 'id="settings-page-logging"', 'id="logging-form"', 'id="error-log-form"']:
assert marker in html, marker
assert 'location.pathname === "/logging"' in settings_ui
# 0.22.3 h2c Upgrade and externally owned certificate deployment contracts.
for marker in [
'StatusCode::SWITCHING_PROTOCOLS',
'struct H2cUpgradeIo',
'HTTP2-Settings',
'http1_h2c_upgrade_bootstraps_request_as_http2_stream_one',
'malformed_h2c_upgrade_is_rejected_before_switching_protocols',
]:
assert marker in server, marker
for marker in [
'"/api/certificates/reload"',
'certificate_reload',
'"/api/certificates/deploy"',
'certificate_deploy',
'certificate.deploy',
'certificate.reload',
'reload_certificates().await',
'"operation": "unchanged"',
'config.tls.certificates.push(crate::tls::Certificate',
]:
assert marker in admin, marker
for marker in ['pub async fn reload_certificates', 'configured certificates reloaded from disk']:
assert marker in runtime, marker
for marker in ['TlsCommand::Deploy', '/api/certificates/deploy', '/api/certificates/reload', 'read_external_pem']:
assert marker in main, marker
for marker in ['pub fn stage_external_pair', 'pub fn reconcile_external_pair', 'ExternalPairDeployment::Unchanged', 'pub fn remove_external_pair', 'external_pair_stages_generation_and_rolls_back_cleanly', 'external_pair_reconcile_is_idempotent_and_repairs_missing_generation']:
assert marker in tls, marker
assert 'api("/api/certificates/reload"' in app_js
transport_0223 = (ROOT / "docs/TRANSPORT.md").read_text(encoding="utf-8")
assert 'Upgrade: h2c' in transport_0223 and 'stream 1' in transport_0223
assert 'legacy HTTP/1.1 Upgrade-to-h2c is not implemented' not in transport_0223
audit_0233 = (ROOT / "docs/V0.24.0_AUDIT.md").read_text(encoding="utf-8")
audit_0232 = (ROOT / "docs/V0.23.2_AUDIT.md").read_text(encoding="utf-8")
audit_0223 = (ROOT / "docs/V0.22.3_AUDIT.md").read_text(encoding="utf-8")
for marker in ["legacy websocket", "origin", "http/1.1", "sec-websocket-accept", "ilo4"]:
assert marker.lower() in audit_0233.lower(), marker
for marker in ["600 samples", "308", "HTTPS", "<data-dir>/certs", "--livelogs"]:
assert marker.lower() in audit_0232.lower(), marker
assert 'external certificate ownership model' in audit_0223.lower() and 'release gate' in audit_0223.lower()
update_check = rust_source(ROOT, "src/update_check.rs")
update_defaults = tomllib.loads((ROOT / "UPDATE_CHECK.toml").read_text(encoding="utf-8"))
assert update_defaults["repository_url"] == "https://git.linuxiarz.pl/gru/RustProxy"
assert update_defaults["version_url"].endswith("/raw/branch/master/Cargo.toml")
assert update_defaults["interval_seconds"] == 3600
for marker in ['include_str!("../UPDATE_CHECK.toml")', 'pub async fn poll_loop', 'tokio::time::sleep(interval)', 'package_version(&document)', 'MAX_VERSION_DOCUMENT_BYTES']:
assert marker in update_check, marker
assert 'update version document is too large or incomplete: {error}' in update_check
assert '.context("update version document is too large or incomplete")' not in update_check
for marker in ['tracing::error!(', 'MAX_REDIRECTS: usize = 5', 'SSL_CERT_FILE', 'CURL_CA_BUNDLE', '/usr/local/share/certs/ca-root-nss.crt', 'response.status().is_redirection()', 'redirect_target(&current, location)']:
assert marker in update_check, marker
assert 'pub last_error:' not in update_check
for marker in ['RUST_PROXY_UPDATE_REPOSITORY_URL', 'RUST_PROXY_UPDATE_VERSION_URL', 'RUST_PROXY_UPDATE_CHECK_INTERVAL_SECS', 'state.start_update_check(update_check);']:
assert marker in main, marker
assert 'pub software_update: Arc<crate::update_check::SharedStatus>' in runtime
assert 'payload["software_update"] = json!(state.software_update.snapshot());' in admin
for marker in [
"a persisted remote administration listener requires administration TLS",
"validate_narrower_than",
"tls_policy",
"AclProfile",
"normalize_host_pattern",
"profile_country_access",
"https_redirect_port",
"forward_client_ip",
"plain HTTP listener",
"tls_ports.contains(&443)",
"backend_tls_verification_defaults_off_and_can_be_enabled_explicitly",
"pub accept_local_listener_risk: bool",
"backend_diagnostic_flags_compile_to_frontend",
"backend_header: host.backend_header",
"backend_timing_header: host.backend_timing_header",
]:
assert marker in config, marker
for marker in ["compile_admin", "ProtocolPolicy", "policy_only_configuration_does_not_require_certificate_directory", "certificate_covered_hosts", "legacy_certificate_coverage_can_be_read_from_existing_pem"]:
assert marker in tls, marker
assert "compile_admin" in main and "serve_tls_with_plain_redirect" in main
assert "TlsMode::Fixed" in server and "TlsMode::Frontend" in server and "plaintext_router" in server
assert "additional_addresses" in settings and "multi-address listeners require explicit IPs" in settings
for marker in ["pub session_days: u16", "session_days: 30", "(7..=30).contains(&self.session_days)", "administration_session_lifetime_is_7_to_30_days_and_hot_apply_only"]:
assert marker in settings, marker
for marker in ["30 * 24 * 60 * 60", "browser_session_lifetime_is_configurable_without_sliding_expiry", "session_cookie_uses_configured_remaining_lifetime"]:
assert marker in auth, marker
for marker in ["create_browser_session", "session_at_with_browser_lifetime", "browser session lifetime must be 7..30 days"]:
assert marker in auth_store, marker
for marker in ["accept_local_listener_risk", "!self_target || self.accept_local_listener_risk", "explicit_risk_acceptance_allows_local_listener_but_not_private_policy_bypass"]:
assert marker in upstream, marker
assert "member-local-risk" in vhosts_js and "vhLocalListenerRiskConfirm" in vhosts_js
assert 'function administrationListenCandidates()' in vhosts_js and 'function adminListenerBackendCandidate(raw)' in vhosts_js
for marker in ['latestStatus?.administration?.locked_listen', 'latestStatus?.administration?.active?.listen', 'latestStatus?.admin_listen', "value === 'localhost'", '/^127(?:\\.\\d{1,3}){3}$/.test(value)', 'riskGate.hidden = show !== true', 'row.syncLocalRisk = () => syncRisk(false)']:
assert marker in vhosts_js, marker
assert 'const saved = editing ? (envelope?.config.vhosts || []).find(item => item.id === editing) : null;' in vhosts_js
assert 'load(saved ? structuredClone(saved) : defaults());' in vhosts_js
assert 'backends: [{ id: "origin-1", url: ""' in vhosts_js
assert 'preserve_host: true' in vhosts_js
for marker in ['vhBackendDiagnostics', 'backend_header: yes("backend-header")', 'backend_timing_header: yes("backend-timing-header")', 'protocol-toggle-row', 'auth-subcard', 'vh-cache-options', 'syncCacheVisibility', 'cache: { enabled: false }']:
assert marker in vhosts_js, marker
assert 'values.length ? values : [{ id: "root"' not in vhosts_js
for marker in ["PinnedCertificateVerifier", "tls_fingerprint_sha256", "health_check: crate::balance::HealthCheck", "certificate fingerprint mismatch", "generated_id"]:
assert marker in docker_source, marker
for marker in ["docker-agent-fingerprint", "docker-agent-certificate", "docker-health-mode", "docker-search", "docker-show-hidden", "pool_routes", "dockerBackendUp", "setInterval"]:
assert marker in docker_js, marker
assert 'function checkboxField(key, control)' in docker_js
assert 'checkboxField("dockerPreserveHost", preserve)' in docker_js and 'checkboxField("dockerAclInline", inline)' in docker_js
assert 'label = el("label", undefined, "check vh-check")' in docker_js
ui_css = (ROOT / "src/admin/web/style.css").read_text(encoding="utf-8")
assert '.docker-route-grid input:not([type="checkbox"])' in ui_css and '.docker-checkbox-field .vh-check {' in ui_css
assert 'docker-switch-row' in docker_js and '.docker-switch-row {' in ui_css and 'repeat(5, minmax(110px, 160px))' in ui_css
assert '.docker-passive-health-editor .docker-health-grid {' in ui_css
assert '.docker-health-grid input:not([type="checkbox"])' in ui_css and '.docker-passive-health-editor .docker-checkbox-field input[type="checkbox"]' in ui_css
assert 'id="sparkline-transform"' in html and 'id="rate-transform"' in html
assert 'id="panel-transform"' in html and 'data-panel="transform"' in html and '/assets/transform-panel.js' in html
assert '/api/transform' in transform_panel and 'tp-cache-hits' in transform_panel
for marker in ['transform-memory-budget', 'transform-max-inflight', 'data-transform-preset="conservative"', 'data-transform-preset="recommended"', 'data-transform-preset="performance"', 'data-transform-preset="uber"']:
assert marker in html, marker
for marker in ['selectedTransformPreset', 'transformPresetValues', 'applyTransformPreset', 'updateTransformResourceSizing', 'memory_budget_bytes', 'max_inflight']:
assert marker in app_js, marker
assert '<details class="card function-live-chart" id="cache-live-chart">' in html
assert '<details class="card function-live-chart" id="transform-live-chart">' in html
for marker in ['cache-live-hit-line', 'cache-live-miss-line', 'cache-live-bypass-line']:
assert marker in html and marker in cache_panel_ui, marker
for marker in ['transform-live-applied-line', 'transform-live-cache-line', 'transform-live-skipped-line', 'transform-live-error-line']:
assert marker in html and marker in transform_panel, marker
assert 'sparklinePoints(liveChartSamples.hits, 900, 180, 10, peak)' in cache_panel_ui
assert 'sparklinePoints(liveChartSamples.applied, 900, 180, 10, peak)' in transform_panel
assert '.function-live-chart svg {' in ui_css and 'height: clamp(180px, 22vw, 270px)' in ui_css
assert '--transform-chart:' in ui_css and '--chart-quaternary:' in ui_css and 'stroke: var(--chart-quaternary)' in ui_css and 'background: var(--transform-chart)' in ui_css
assert '.cache-transform-badge {' in ui_css and 'border: 1px solid var(--pill-line)' in ui_css and 'border-radius: 3px' in ui_css
html = (ROOT / 'src/admin/web/index.html').read_text(encoding='utf-8')
cache_panel_js = (ROOT / 'src/admin/web/cache-panel.js').read_text(encoding='utf-8')
management_js = (ROOT / 'src/admin/web/management.js').read_text(encoding='utf-8')
app_js = read_app_js(ROOT)
vhosts_js = read_vhosts_js(ROOT)
cache_rs = read_cache_source()
admin_rs = rust_source(ROOT, 'src/admin/mod.rs')
logging_rs = rust_source(ROOT, 'src/logging.rs')
assert 'bindSettingsForm("pages-form", next => { next.pages = global.read(); }, "pages")' in management_js
assert '"pages" => {' in admin_rs and 'default_pages_section_accepts_the_standard_value_payload' in admin_rs
assert 'pub const LIVE_WINDOW: usize = 256;' in logging_rs and 'pub const HISTORY_CAPACITY: usize = 4096;' in logging_rs
assert 'api("/api/logs?limit=4096")' in management_js
assert 'const STATUS_NAMES = { 200: "OK"' in management_js and '404: "Not Found"' in management_js and 'statusLabel(code)' in management_js
assert 'let paused = null, expanded = null' in management_js and 'function mergeExpanded(data)' in management_js and 'entries.length >= 4096' in management_js
assert 'dialog.addEventListener("close", () => { if (pinning) return; expanded = null; paused = null;' in management_js
assert 'hash_scheme.as_bytes()' in cache_rs and 'cache_key_separates_frontend_scheme' in cache_rs
assert not (ROOT / 'src/cache.rs').exists() and (ROOT / 'src/cache/mod.rs').is_file()
cache_mod = rust_source(ROOT, 'src/cache/mod.rs')
assert 'impl Cache {\n include!(' not in cache_mod
for fragment in ['lifecycle.rs', 'cold.rs', 'dashboard.rs', 'tickets.rs', 'lookup.rs']:
assert rust_source(ROOT, f'src/cache/{fragment}').lstrip().startswith('impl Cache {')
assert 'payload["transform_metrics"] = json!(state.transform_metrics.total())' in admin_rs
for marker in ['payload["transform_resources"]', 'payload["memory_accounting"]', 'non_cache_rss_includes_transform', 'cold_limit_bytes', 'cold_limit_entries']:
assert marker in admin_rs or marker in cache, marker
assert 'const transformed = s.transform_metrics || {}' in app_js and 'transformSamples.push(rates.transform)' in app_js
for marker in ['status.memory_accounting || {}', 'memory.cache_hot_bytes', 'memory.transform_reserved_bytes', 'cache-cold-storage']:
assert marker in app_js, marker
for marker in ['data?.resources || {}', 'tp-memory', 'tp-inflight']:
assert marker in transform_panel, marker
assert 'function uptime(value)' in app_js and 'setOptionalMetric("process-uptime", status.uptime_seconds != null' in app_js
assert 'connection-uptime-footer' not in html and 'id="process-uptime"' in html
assert 'function rateBytes(value)' in app_js and 'if (value === 0) return "0.0 KiB";' in app_js and 'return "<0.1 KiB";' in app_js
assert 'function backendTrafficRate(' in vhosts_js and 'member.bytes_in' in vhosts_js and 'member.bytes_out' in vhosts_js and 'rateBytes(memberTraffic)' in vhosts_js and 'rateBytes(d.traffic)' in vhosts_js
assert 'id="cache-domain-filter"' in html and 'cacheDomainFilterEnabled' in html and 'cacheDomainFilterDisabled' in html
assert 'const visibleDomains = (data, groups) =>' in cache_panel_js and 'scoped?.settings?.enabled ?? data.settings?.enabled ?? false' in cache_panel_js
for marker in ['effectiveCacheMemory', 'cacheMemoryHeadroom', 'cachePresetValues', 'applyCacheMemoryPreset', 'selectedCacheMemoryPreset', 'selectedCacheColdPreset', 'applyRecommendedColdCachePreset', 'cacheMemoryPresetTooSmall', 'cacheObjectOverTransientWarning', 'cgroup_memory_max_bytes', 'transient_max_bytes', 'cold_queue_max_bytes', 'hot_eviction_max_objects']:
assert marker in app_js, marker
assert 'envelope?.config.cache?.enabled === true && cacheToggle.value !== "off"' in vhosts_js
assert 'groups = d.domains || [], shownGroups = visibleDomains(data, groups)' in cache_panel_js and 'replaceChildren(...shownGroups.map(row =>' in cache_panel_js
balance_rs = rust_source(ROOT, 'src/balance.rs')
assert 'traffic_rate_drops_to_zero_after_an_idle_sample_window' in balance_rs and 'if delta == 0 {' in balance_rs and 'sample.rate = 0.0;' in balance_rs
all_admin_js = "\n".join(path.read_text(encoding='utf-8') for path in (ROOT / 'src/admin/web').glob('*.js'))
assert not re.search(r'(?<![A-Za-z])(?:window\.)?(?:alert|confirm)\s*\(', all_admin_js)
assert 'function appConfirm(message, options = {})' in app_js and 'function apiErrorMessage(path, response, value)' in app_js
deploy_py = read_deploy_source()
assert "SHELL_PATH_PROFILE = Path('/etc/profile.d/rustproxy-path.sh')" in deploy_py and 'install_shell_path(prefix)' in deploy_py and 'check_shell_path(prefix)' in deploy_py
assert 'grid-column: span 3' in ui_css and '.check>span {' in ui_css and 'transform: translateY(-1px)' in ui_css
assert '.cache-toolbar-status .check' in ui_css and '.docker-toolbar .check' in ui_css and '.docker-alias-field>span:first-child {' in ui_css and 'margin-bottom: 0' in ui_css and '.docker-alias-editor {' in ui_css and 'min-height: 38px' in ui_css and 'height: 30px' in ui_css
assert 'readOnly: !!editing' not in vhosts_js and 'vhIdRenameConfirm' in vhosts_js and 'v.id === value.id && v.id !== editing' in vhosts_js
assert 'enforce_local_page_no_cache' in proxy and 'LocalGeneratedResponse' in proxy and 'locally_generated_pages_remove_cache_metadata' in proxy
assert 'local_page_method_rejection_is_still_marked_non_cacheable' in pages and 'response.extensions_mut().insert(PageServed);' in pages
screenshots = {path.name for path in (ROOT / 'docs/screenshots').iterdir() if path.is_file()}
assert 'README.md' in screenshots
assert screenshots <= {'README.md', 'login.png', 'location-headers.png'}
assert 'generated_vhost_inherits_global_server_header_policy' in rust_source(ROOT, 'src/docker.rs')
assert '.docker-route-grid {' in ui_css and 'align-items: start' in ui_css
assert '.docker-acl-grid {' in ui_css and 'height: 38px' in ui_css
assert 'preserve.checked = assignment?.preserve_host !== false' in docker_js
for marker in ["ensureSelfSignedCertificate", "ListenAndServeTLS", "CertificateSHA256", "RUSTPROXY_DOCKER_AGENT_TLS"]:
assert marker in docker_agent, marker
for marker in [
'fallbacks: HashMap<String, Vec<CompiledRoute>>',
'id: format!("{}/main", host.id)',
'custom_locations_override_but_main_vhost_remains_fallback',
'direct_admin_listener_backend_is_marked_for_guarded_control_plane_use',
'pub targets_admin_listener: bool',
]:
assert marker in config, marker
vhost_source = rust_source(ROOT, "src/vhost.rs")
assert 'is_legacy_main_fallback' in vhost_source
assert "benign_tunnel_close" in websocket and "ConnectionReset" in websocket
assert 'StatusCode::UNAUTHORIZED => Some(include_str!("admin/web/error_401.html"))' in pages
assert 'StatusCode::NOT_FOUND => Some(include_str!("admin/web/error_404.html"))' in pages
assert 'pub struct MinimalWelcome' in pages and 'builtin_welcome_is_minimal_and_never_cacheable' in pages and 'HeaderValue::from_static("no-store")' in pages
for marker in [
"fn admin_request_authority",
"Version::HTTP_2 | Version::HTTP_3",
"request.uri().authority().ok_or(())?",
"administration_https_health_accepts_http2_authority_without_host_header",
]:
assert marker in admin, marker
assert "fn panel_document_path" in admin and "panel_document_path(&path)" in admin and "panel_subpage_routes_survive_browser_refresh" in admin
assert 'response.extensions().get::<LocalHostResponse>().is_none()' in proxy
assert '"cdn-cache-control"' in proxy and '"x-cache-resident-age"' in proxy
for marker in ["OriginBackend", "apply_backend_diagnostic_headers", "x-rustproxy-backend-time", "backend_diagnostics_are_opt_in_and_strip_spoofed_values"]:
assert marker in proxy, marker
for marker in ["force_ssl_redirect", "force_ssl_location", "StatusCode::PERMANENT_REDIRECT", "https_redirect_port", "apply_upstream"]:
assert marker in proxy, marker
forwarding = rust_source(ROOT, "src/forwarding.rs")
for marker in ["pub fn apply_upstream", 'headers.remove("x-forwarded-for")', 'headers.remove("x-real-ip")', "upstream_can_withhold_client_ip_without_losing_origin_metadata"]:
assert marker in forwarding, marker
for marker in ["SINK_QUEUE", "FileSink", "LogstashSink", "LokiSink", "Limited::new"]:
assert marker in logging, marker
for marker in [
"matching_body_bytes", "matching_resident_bytes", "matching_cold_bytes",
"expose_resident_age", "x-cache-resident-age", "COLD_QUEUE",
"ColdFile", "body_sha256", "get_tiered", "revalidation_tiered",
"cold_queue_dropped_total", "cold_after_seconds", "cold_on_age", "cold_max_hot_seconds", "cold_policy_can_demote_by_total_hot_residency_age",
"lru_demotion_persists_and_survives_restart",
"managed_update_flush_ignores_shutdown_toggle_when_cold_tier_is_enabled",
"flush_hot_to_cold_for_update",
"disabled_cache_strips_diagnostics_without_exposing_bypass_headers",
"vary_selects_variants_without_fragmenting_the_primary_hash",
"vary_schema_change_removes_incompatible_fallbacks",
"varnish_primary_hash_uses_host_and_url_not_unrelated_request_headers",
"resident_age_survives_successful_revalidation",
"COLD_FORMAT_VERSION: u8 = 3",
"cold_cache_uses_two_hex_character_shards",
"legacy_flat_cold_cache_is_not_migrated",
"manual_cold_cleanup_reports_removed_objects_and_freed_bytes",
"rfc5861_windows_and_revalidation_flags_are_separate_metadata",
"request_max_stale_is_bounded_and_respects_revalidation_flags",
"max_stale_can_reuse_an_object_after_normal_ttl_expiry",
"request_max_age_only_revalidates_when_cached_age_exceeds_limit",
"transformed_plan_uses_transformed_content_length",
"transformed_representation_is_stored_and_reused",
"every_enabled_transform_kind_can_fill_and_reuse_the_cached_representation",
"query_strings_remain_distinct_and_bypass_query_is_only_a_request_policy",
"transformed_client_conditionals_use_the_representation_etag_not_the_origin_etag",
"no_transform_does_not_disable_shared_storage",
"duplicate_and_argument_immutable_do_not_invalidate_cache_control",
"fill_incomplete", "transform_length_mismatch", "object_too_large",
"cold_cleanup_skips_an_entry_while_a_read_is_pinned",
"stale_if_error_is_only_available_inside_its_window",
"matching_hot_key",
"matching_cold_key",
]:
assert marker in cache, marker
transform = rust_source(ROOT, "src/transform/mod.rs")
for marker in [
"pub struct Settings", "pub struct Overrides",
"resolved.enabled = global.enabled && self.enabled",
"pub fn no_transform", "OriginValidators", "fn decode<'a>",
"Cow::Borrowed(body)", "trim_allocator_after_large_transform",
"pub memory_budget_bytes: usize", "pub max_inflight: usize", "Preset::Uber",
"try_reserve_transform_memory(required_memory, settings.memory_budget_bytes)",
"try_reserve_transform_slot(settings.max_inflight)",
"drop(decoded);", "drop(raw);",
"rustproxy-",
"unchanged_compressed_body_is_canonicalized_to_identity",
"origin_no_transform_blocks_body_changes_and_keeps_validator",
"final_no_transform_blocks_body_changes",
"compressed_no_transform_preserves_origin_encoding_and_bytes",
"empty_head_body_with_origin_length_keeps_origin_representation_headers",
"CanonicalIdentity",
]:
assert marker in transform, marker
assert 'collect bounded static response for transformation: {error}' in transform
assert '.context("collect bounded static response for transformation")' not in transform
lookup = rust_source(ROOT, 'src/cache/lookup.rs')
model = rust_source(ROOT, 'src/cache/model.rs')
css_minifier = rust_source(ROOT, 'src/transform/css.rs')
assert 'let Some(_read_lease) = self.try_reserve_transient' in lookup
assert 'struct TransientLease {\n _inner: Arc<TransientLeaseInner>,' in model
assert 'struct TransientLease(Arc<TransientLeaseInner>);' not in model
assert 'last_char = out.chars().last();' not in css_minifier
assert css_minifier.count("last_char = Some(' ');") == 1
assert "pub mod transform;" in rust_source(ROOT, "src/lib.rs")
for name in ["js.rs", "css.rs"]:
minifier = (ROOT / "src/transform" / name).read_text(encoding="utf-8")
assert "Vec<char>" not in minifier and ".chars().collect" not in minifier
assert "into_bytes()" in minifier
balance = rust_source(ROOT, "src/balance.rs")
for marker in [
"pub struct PassiveHealth", "enabled: false", "pub passive_health: PassiveHealth",
"passive_health_is_opt_in_and_uses_separate_thresholds",
"legacy_settings_without_passive_health_default_to_off",
"slow_5xx_counts_both_observability_classes_when_passive_is_off",
"unhealthy_source_hands_off_when_the_original_health_domain_recovers",
"active_health_failures_total", "passive_health_failures_total",
"http_5xx_total", "timeouts_total", "connect_errors_total", "io_errors_total", "slow_responses_total",
]:
assert marker in balance, marker
for marker in [
'fill_bytes_active', 'cold_queue_items', 'cold_queue_bytes',
'cold_write_bytes_active', 'cold_read_bytes_active', 'transient_bytes',
'transient_max_bytes', 'cold_queue_max_bytes', 'hot_eviction_max_objects',
'hot_logical_bytes', 'hot_allocated_bytes', 'memory_pressure_rejections',
'struct FillGauge', 'struct TransientLease', 'reserve_fill_allocation',
'release_allocator_memory()', 'fn hot_entry_cost(',
]:
assert marker in cache, marker
assert 'impl Drop for Candidate' not in cache
diagnostics = rust_source(ROOT, "src/diagnostics.rs") + rust_source(ROOT, "src/system_stats/mod.rs") + rust_source(ROOT, "src/system_stats/linux.rs")
for marker in ['process_rss_anon_bytes', 'process_rss_file_bytes', 'process_rss_shmem_bytes', 'process_data_bytes', 'process_swap_bytes', 'RssAnon', 'RssFile', 'RssShmem', 'VmData', 'VmSwap']:
assert marker in diagnostics, marker
for marker in ['admin_session_passthrough', 'admin_control_plane', 'restore_admin_cookie', 'BEFORE returning a successful RFC 9220 response', 'websocket_phase("tunnel_ready")']:
assert marker in proxy, marker
headers_source = rust_source(ROOT, "src/proxy/headers.rs")
assert 'pub fn restore_admin_cookie' in headers_source and 'admin_cookie_can_be_restored_only_from_captured_fragment' in headers_source
assert "state.cache.purge(None)" not in proxy
assert "request.method() == Method::HEAD && cache_ticket.is_some()" in proxy
for marker in ["cache_effective_enabled", "transform_effective_enabled", "Some(route.id.clone())"]:
assert marker in proxy, marker
for marker in ["pub struct Metrics", "pub struct MetricsView", "struct ActiveSession", "sessions_total", "sessions_completed", "bytes_in", "bytes_out", "packets_in", "packets_out", "metrics_view_reports_l4_counters"]:
assert marker in stream, marker
for marker in ["pub enum TargetType", "pub enum PathMode", "pub enum QueryMode", "StatusCode::from_u16", "cache_seconds", "unconditional self-redirect"]:
assert marker in redirect, marker
assert "pub redirects: Vec<crate::redirect::Host>" in config and "redirect_on" in config
assert "pub not_found_hosts: Vec<crate::not_found::Host>" in config and "not_found_host_on" in config
for marker in ["pub struct Host", "pub struct CompiledHost", "crate::pages::Page"]:
assert marker in not_found, marker
assert "RedirectResponse" in proxy and "snapshot.redirect_on" in proxy
assert "#[serde(default)]\n pub tls_verify: bool" in config
upstream = rust_source(ROOT, "src/upstream.rs")
assert "BackendCertificateVerificationDisabled" in upstream and "supported_schemes" in upstream
disabled_verifier = upstream.split("impl ServerCertVerifier for BackendCertificateVerificationDisabled", 1)[1].split("pub fn client(", 1)[0]
assert disabled_verifier.count("Ok(HandshakeSignatureValid::assertion())") == 2
assert "WebPkiServerVerifier" not in disabled_verifier
assert "stream_metrics" in runtime and "stream_metrics" in main
for marker in ["PrefixPriority", "RegexInsensitive", "LocationResponse", "pub rewrite: PathRewrite"]:
assert marker in rust_source(ROOT, "src/vhost.rs"), marker
for marker in ["rewritten_path", "nginx_style_location_precedence_exact_priority_regex_prefix", "location_rewrites_are_normalized_and_regex_captures_expand", "custom_location_response_is_validated"]:
assert marker in config, marker
for marker in ["local_location_response", "route.rewritten_path(&path)", "encoded_origin_path", "client_ip.to_string()"]:
assert marker in proxy, marker
assert proxy.index("RustProxy: denied by access policy") < proxy.index("if let Some(spec) = &route.response") < proxy.index("let (backend, backend_lease, affinity_cookie)")
assert "pub client_ip: Option<String>" in logging and "client_ip" in logging
assert '"metrics": stream_metrics' in admin and '"/assets/streams.js"' in admin and 'include_str!("web/streams.js")' in admin
assert '.route("/404-hosts", get(public::index))' in admin and '"/assets/host404.js"' in admin and 'include_str!("web/host404.js")' in admin and '"not_found_hosts"' in admin
assert "session_at_with_browser_lifetime" in admin and "session_lifetime_seconds" in admin
assert '/api/preferences' in admin and 'live_log_columns' in admin
assert 'default_admin_theme' in admin and '"dark" | "light"' in admin
html = (ROOT / 'src/admin/web/index.html').read_text(encoding='utf-8')
assert 'id="ui-theme"' in html and 'html[data-theme="light"] {' in ui_css
assert 'body[data-density="compact"] input:not([type="checkbox"]):not([type="radio"])' in ui_css
assert '<div class="edition">INDEPENDENT BY DESIGN <span>' not in html
assert '<div class="edition">INDEPENDENT BY DESIGN</div>' in html
assert "inspected_metadata" in admin and "certificate_metadata" in admin
balance = rust_source(ROOT, "src/balance.rs")
assert "max_concurrent_requests" in balance and "queue_timeout_ms" in balance and "select_retry_wait" in balance
for marker in ["dns_errors_total", "tls_errors_total", "response_header_timeouts_total", "last_failure_detail", "last_recovered_ms"]:
assert marker in balance, marker
tls_source = rust_source(ROOT, "src/tls.rs")
for marker in ["CertificateMetadata", "not_before_unix", "not_after_unix", "days_remaining", "expiry_status"]:
assert marker in tls_source, marker
assert '"/assets/redirects.js"' in admin and 'include_str!("web/redirects.js")' in admin and '.route("/redirects", get(public::index))' in admin
assert '/Ubuntu%2014.04%20LTS/' in acl and 'repeated_slashes_are_normalized_for_routing' in acl and 'percent_encoded_spaces_are_valid_request_paths' in policies and 'repeated_slashes_route_normally_but_reach_upstream_unchanged' in policies and 'local_location_response_uses_response_rules_without_opening_backend' in policies and 'local_location_response_still_respects_route_acl' in policies
db_source = rust_source(ROOT, "src/db.rs")
acme_source = rust_source(ROOT, "src/acme.rs")
acme_runtime = (ROOT / "src/acme/runtime_env.rs").read_text(encoding="utf-8")
assert 'runtime_setup_once' in acme_source
assert 'include!("acme/runtime_env.rs")' in (ROOT / 'src/acme.rs').read_text(encoding='utf-8')
for marker in ['CERTBOT_PACKAGES', 'join("acme-python")', 'virtualenv>=20,<21', 'distro_certbot_available', 'acme.runtime_python_missing', 'acme.runtime_pip_missing']:
assert marker in acme_runtime, marker
assert "SCHEMA_VERSION: i64 = 8" in db_source and "certificate_renewals" in db_source and "0004_admin_preferences.sql" in db_source and "0005_certificate_api.sql" in db_source and "0006_certificate_api_credentials.sql" in db_source and "0007_secret_store.sql" in db_source and "0008_config_history.sql" in db_source
runtime_source = rust_source(ROOT, "src/runtime.rs")
assert 'replace_as_restore' in db_source and 'config.restore' in db_source and 'source_revision' in db_source
assert 'restore_config_history' in admin and '/api/config/history/restore' in admin
assert 'pub async fn restore_as' in runtime_source and 'replace_as_restore' in runtime_source
assert "load_renewal_observations" not in acme_source and "renewals.json" not in acme_source
for marker in ["pub kind: &'static str", "pub log_directory: String", "pub(crate) fn certbot_log_directory", 'kind: "issue"', '"renewal-dry-run"']:
assert marker in acme_source, marker
assert '"log_directories":log_directories' in admin and 'crate::acme::certbot_log_directory' in admin
assert '"runtime":state.acme.runtime_view()' in admin
assert '"certificate_runtime": state.acme.runtime_view()' in rust_source(ROOT, 'src/admin/status.rs')
assert "store.load_certificate_renewals().await?" in main and "state.acme.restore_renewals(renewal_observations)" in main
assert 'accept-remote-admin-risk' in main and 'hide = true' in main
settings_rust = rust_source(ROOT, 'src/settings.rs')
assert 'pub struct StartupLocks' in settings_rust and 'fixed by systemd/startup' in settings_rust
logging_rust = rust_source(ROOT, 'src/logging.rs')
assert 'pub per_vhost: bool' in logging_rust and 'default_vhost_log_directory' in logging_rust
pages_rust = rust_source(ROOT, 'src/pages.rs')
assert 'validate_local_response' in pages_rust and '(200..=599).contains(&status)' in pages_rust
assert 'Kind::Builtin' in pages_rust and 'builtin_application_page' in pages_rust
docker_rust = rust_source(ROOT, 'src/docker.rs')
for marker in ['pub poll_interval_seconds: u64', 'pub acl_profile_id: Option<String>', 'pub proxy_timeouts: crate::vhost::ProxyTimeouts', 'refresh(&self, settings: &Settings, force: bool)']:
assert marker in docker_rust, marker
docker_ui = (ROOT / 'src/admin/web/docker.js').read_text(encoding='utf-8')
for marker in ['docker-live-refresh', 'saveAssignment(agentId, row, publish)', 'docker-acl-profile', 'docker-proxy-read', 'dockerOpenApp', 'proxiedUrl(agent, hostname)']:
assert marker in docker_ui, marker
compression_source = rust_source(ROOT, 'src/compression.rs')
metrics_source = rust_source(ROOT, 'src/metrics.rs')
style_source = (ROOT / 'src/admin/web/style.css').read_text(encoding='utf-8')
app_source = read_app_js(ROOT)
assert 'restoreConfigRevision' in app_source and 'configHistoryRestoreConfirm' in app_source
assert 'function setMobileMenu(open)' in app_source and 'setPanel(button.dataset.panel); setMobileMenu(false);' in app_source
assert 'id="mobile-menu-toggle"' in html and 'id="main-navigation"' in html
assert '.sidebar.mobile-nav-open .main-navigation' in style_source
assert 'id="about-open"' in html and 'id="about-dialog"' in html and 'Mateusz Gruszczynski' in html
assert 'https://git.linuxiarz.pl/gru/RustProxy' in html and 'data-i18n="documentation" id="api-docs"' in html
assert '$("about-open").addEventListener("click"' in app_source and '.about-dialog {' in style_source
assert 'id="config-history-restore"' in html
vhosts_ui = read_vhosts_js(ROOT)
for marker in ['pub enum AppliedEncoding', 'AppliedEncoding::Gzip', 'AppliedEncoding::Brotli', 'AppliedEncoding::Zstd', 'AppliedEncoding::Deflate']:
assert marker in compression_source, marker
for marker in [
'gzip: true', 'br: true', 'zstd: true', 'deflate: false',
'const PREFERENCE: [Self; 4] = [Self::Zstd, Self::Brotli, Self::Gzip, Self::Deflate]',
'quality_weights_win_before_server_priority',
'equal_quality_uses_zstd_then_br_then_gzip_then_deflate',
'cookies_authorization_and_private_cache_policy_do_not_disable_compression',
'existing_content_encoding_is_never_recompressed',
]:
assert marker in compression_source, marker
assert 'AllowCookie' not in compression_source
diagnostics_source = rust_source(ROOT, 'src/diagnostics.rs') + rust_source(ROOT, 'src/system_stats/mod.rs') + rust_source(ROOT, 'src/system_stats/linux.rs')
for marker in ['pub struct NetworkInterfaceView', 'pub network_interfaces: Vec<NetworkInterfaceView>', 'sysinfo::Networks::new_with_refreshed_list', 'system.network_stats', 'collect_network_systemstat', 'normalize_network_interfaces', 'merge_network_interfaces']:
assert marker in diagnostics_source, marker
assert 'interfaces.truncate(64)' not in diagnostics_source, 'network inventory must not silently truncate interfaces'
for marker in ['pub http1_requests: AtomicU64', 'pub fn record_compression', 'each_codec_has_an_independent_counter', 'http1_requests_total']:
assert marker in metrics_source, marker
assert 'state.metrics.record_compression(encoding)' in proxy
assert 'protocolSamples' in app_source and 'm.http1_requests' in app_source and '$("open-proxy")' not in app_source
assert 'renderNetworkInterfaceCharts' in app_source and 'networkInterfaceSamples' in app_source
assert 'rp-hidden-network-interfaces' in app_source and 'network-interface-hide' in style_source
ui_html = (ROOT / 'src/admin/web/index.html').read_text(encoding='utf-8')
for marker in ['metric-bytes-down', 'metric-bytes-up', 'connection-chart', 'certificate-api-credentials']:
assert marker in ui_html, marker
assert 'id="network-interface-charts"' in ui_html
assert 'compressionCountersHelp' not in ui_html
assert 'sparkline-h1' in (ROOT / 'src/admin/web/index.html').read_text(encoding='utf-8')
assert 'body[data-density="compact"]' in style_source and '--ui-control: 22px' in style_source
compact_style_0270 = style_source.replace(' ', '')
for marker in ['progress::-webkit-progress-bar', 'progress::-webkit-progress-value', 'progress::-moz-progress-bar', '-webkit-appearance:none']:
assert marker in compact_style_0270, marker
assert 'status.backend_pool_domains' in vhosts_ui and 'payload["backend_pool_domains"]' in admin
docker_source = rust_source(ROOT, 'src/docker.rs')
management_ui = (ROOT / 'src/admin/web/management.js').read_text(encoding='utf-8')
for marker in ['function aliasEditor(', 'host-chip-editor docker-alias-editor', '.docker-force-ssl', '.docker-websockets', '.docker-cache-enabled', '.docker-compression-enabled', '.docker-passive-enabled', '.docker-passive-5xx', '.docker-agent-acl-profile']:
assert marker in docker_ui, marker
for forbidden in ['.docker-compression-zstd', '.docker-compression-br', '.docker-compression-gzip', '.docker-compression-deflate', 'compressionSelect(']:
assert forbidden not in docker_ui, forbidden
assert 'compressionEnabled.checked = assignment?.compression_enabled !== false' in docker_ui
assert 'compression_zstd: null, compression_br: null, compression_gzip: null, compression_deflate: null' in docker_ui
for marker in ['"force_ssl": assignment.force_ssl.unwrap_or(false)', '"websockets": assignment.websockets', '"enabled": assignment.compression_enabled', '"cache": {"enabled": assignment.cache_enabled}', '"passive_health": assignment.passive_health.clone()', 'assignment.acl_profile_id.clone().or_else(|| agent.acl_profile_id.clone())', 'host.transient_unavailable = target.is_none()', 'inventory: previous_inventory']:
assert marker in docker_source, marker
route_cache_0270 = rust_source(ROOT, 'src/proxy/forward/route_cache.rs')
for marker in ['if route.unavailable', 'StatusCode::SERVICE_UNAVAILABLE', 'published Docker service is currently unavailable', 'retry-after']:
assert marker in route_cache_0270, marker
assert 'agent.acl_profile_id === profile.id' in management_ui
vhost_source = rust_source(ROOT, 'src/vhost.rs')
for marker in ['DEFAULT_PROXY_CONNECT_TIMEOUT_SECONDS: u64 = 10', 'DEFAULT_PROXY_RESPONSE_HEADER_TIMEOUT_SECONDS: u64 = 120', 'DEFAULT_PROXY_READ_TIMEOUT_SECONDS: u64 = 60', 'DEFAULT_PROXY_SEND_TIMEOUT_SECONDS: u64 = 60', 'proxy_timeouts_have_explicit_safe_defaults_but_keep_null_compatibility']:
assert marker in vhost_source, marker
assert 'defaultProxyTimeouts = () => ({ connect_timeout_seconds: 10, response_header_timeout_seconds: 120, read_timeout_seconds: 60, send_timeout_seconds: 60 })' in vhosts_ui
assert 'actions.append(enabledLabel, stateBadge, remove)' in docker_ui
assert 'row._readListeners = listenerControl.read' in docker_ui and '.docker-agent-listeners' not in docker_ui
assert 'docker-agent-status-badge' in style_source and 'justify-content: center' in style_source
assert '.docker-agent-grid>label' in style_source and '#docker-agent-add' in style_source and 'margin-top: 14px' in style_source
assert 'Bearer token:' in (ROOT / 'docker-agent/install.sh').read_text(encoding='utf-8')
tls_rust = rust_source(ROOT, 'src/tls.rs')
assert 'pub fn validate_certificate_removals' in tls_rust and 'pub fn effective_cipher_suites' in tls_rust
assert "renewals.json" not in main
upgrade_source = (ROOT / "scripts/upgrade.py").read_text(encoding="utf-8")
assert "removed_obsolete_runtime_state" in upgrade_source and "legacy-data" not in upgrade_source and "certs_dir.is_symlink()" in upgrade_source
assert '.managed-update' in main and "flush_hot_to_cold_for_update" in main
openapi = (ROOT / "src/admin/openapi.json").read_text(encoding="utf-8")
assert '"/api/config/history/restore"' in openapi
for marker in ['"version": "0.27.2"', '"memory_preset"', '"cold_preset"', '"memory_budget_bytes"', '"max_inflight"', '"preset"', '"session_days"', '"session_lifetime_seconds"', '"accept_local_listener_risk"', '"backend_header"', '"backend_timing_header"', '"cold_on_age"', '"match_type"', '"regex_insensitive"', '"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
assert marker in openapi, marker
assert '"software_update"' in openapi and '"update_available"' in openapi
for marker in ['"/api/certificates/deploy"', '"CertificateDeploy"', '"CertificateDeployResult"', '"/api/certificates/reload"']:
assert marker in openapi, marker
for marker in ['"/api/certificate-api"', '"/api/certificate-api/{key_id}"', '"/api/certificates/{certificate_id}/reload"', '"CertificateReloadOneResult"', '"CertificateApiBearer"', '"CertificateApiKey"', '"/api/live-logs"']:
assert marker in openapi, marker
websocket_source = rust_source(ROOT, 'src/proxy/websocket.rs')
proxy_source = rust_source(ROOT, 'src/proxy/mod.rs')
for marker in ['websocket_legacy_support', 'vhWebsocketLegacySupport', 'vhWebsocketLegacySupportHelp']:
assert marker in vhosts_ui or marker in openapi, marker
for marker in ['fn legacy_websocket_origin(', 'request.headers_mut().insert("origin", origin)', 'websocket_legacy_support && !websocket_upstream_h2']:
assert marker in proxy_source, marker
for marker in ['legacy_support: bool', 'legacy 101 response declared a non-zero body', 'legacy_upstream_response_accepts_old_101_syntax_but_keeps_accept_proof_strict']:
assert marker in websocket_source, marker
for marker in ['certificate_api_credentials', 'upsert_certificate_api_credential', 'revoke_certificate_api_credential', 'verify_certificate_api_credential']:
assert marker in db_source, marker
logging_source = rust_source(ROOT, 'src/logging.rs')
for marker in ['LIVE_STREAM_CAPACITY', 'broadcast::Sender<LogEntry>', 'pub fn subscribe']:
assert marker in logging_source, marker
for marker in ['livelogs: bool', 'LiveLogFilters', 'live_logs_command', 'LocalAdminCertificateVerifier', 'detect_local_admin_scheme', 'admin.uri("/api/live-logs")']:
assert marker in main, marker
assert '.uri(format!("http://{addr}/api/live-logs"))' not in main
for marker in [
"allow_trusted_network_without_token", "include_docker_vhosts", "include_streams", "include_logging",
"rust_proxy_docker_vhost_{name}", "rust_proxy_docker_backend_{name}", "rust_proxy_geoip_loaded", "not_after_timestamp_seconds",
"ewma_latency_seconds", "passive_failures", "active_health_failures_total",
"passive_health_failures_total", "http_5xx_total", "timeouts_total",
"connect_errors_total", "io_errors_total", "slow_responses_total",
"rust_proxy_log_sink_queue_dropped_total", "rust_proxy_not_found_hosts_configured",
]:
assert marker in prometheus, marker
assert "if docker_targets.contains_key(&pool.id)" in prometheus
assert "if docker_targets.contains_key(id)" in prometheus
assert "rust_proxy_docker_vhost_exchange_duration_seconds" in prometheus
assert "PeerAddr" in prometheus and "Forwarded headers never participate in this decision" in prometheus
transport_doc = (ROOT / "docs/TRANSPORT.md").read_text(encoding="utf-8")
assert '127.0.0.1:9090' in transport_doc and 'accept_local_listener_risk: true' in transport_doc
drop_impl = http3.split('impl Drop for ReceiveBody', 1)[1].split('#[cfg(test)]', 1)[0]
assert 'self.stream.stop_sending(' not in drop_impl
websocket_source = rust_source(ROOT, 'src/proxy/websocket.rs')
for marker in ['h3::quinn::RecvStream', 'websocket_rfc9220_on(local)', 'run_h3', 'H3_REQUEST_CANCELLED']:
assert marker in http3 or marker in websocket_source, marker
assert 'upstream: Upgraded' in websocket_source
assert 'timeout(Duration::from_secs(10), upstream)' not in websocket_source
for marker in ['websocket_phase("tunnel_started")', 'websocket_phase("tunnel_closed")', 'h3_reset("H3_REQUEST_CANCELLED")']:
assert marker in websocket_source, marker
logging_source = rust_source(ROOT, 'src/logging.rs')
for marker in ['http_version', 'transport', 'websocket_bootstrap', 'websocket_phase', 'websocket_error', 'h3_reset_code', 'upstream_status']:
assert marker in logging_source, marker
assert 'h3_quinn' not in http3 and 'h3_quinn' not in (ROOT / 'tests/http3.rs').read_text(encoding='utf-8')
h3_test = (ROOT / 'tests/http3.rs').read_text(encoding='utf-8')
assert 'RFC 9220: Extended CONNECT negotiates WebSocket on the same H3 stream.' in h3_test
assert 'Method::CONNECT' in h3_test and 'h3::ext::Protocol' in h3_test and '.send_data(' in h3_test
for marker in ['external_admin_authority', 'external_admin_scheme', 'trusted_admin_forwarded_proto', 'reverse_proxy_dns_allowlist_uses_external_port_and_keeps_direct_port_available']:
assert marker in admin, marker
def container_contract_smoke() -> None:
dockerfile = (ROOT / "Dockerfile").read_text(encoding="utf-8")
unit = (ROOT / "deploy/rustproxy.service").read_text(encoding="utf-8")
assert "LogsDirectory=rustproxy" in unit and "LogsDirectoryMode=0770" in unit
assert "Environment=MALLOC_ARENA_MAX=2" in unit
assert "TimeoutStopSec=120" in unit
assert "ReadWritePaths=/var/lib/rustproxy /var/log/rustproxy" in unit
generated_unit = deploy.service_unit({"prefix":"/opt/rustproxy", "data_dir":"/var/lib/rustproxy", "user":"rustproxy", "group":"rustproxy", "admin":"127.0.0.1:9090", "remote_admin":False, "http":"0.0.0.0:80", "https":"0.0.0.0:443"})
assert "LogsDirectory=rustproxy" in generated_unit and "/var/log/rustproxy" in generated_unit
assert "Environment=MALLOC_ARENA_MAX=2" in generated_unit
for marker in [
"FROM rust:1.88-trixie AS build",
"cargo generate-lockfile",
"cargo build --locked --release",
"FROM debian:trixie-slim AS runtime",
"python3-venv",
"python3-pip",
"certbot",
"python3-certbot-dns-cloudflare",
"python3-certbot-dns-ovh",
"python3-certbot-dns-rfc2136",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"USER rustproxy:rustproxy",
'VOLUME ["/var/lib/rustproxy"]',
]:
assert marker in dockerfile, marker
ignore = set((ROOT / ".dockerignore").read_text(encoding="utf-8").splitlines())
for marker in {"data", "target", ".env", "*.pem", "*.key"}:
assert marker in ignore, marker
assert (ROOT / "docs/CONTAINER.md").is_file()
def grafana_contract_smoke() -> None:
assert not (ROOT / "debian").exists(), "Debian packaging belongs to the external packaging repository"
dashboard = json.loads((ROOT / "grafana/rustproxy-overview.json").read_text(encoding="utf-8"))
assert dashboard.get("uid") == "rustproxy-0170"
queries = json.dumps(dashboard)
for marker in ["rust_proxy_process_rss_bytes", "rust_proxy_cache_transient_bytes", "rust_proxy_backend_ewma_latency_seconds", "rust_proxy_certificate_renewal_not_after_timestamp_seconds", "rust_proxy_not_found_hosts_configured", "rust_proxy_http1_requests_total", "rust_proxy_backend_upstream_client_generation_requests", "rust_proxy_backend_upstream_client_generation_age_seconds", "rust_proxy_backend_retry_attempts_total", "rust_proxy_backend_upstream_client_rotations_total", "rust_proxy_docker_backend_upstream_client_generation_requests", "rust_proxy_docker_backend_retry_attempts_total"]:
assert marker in queries, marker
def reproducible_archive_smoke(tmp: Path) -> None:
first = tmp / "first.zip"
second = tmp / "second.zip"
env = dict(os.environ, SOURCE_DATE_EPOCH="1700000000")
subprocess.run(["python3", str(ROOT / "make_zip.py"), str(first)], check=True, env=env, stdout=subprocess.DEVNULL)
subprocess.run(["python3", str(ROOT / "make_zip.py"), str(second)], check=True, env=env, stdout=subprocess.DEVNULL)
first_bytes, second_bytes = first.read_bytes(), second.read_bytes()
assert hashlib.sha256(first_bytes).digest() == hashlib.sha256(second_bytes).digest()
with zipfile.ZipFile(first) as archive:
names = archive.namelist()
assert "RustProxy/SHA256SUMS" in names
assert "RustProxy/scripts/rust_source.py" in names
assert "RustProxy/scripts/rust_module_contracts.py" in names
assert "RustProxy/scripts/source_module_contracts.py" in names
for name in names:
if name.startswith("RustProxy/") and name.endswith(".rs"):
assert len(archive.read(name).decode("utf-8").splitlines()) <= 500, name
if name.startswith("RustProxy/src/admin/web/") and name.endswith(".js"):
assert len(archive.read(name).decode("utf-8").splitlines()) <= 500, name
if name.startswith("RustProxy/docker-agent/") and name.endswith(".go"):
assert len(archive.read(name).decode("utf-8").splitlines()) <= 500, name
assert "RustProxy/src/admin/web/error_401.html" in names
assert "RustProxy/src/admin/web/error_404.html" in names
assert "RustProxy/docs/CUSTOM_LOCATIONS.md" in names
assert "RustProxy/docs/WEB_SERVER.md" in names
assert "RustProxy/docs/PHP_FPM.md" in names
for module in ["mod.rs", "config.rs", "filesystem.rs", "security.rs", "response.rs", "request.rs", "routing.rs", "fastcgi.rs", "serve.rs", "htaccess.rs", "tests.rs"]:
assert f"RustProxy/src/web_server/{module}" in names
assert "RustProxy/src/web_server.rs" not in names
assert "RustProxy/src/admin/web/web-server.js" in names
for module in ["app/core.js", "app/preferences.js", "app/config-history.js", "app/runtime.js", "app/settings.js", "drafts.js", "vhosts/core.js", "vhosts/editor.js", "vhosts/render.js"]:
assert f"RustProxy/src/admin/web/{module}" in names
assert "RustProxy/src/admin/web/app.js" not in names and "RustProxy/src/admin/web/vhosts.js" not in names
for module in ["model.go", "docker.go", "network.go", "certificate.go", "server.go", "main.go", "main_test.go"]:
assert f"RustProxy/docker-agent/{module}" in names
for module in ["deploy.py", "deploy_core.py", "deploy_runtime.py", "migrate_npm.py", "migrate_npm_core.py", "migrate_npm_api.py"]:
assert f"RustProxy/scripts/{module}" in names
assert "RustProxy/nagios/check_rustproxy.py" in names
assert "RustProxy/nagios/check_rustproxy_api" in names
assert "RustProxy/src/admin/web/host404.js" in names
assert "RustProxy/docs/NOT_FOUND_HOSTS.md" in names
assert not any(name.startswith("RustProxy/debian/") for name in names)
assert "RustProxy/grafana/rustproxy-overview.json" in names
assert "RustProxy/UPDATE_CHECK.toml" in names
assert "RustProxy/src/update_check.rs" in names
assert "RustProxy/docker-agent/docker-agent" not in names
assert all("/data/" not in name and "/target/" not in name for name in names)
assert all(not name.endswith("/.env") for name in names), "local .env leaked into release archive"
assert "RustProxy/.env.example" in names
timestamps = {item.date_time for item in archive.infolist()}
assert len(timestamps) == 1
def managed_acme_relocation_smoke(tmp: Path) -> None:
"""Reproduce the 0.27.1 relocated console-script shebang failure."""
stage = tmp / "acme-python.installing"
final = tmp / "acme-python"
bindir = stage / "bin"
bindir.mkdir(parents=True)
python = bindir / "python"
python.write_text(
"#!/bin/sh\n"
"test \"$1\" = -I && test \"$2\" = -m && test \"$3\" = certbot && test \"$4\" = --version || exit 2\n"
"echo 'certbot 5.8.0'\n",
encoding="utf-8",
)
python.chmod(0o755)
console = bindir / "certbot"
console.write_text(f"#!{python}\necho 'certbot 5.8.0'\n", encoding="utf-8")
console.chmod(0o755)
stage.rename(final)
relocated_console = final / "bin/certbot"
try:
subprocess.run([str(relocated_console), "--version"], check=True, capture_output=True, text=True)
except OSError:
pass
else:
raise AssertionError("relocated pip console script unexpectedly kept a valid staging shebang")
worker_path = ROOT / "scripts/acme_provider.py"
spec = importlib.util.spec_from_file_location("rustproxy_acme_provider", worker_path)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
old = os.environ.get("RUSTPROXY_CERTBOT_PYTHON")
os.environ["RUSTPROXY_CERTBOT_PYTHON"] = str(final / "bin/python")
try:
spec.loader.exec_module(module)
prefix = module.certbot_prefix()
assert prefix == [str(final / "bin/python"), "-I", "-m", "certbot"]
result = subprocess.run(prefix + ["--version"], check=True, capture_output=True, text=True)
assert result.stdout.strip() == "certbot 5.8.0"
finally:
if old is None:
os.environ.pop("RUSTPROXY_CERTBOT_PYTHON", None)
else:
os.environ["RUSTPROXY_CERTBOT_PYTHON"] = old
def main() -> None:
subprocess.run(["python3", str(ROOT / "scripts/rust_module_contracts.py")], check=True)
subprocess.run(["python3", str(ROOT / "scripts/source_module_contracts.py")], check=True)
with tempfile.TemporaryDirectory(prefix="rustproxy-release-smoke-") as directory:
tmp = Path(directory)
sql_and_backup_smoke(tmp / "sql")
upgrade_smoke(tmp / "upgrade")
managed_acme_relocation_smoke(tmp / "acme-relocation")
reproducible_archive_smoke(tmp / "archives")
deployment_gate_smoke(tmp)
npm_migration_smoke(tmp)
ui_contract_smoke()
source_contract_smoke()
container_contract_smoke()
grafana_contract_smoke()
print("PASS: 0.27.2 SQLite/upgrade, local-response 404 hosts, Prometheus/Grafana diagnostics, external Debian-packaging separation, cache/dashboard/logging/Nagios, retained admin/H3/stream/TLS contracts and reproducible source archives.")
if __name__ == "__main__":
main()