#!/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 '
' 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('

= 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", "/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(¤t, 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' 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 '
' in html assert '
' 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'(?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>', '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::().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,' in model assert 'struct TransientLease(Arc);' 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" 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" in config and "redirect_on" in config assert "pub not_found_hosts: Vec" 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" 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 '
INDEPENDENT BY DESIGN ' not in html assert '
INDEPENDENT BY DESIGN
' 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', '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', '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', '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()