1640 lines
124 KiB
Python
Executable File
1640 lines
124 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""Static package checks only. Does not compile or validate Rust types/behavior."""
|
||
from pathlib import Path
|
||
import ast
|
||
import json
|
||
import re
|
||
import sqlite3
|
||
import subprocess
|
||
import tomllib
|
||
from preflight import check as preflight
|
||
from rust_source import rust_source
|
||
from web_assets import read_app_js, read_vhosts_js
|
||
|
||
root = Path(__file__).resolve().parents[2]
|
||
|
||
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/deploy_core.py", "scripts/deploy/deploy_runtime.py", "scripts/deploy/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"))
|
||
|
||
preflight(root)
|
||
manifest = tomllib.loads((root / "Cargo.toml").read_text())
|
||
assert manifest["package"]["version"] == "0.31.2"
|
||
assert manifest["dependencies"]["quinn"]["version"] == "0.11.12"
|
||
h3dep = manifest["dependencies"]["h3"]
|
||
assert h3dep["package"] == "hpx-h3" and h3dep["version"] == "=2.5.20" and "quinn" in h3dep["features"]
|
||
assert "h3-quinn" not in manifest["dependencies"]
|
||
lock_path = root / "Cargo.lock"
|
||
if lock_path.exists():
|
||
lock = lock_path.read_text()
|
||
assert 'name = "rust-proxy"\nversion = "0.31.2"' in lock
|
||
assert 'name = "systemstat"' in lock and 'name = "metrics-process"' in lock and 'name = "sysinfo"' in lock, "Cargo.lock must be resolver-generated after adding cross-platform statistics libraries"
|
||
for release_doc, marker in {
|
||
"README.md": "# RustProxy 0.31.2",
|
||
"CHANGELOG.md": "## 0.31.2",
|
||
"ROADMAP.md": "## 0.31.2",
|
||
"AGENTS.md": "Current source version: 0.31.2.",
|
||
"docs/TEST_REPORT.md": "# Verification report - RustProxy 0.31.2",
|
||
}.items():
|
||
assert marker in (root / release_doc).read_text(), f"stale release identity in {release_doc}"
|
||
assert tomllib.loads((root / "rust-toolchain.toml").read_text())["toolchain"]["channel"] == "stable"
|
||
update_defaults = tomllib.loads((root / "UPDATE_CHECK.toml").read_text())
|
||
assert update_defaults == {
|
||
"repository_url": "https://git.linuxiarz.pl/gru/RustProxy",
|
||
"version_url": "https://git.linuxiarz.pl/gru/RustProxy/raw/branch/master/Cargo.toml",
|
||
"interval_seconds": 3600,
|
||
}
|
||
update_check_source = rust_source(root, "src/update_check.rs")
|
||
for marker in [
|
||
'include_str!("../UPDATE_CHECK.toml")',
|
||
'pub async fn poll_loop',
|
||
'tokio::time::sleep(interval)',
|
||
'Cargo.toml [package] version was not found',
|
||
'MAX_VERSION_DOCUMENT_BYTES',
|
||
'update check failed; retrying on the next interval',
|
||
]:
|
||
assert marker in update_check_source, marker
|
||
assert 'update version document is too large or incomplete: {error}' in update_check_source
|
||
assert '.context("update version document is too large or incomplete")' not in update_check_source
|
||
for marker in [
|
||
'tracing::warn!(',
|
||
'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)',
|
||
'update version redirect must not downgrade HTTPS to HTTP',
|
||
]:
|
||
assert marker in update_check_source, marker
|
||
assert 'pub last_error:' not in update_check_source, "update-check errors must not change dashboard/API state"
|
||
for name in ["README.md", "ROADMAP.md", "AGENTS.md", "CHANGELOG.md", "UPDATE_CHECK.toml", "Dockerfile", ".dockerignore", "docs/TEST_REPORT.md", "docs/V0.24.8_AUDIT.md", "docs/V0.24.7_AUDIT.md", "docs/V0.24.6_AUDIT.md", "docs/V0.24.5_AUDIT.md", "docs/V0.24.1_AUDIT.md", "docs/V0.24.0_AUDIT.md", "docs/V0.23.2_AUDIT.md", "docs/V0.23.1_AUDIT.md", "docs/V0.22.3_AUDIT.md", "docs/LIVE_LOGS.md", "docs/README.md", "docs/API.md", "docs/ARCHITECTURE.md", "docs/WEB_SERVER.md", "docs/PHP_FPM.md", "docs/VHOSTS.md", "docs/STREAMS.md", "docs/REDIRECTS.md", "docs/CONTAINER.md", "docs/NPM_MIGRATION.md", "docs/DOCKER_DISCOVERY.md", "docker-agent/main.go", "docker-agent/main_test.go", "docker-agent/rustproxy-docker-agent.service", "docker-agent/docker-agent.env.example", "docker-agent/install.sh", "src/docker.rs", "src/admin/web/docker.js", "scripts/migration/migrate_npm.py", "docs/CUSTOM_LOCATIONS.md", "nagios/README.md", "nagios/check_rustproxy.py", "nagios/check_rustproxy_api", "nagios/check_rustproxy_backends", "nagios/check_rustproxy_latency", "nagios/check_rustproxy_queues", "src/admin/web/error_401.html", "src/admin/web/error_404.html", "src/admin/web/streams.js", "src/admin/web/redirects.js", "src/admin/web/host404.js", "docs/NOT_FOUND_HOSTS.md", "grafana/rustproxy-overview.json", "grafana/README.md"]:
|
||
assert (root / name).is_file(), name
|
||
npm_doc = (root / "docs/NPM_MIGRATION.md").read_text(encoding="utf-8")
|
||
for marker in ["scripts/migration/migrate_npm_core.py", "scripts/migration/migrate_npm_api.py", '-v "$PWD/scripts/migration:/rustproxy-migration:ro"', "python /rustproxy-migration/migrate_npm.py"]:
|
||
assert marker in npm_doc, marker
|
||
for module in ["scripts/migration/migrate_npm.py", "scripts/migration/migrate_npm_core.py", "scripts/migration/migrate_npm_api.py"]:
|
||
assert (root / module).is_file(), module
|
||
for path in (root / "examples").glob("*.json"):
|
||
data = json.loads(path.read_text())
|
||
assert isinstance(data, dict), path
|
||
assert isinstance(data.get("vhosts"), list), path
|
||
assert "backends" not in data and "routes" not in data, path
|
||
for key in ("backends", "routes", "vhosts"):
|
||
assert isinstance(data.get(key, []), list), (path, key)
|
||
for path in (root / "src").rglob("*.rs"):
|
||
source = path.read_text()
|
||
for target in re.findall(r'include_str!\("([^"]+)"\)', source):
|
||
assert (path.parent / target).is_file(), (path, target)
|
||
lines = source.splitlines()
|
||
for index in range(len(lines) - 1):
|
||
assert not (lines[index].lstrip().startswith("#[derive(") and lines[index + 1].lstrip().startswith("#[derive(")), f"consecutive derive attributes in {path}:{index + 1}"
|
||
html = (root / "src/admin/web/index.html").read_text()
|
||
js = "\n".join(p.read_text() for p in (root / "src/admin/web").rglob("*.js") if p.name not in ("login.js", "docs.js"))
|
||
ids = re.findall(r'id="([^"]+)"', html)
|
||
assert len(ids) == len(set(ids)), "duplicate HTML IDs"
|
||
for identifier in set(re.findall(r'\$\("([^"]+)"\)', js)):
|
||
assert identifier in ids, f"unknown frontend ID: {identifier}"
|
||
python_sources = [root / "make_zip.py", *(root / "scripts").rglob("*.py"), *(root / "tools").rglob("*.py")]
|
||
for path in python_sources:
|
||
ast.parse(path.read_text(), str(path))
|
||
subprocess.run(["bash", "-n", str(root / "dev.sh")], check=True)
|
||
subprocess.run(["bash", "-n", str(root / "upgrade.sh")], check=True)
|
||
for name in ["install.sh", "install-debian13.sh", "update.sh", "nagios/check_rustproxy_api", "nagios/check_rustproxy_backends", "nagios/check_rustproxy_latency", "nagios/check_rustproxy_queues", "docker-agent/install.sh"]:
|
||
subprocess.run(["bash", "-n", str(root / name)], check=True)
|
||
bootstrap = (root / "install-debian13.sh").read_text()
|
||
for marker in [
|
||
'MODE="${1:-auto}"',
|
||
'auto|install|update',
|
||
'MODE=update',
|
||
'bash update.sh',
|
||
'--prefix "$PREFIX"',
|
||
'python3-venv',
|
||
'python3-pip',
|
||
]:
|
||
assert marker in bootstrap, marker
|
||
assert 'curl ' not in bootstrap, 'Debian bootstrap must use wget instead of curl'
|
||
dev = (root / "dev.sh").read_text()
|
||
assert 'cargo generate-lockfile' in dev and r'version = "0\.9\.0"' not in dev
|
||
assert dev.count('cargo generate-lockfile') == 1, "dev.sh must only create Cargo.lock when it is missing"
|
||
unit = (root / "deploy/rustproxy.service").read_text()
|
||
assert "LogsDirectory=rustproxy" in unit and "/var/log/rustproxy" in unit
|
||
assert "ReadOnlyPaths=-/etc/letsencrypt -/etc/ssl" in unit
|
||
assert "Environment=MALLOC_ARENA_MAX=2" in unit
|
||
assert "Environment=MALLOC_ARENA_MAX=2" in read_deploy_source()
|
||
assert not re.search(r"\b20\d{2}[-/]\d{1,2}[-/]\d{1,2}\b", (root / "CHANGELOG.md").read_text()), "changelog contains a release date"
|
||
subprocess.run(["python3", str(root / "tools/checks/langs_check.py")], check=True)
|
||
subprocess.run(["python3", str(root / "tools/checks/web_server_contracts.py")], check=True)
|
||
subprocess.run(["python3", str(root / "tools/checks/rust_module_contracts.py")], check=True)
|
||
subprocess.run(["python3", str(root / "tools/checks/source_module_contracts.py")], check=True)
|
||
dockerfile = (root / "Dockerfile").read_text()
|
||
assert "cargo generate-lockfile" in dockerfile and "cargo build --locked --release" in dockerfile
|
||
assert "USER rustproxy:rustproxy" in dockerfile
|
||
assert 'VOLUME ["/var/lib/rustproxy"]' in dockerfile
|
||
assert "COPY . ." in dockerfile
|
||
for marker in [
|
||
"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",
|
||
]:
|
||
assert marker in dockerfile, marker
|
||
assert "data" in (root / ".dockerignore").read_text().splitlines()
|
||
css = (root / "src/admin/web/style.css").read_text()
|
||
assert ".logging-grid fieldset" not in css, "logging sinks must reuse shared card styling"
|
||
assert "dialog.config-history-dialog {" in css, "Revision History must use the shared dialog appearance rule"
|
||
assert ".config-history-dialog::backdrop" not in css, "Revision History must not duplicate the shared dialog backdrop"
|
||
history_dialog_css = re.search(r"^\.config-history-dialog\s*\{([^}]*)\}", css, re.M).group(1)
|
||
assert "border-radius:" not in history_dialog_css, "Revision History dialog must stay square-cornered"
|
||
for selector in ["config-history-month", "config-history-day", "config-history-change"]:
|
||
block = re.search(rf"^\.{selector}\s*\{{([^}}]*)\}}", css, re.M).group(1)
|
||
assert "border-radius:" not in block, f"{selector} must stay square-cornered"
|
||
assert ".vhost-chart-line" in css and ".host-chip-editor" in css
|
||
assert 'class="vhost-dialog logging-format-dialog"' in html
|
||
assert 'data-panel="streams"' in html and 'id="panel-streams"' in html
|
||
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 = rust_source(root, "src/admin/preferences.rs")
|
||
for marker in ['vhost_list_sort', 'docker_vhost_sort', 'redirect_list_sort', 'notfound_list_sort', 'stream_list_sort', 'VHOST_LIST_SORT_COLUMNS', 'DOCKER_VHOST_SORT_COLUMNS', 'REDIRECT_LIST_SORT_COLUMNS', 'NOTFOUND_LIST_SORT_COLUMNS', 'STREAM_LIST_SORT_COLUMNS']:
|
||
assert marker in preferences_source, marker
|
||
for source_name, markers in {
|
||
"src/admin/web/vhosts/render.js": ["vhost_list_sort", "vhost_list_descending"],
|
||
"src/admin/web/docker.js": ["docker_vhost_sort", "docker_vhost_descending", "sortServiceRows"],
|
||
"src/admin/web/redirects.js": ["redirect_list_sort", "redirect_list_descending"],
|
||
"src/admin/web/host404.js": ["notfound_list_sort", "notfound_list_descending"],
|
||
"src/admin/web/streams.js": ["stream_list_sort", "stream_list_descending"],
|
||
}.items():
|
||
source_text = (root / source_name).read_text(encoding="utf-8")
|
||
for marker in markers:
|
||
assert marker in source_text, (source_name, marker)
|
||
assert 'data-panel="docker-vhosts"' in html and 'id="panel-docker-vhosts"' in html
|
||
assert 'id="settings-tab-docker-agents"' in html and 'id="settings-page-docker-agents"' in html
|
||
assert 'include_str!("web/docker.js")' in (root / 'src/admin/router.rs').read_text() and 'cache-live-toggle' in html
|
||
assert 'expose-revision' not in html and 'hideRevision' not in js and 'diagRevision' not in js
|
||
for path in ['src/settings.rs', 'src/admin/mod.rs', 'src/cache/mod.rs', 'src/headers.rs', 'src/tinyauth.rs', 'src/admin/web/vhosts/core.js']:
|
||
text = rust_source(root, path) if path.endswith('.rs') else (root / path).read_text()
|
||
assert 'x-rust-proxy-revision' not in text.lower(), f'legacy revision-header logic remains in {path}'
|
||
assert './dev.sh admin reset-password' not in html and re.search(r'rust-proxy\s+admin reset-password locally', html)
|
||
assert 'data-panel="redirects"' in html and 'id="panel-redirects"' in html
|
||
assert 'data-panel="configuration"' not in html and 'id="panel-configuration"' not in html
|
||
assert 'id="settings-tab-manual-config"' in html and 'id="settings-page-manual-config"' in html
|
||
assert 'id="security-audit-open"' in html and 'id="audit-dialog"' in html and 'id="audit-dialog-close"' in html
|
||
assert 'id="security-password-editor"' in html and 'id="security-policy-editor"' in html
|
||
assert 'docker-service-editor' in (root / "src/admin/web/docker.js").read_text() and 'docker-agent-details' in (root / "src/admin/web/docker.js").read_text()
|
||
balance_source = rust_source(root, "src/balance.rs")
|
||
assert 'mode: HealthMode::Tcp' in balance_source
|
||
assert 'member.backend.client.maintain_rotation();' in balance_source
|
||
assert 'member.backend.websocket_client.maintain_rotation();' in balance_source
|
||
upstream_client_source = rust_source(root, "src/upstream/client.rs")
|
||
assert 'fn maintain_rotation(&self)' in upstream_client_source
|
||
assert 'self.rotate(self.generation.load(Ordering::Acquire), "age", false);' in upstream_client_source
|
||
assert 'fn reusable_with(&self, other: &Self) -> bool' in upstream_client_source
|
||
assert 'member.backend = old.backend.clone();' in balance_source
|
||
assert 'compatible_pool_rebuild_reuses_upstream_clients' in balance_source
|
||
for marker in [
|
||
'pub struct PassiveHealth', 'enabled: false', 'pub passive_health: PassiveHealth',
|
||
'passive_health_failures_total', 'http_5xx_total', 'timeouts_total',
|
||
'connect_errors_total', 'io_errors_total', 'slow_responses_total',
|
||
'passive_health_is_opt_in_and_uses_separate_thresholds',
|
||
'unhealthy_source_hands_off_when_the_original_health_domain_recovers',
|
||
]:
|
||
assert marker in balance_source, marker
|
||
assert 'id="config-editor"' in html and 'id="save-json"' in html and 'id="export-config"' in html
|
||
assert 'toast(t("saved"));' in js and 'toast(t("saved") + envelope.revision);' not in js
|
||
for lang_name in ["en_US.json", "pl_pl.json"]:
|
||
translations = json.loads((root / "langs" / lang_name).read_text())["translations"]
|
||
assert translations["saved"] in {"Configuration applied.", "Zastosowano konfigurację."}
|
||
assert "revision" not in translations["saved"].lower() and "rewizj" not in translations["saved"].lower()
|
||
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 'id="stream-dialog"' in html and 'id="stream-list"' in html and 'id="stream-search"' in html
|
||
assert 'id="settings-page-streams"' not in html and 'id="streams-form"' not in html
|
||
assert all(f'include_str!("web/{name}.js")' in (root / 'src/admin/router.rs').read_text() for name in ('streams', 'redirects', 'host404'))
|
||
vhosts_js = read_vhosts_js(root)
|
||
assert '"force-ssl", "vhForceSsl"' in vhosts_js and 'force_ssl: yes("force-ssl")' in vhosts_js
|
||
assert 'vhForceSslNeedsHttp' in vhosts_js and 'tlsPorts.has(443)' in vhosts_js
|
||
assert '"forward-client-ip", "vhForwardClientIp"' in vhosts_js and 'forward_client_ip: yes("forward-client-ip")' in vhosts_js
|
||
assert 'id="vhost-search"' in html and '$("vhost-search").addEventListener("input", render)' in vhosts_js
|
||
for marker in ['id="pool-search"', 'id="pool-sort"', 'data-pool-column-toggle="vhost"', 'data-pool-column="generationAge"']:
|
||
assert marker in html, marker
|
||
for marker in ['toggleVhostEnabled', 'vhQuickEnable', 'vhQuickDisable', 'backend_table_columns', 'applyPoolTableView', 'saveSection("vhosts", next.vhosts || [])', 'rustProxyEnableControl']:
|
||
assert marker in vhosts_js, marker
|
||
assert 'rustproxy-backend-table-v1' not in vhosts_js and 'poolTablePreferenceKey' not in vhosts_js
|
||
assert 'window.rustProxyPreferences.update({' in vhosts_js and 'backend_table_sort' in vhosts_js and 'backend_table_descending' in vhosts_js
|
||
style_ui = (root / 'src/admin/web/style.css').read_text()
|
||
for marker in ['--scrollbar-thumb', '*::-webkit-scrollbar-thumb', '.backend-table-tools', '.vh-enable-control', '.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_ui, marker
|
||
preferences_js = (root / "src/admin/web/app/preferences.js").read_text()
|
||
assert 'dashboard-widget-toggle' in preferences_js and 'visible.type = "checkbox"' not in preferences_js
|
||
assert 'window.rustProxyVhosts?.applyPreferences(normalized);' in preferences_js, "saved backend table preferences must be reapplied from the shared account preference path"
|
||
core_js = (root / "src/admin/web/app/core.js").read_text()
|
||
assert 'window.rustProxyEnableControl = enhanceEnabledControl' in core_js
|
||
assert 'id="admin-session-days"' in html and 'min="7" max="30"' in html
|
||
assert 'member-local-risk' in vhosts_js and 'vhLocalListenerRiskConfirm' in vhosts_js
|
||
assert 'accept_local_listener_risk: yes("member-local-risk", row)' 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, "new backend drafts must require an explicit origin URL"
|
||
assert 'preserve_host: true' in vhosts_js, "new ordinary HTTP/1.1 backends must preserve public Host by default"
|
||
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']:
|
||
assert marker in vhosts_js, marker
|
||
assert 'cache: { enabled: false }' in vhosts_js, "new vhost drafts must default cache off"
|
||
docker_js = (root / "src/admin/web/docker.js").read_text()
|
||
assert 'preserve.checked = assignment?.preserve_host !== false' in docker_js, "new Docker backends must preserve public Host by default"
|
||
for marker in ['saveSection("docker-agents"', '/api/docker/refresh', 'docker-publish', 'docker-hostname', 'host-chip-editor docker-alias-editor', 'docker-target-ip', 'default_target_ip', 'docker-agent-fingerprint', 'docker-agent-certificate', 'docker-health-mode', 'docker-passive-enabled', 'docker-passive-5xx', 'docker-search', 'docker-show-hidden', 'dockerHideVhost', 'pool_routes', 'rustProxyRefresh.register("docker"']:
|
||
assert marker in docker_js, marker
|
||
docker_source = rust_source(root, "src/docker.rs")
|
||
for marker in ['pub struct Settings', 'pub struct Observation', 'pub struct HostAddress', 'pub struct Binding', 'aliases: Vec<String>', 'target_ip: Option<IpAddr>', 'tls_fingerprint_sha256', 'certificate_id: Option<String>', 'health_check: crate::balance::HealthCheck', 'passive_health: crate::balance::PassiveHealth', 'PinnedCertificateVerifier', 'fingerprint mismatch', 'effective_config', 'poll_loop', 'address_allowlist']:
|
||
assert marker in docker_source, marker
|
||
agent_source = read_docker_agent_source()
|
||
for marker in ['/v1/inventory', 'Authorization', '/containers/json?all=0', 'IsLoopback', 'localHostAddresses', 'detectedAddresses', 'Bindings', 'RUSTPROXY_DOCKER_AGENT_TOKEN', 'ensureSelfSignedCertificate', 'ListenAndServeTLS', 'RUSTPROXY_DOCKER_AGENT_TLS', 'CertificateSHA256']:
|
||
assert marker in agent_source, marker
|
||
assert 'pool-vhost-disabled' in vhosts_js and '.pool-vhost-disabled' in css
|
||
streams_js = (root / "src/admin/web/streams.js").read_text()
|
||
redirects_js = (root / "src/admin/web/redirects.js").read_text()
|
||
for marker in ["rustProxyRedirects", "redirectTargetType", "status_code", "cache_seconds", 'saveSection("redirects"']:
|
||
assert marker in redirects_js, marker
|
||
for marker in ["rustProxyStreams", "streamTrafficChart", "streamPendingRestart", "stream-listener-add", "chartHistory", "sessions_total", "bytes_in", "bytes_out"]:
|
||
assert marker in streams_js, marker
|
||
assert '$("stream-listener-add").addEventListener("click", () => { setPanel("settings"); window.rustProxySettings?.select("listeners"); });' in streams_js
|
||
integrations_js = (root / "src/admin/web/integrations.js").read_text()
|
||
assert "addressTagEditor" in integrations_js and "listener-address-chip-editor" in integrations_js
|
||
assert "stream-host-rows" not in integrations_js
|
||
assert 'const http80 = prefer(false, 80);' in integrations_js
|
||
assert 'if (http80) autoIds.add(http80.id);' in integrations_js and 'candidates.find(endpoint => !endpoint.binding?.tls)' in integrations_js
|
||
assert 'https443' not in integrations_js
|
||
assert 'window.rustProxyIntegrations.bindings(holder, selected, !editing)' in redirects_js
|
||
assert 'window.rustProxyIntegrations.hostnames(hostHolder' in redirects_js
|
||
assert 'id="redirect-hosts-editor"' in html
|
||
assert 'class="protocol-toggle-row"' in html
|
||
for marker in ['.vh-backend-card', '.auth-subcard', '.vh-cache-options[hidden]', '.protocol-toggle-row', '.listener-choices']:
|
||
assert marker in css, marker
|
||
openapi = (root / "src/admin/openapi.json").read_text()
|
||
assert '"load_balancing"' in openapi and '"LoadBalancingConfig"' in openapi and '"PassiveHealthConfig"' in openapi
|
||
assert 'Opt-in health decisions based on ordinary user traffic. Defaults to disabled.' in openapi
|
||
assert '"force_ssl"' in openapi and '"forward_client_ip"' in openapi
|
||
assert '"backend_header"' in openapi and '"backend_timing_header"' in openapi
|
||
assert '"StreamMetrics"' in openapi and '"streams"' in openapi
|
||
for marker in ['"version": "0.31.2"', '"/api/traffic"', '"capture_user_agent"', '"capture_country"', '"hot_auto_trim_enabled"', '"hot_auto_trim_after_seconds"', '"hot_auto_trim_target_percent"', '"/api/config/{section}"', '"ConfigSectionUpdate"', '"cold_on_idle"', '"cold_on_age"', '"cold_max_hot_seconds"', '"cold_on_lru"', '"cold_on_shutdown"', '"cold_gc_interval_seconds"', '"cold_gc_max_delete"', '"collapse_enabled"', '"collapse_wait_timeout_ms"', '"collapse_timeout_fallback"', '"stale_if_error_timeout"', '"stale_if_error_connection"', '"stale_if_error_dns"', '"stale_if_error_tls"', '"stale_if_error_statuses"', '"proactive_refresh_enabled"', '"proactive_refresh_before_expiry_seconds"', '"proactive_refresh_percent"', '"proactive_refresh_max_inflight"', '"TransformSettings"', '"TransformOverrides"', '"/api/cache/cold/cleanup"', '"/api/cache/invalidate"', '"/api/cache/bans"', '"/api/cache/bans/{id}"', '"CacheInvalidationRequest"', '"CacheInvalidationResponse"', '"ColdCleanupJob"', '"match_type"', '"regex_insensitive"', '"rewrite"', '"response"', '"covered_hosts"', '"default": true']:
|
||
assert marker in openapi, marker
|
||
for marker in ['"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
|
||
assert marker in openapi, marker
|
||
assert 'id="panel-notfound"' in html and 'id="notfound-dialog"' in html and 'id="notfound-hosts-editor"' in html
|
||
host404_js = (root / "src/admin/web/host404.js").read_text()
|
||
for marker in ['rustProxyNotFound', 'saveSection("not_found_hosts"', 'rustProxyIntegrations.hostnames', 'rustProxyIntegrations.bindings', 'BUILTIN_STATUSES', 'ERROR_STATUSES', 'rustProxyDrafts.register("notfound"', 'rustProxyEnableControl($("notfound-enabled"))']:
|
||
assert marker in host404_js, marker
|
||
assert 'for (let code = 200; code <= 599; code++)' not in host404_js
|
||
assert 'preserve_host: true' in vhosts_js, "new HTTP/1.1 backend drafts must preserve the public Host"
|
||
assert 'id="panel-acl-profiles"' in html and 'id="acl-profile-form"' in html
|
||
for marker in ['id="acl-profile-rule-rows"', 'id="acl-profile-rule-add"', 'id="acl-profile-import-rules"', 'id="acl-profile-mode-options"', 'id="acl-profile-preview-flow"', 'id="acl-profile-country-enabled"']:
|
||
assert marker in html, marker
|
||
management = (root / "src/admin/web/management.js").read_text()
|
||
for marker in ['collectRuleRows', 'parseBulkRules', 'guidedAction', 'allow_exceptions', 'block_exceptions', 'renderPreview', 'normalizeNetwork']:
|
||
assert marker in management, marker
|
||
for marker in ['function refreshTranslations()', 'refreshTranslations }']:
|
||
assert marker in management, marker
|
||
assert 'window.rustProxyAclProfiles?.refreshTranslations?.();' in js
|
||
assert 'normalizeAclRuleText' in js and 'ruleSyntaxLine' in js
|
||
cache_source = read_cache_source()
|
||
assert not (root / "src/cache.rs").exists()
|
||
for fragment in ["mod.rs", "model.rs", "lifecycle.rs", "cold.rs", "dashboard.rs", "tickets.rs", "lookup.rs", "policy.rs", "fill.rs", "collapse.rs", "range.rs", "invalidation.rs", "tests.rs"]:
|
||
assert (root / "src/cache" / fragment).is_file(), fragment
|
||
cache_mod = rust_source(root, "src/cache/mod.rs")
|
||
assert 'impl Cache {\n include!(' not in cache_mod, "include! cannot be used directly as Cache impl items"
|
||
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 {"), fragment
|
||
assert 'disabled_cache_strips_diagnostics_without_exposing_bypass_headers' in cache_source
|
||
for marker in ['pub enum MemoryPreset', 'pub enum ColdPreset', 'BACKGROUND_REVALIDATION_LIMIT', 'cleanup_slots']:
|
||
assert marker in cache_source, marker
|
||
for marker in ['selectedCacheMemoryPreset', 'selectedCacheColdPreset', 'applyRecommendedColdCachePreset', 'data-cache-cold-preset="recommended"']:
|
||
assert marker in js or marker in html, marker
|
||
assert 'if !self.enabled' in cache_source
|
||
proxy_source = rust_source(root, "src/proxy/mod.rs")
|
||
assert 'response.extensions().get::<LocalHostResponse>().is_none()' in proxy_source
|
||
assert '"cdn-cache-control"' in proxy_source and '"x-cache-resident-age"' in proxy_source
|
||
for marker in [
|
||
'cold_policy_can_disable_lru_demotion',
|
||
'cold_policy_can_disable_idle_demotion',
|
||
'cold_policy_can_demote_by_total_hot_residency_age',
|
||
'cold_on_age',
|
||
'cold_max_hot_seconds',
|
||
'graceful_shutdown_flush_moves_hot_objects_and_releases_resident_bytes',
|
||
'graceful_shutdown_flush_can_be_disabled',
|
||
'managed_update_flush_ignores_shutdown_toggle_when_cold_tier_is_enabled',
|
||
'pub async fn flush_hot_to_cold',
|
||
'pub async fn flush_hot_to_cold_for_update',
|
||
'cold_on_shutdown',
|
||
'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',
|
||
'stale_if_error_is_only_available_inside_its_window',
|
||
'transformed_etag_is_never_used_as_an_origin_validator',
|
||
'Deduplicate only the exact cached',
|
||
'matching_hot_key',
|
||
'matching_cold_key',
|
||
'concurrent_miss_single_flight_waits_until_fill_is_published',
|
||
'single_flight_timeout_and_leader_drop_release_the_key',
|
||
'cancelled_single_flight_waiter_releases_waiter_metric',
|
||
'single_flight_retries_after_another_vary_representation_is_published',
|
||
'cached_single_range_returns_206_and_preserves_validators',
|
||
'cached_range_supports_suffix_open_unsatisfied_and_if_range',
|
||
'range_parser_rejects_multipart_and_duplicate_fields_and_if_range_date_allows_older_representation',
|
||
'stale_if_error_policy_is_configurable_by_error_class_and_status',
|
||
'proactive_refresh_guards_deduplicate_and_bound_parallel_work',
|
||
'selective_purge_exact_prefix_tag_cache_key_and_query_are_precise',
|
||
'selective_purge_is_a_barrier_for_a_fill_started_before_the_purge',
|
||
'purge_of_one_vary_variant_removes_the_whole_variant_family',
|
||
'ban_is_lazy_blocks_old_object_allows_new_fill_and_can_be_removed',
|
||
'regex_and_root_prefix_require_explicit_wide_confirmation',
|
||
'selective_purge_removes_matching_cold_object',
|
||
]:
|
||
assert marker in cache_source, marker
|
||
transform_source = 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-',
|
||
]:
|
||
assert marker in transform_source, marker
|
||
assert 'collect bounded static response for transformation: {error}' in transform_source
|
||
assert '.context("collect bounded static response for transformation")' not in transform_source
|
||
lookup_source = rust_source(root, 'src/cache/lookup.rs')
|
||
model_source = rust_source(root, 'src/cache/model.rs')
|
||
css_minifier_source = rust_source(root, 'src/transform/css.rs')
|
||
assert 'let Some(_read_lease) = self.try_reserve_transient' in lookup_source
|
||
assert 'struct TransientLease {\n _inner: Arc<TransientLeaseInner>,' in model_source
|
||
assert 'struct TransientLease(Arc<TransientLeaseInner>);' not in model_source
|
||
assert 'last_char = out.chars().last();' not in css_minifier_source
|
||
assert css_minifier_source.count("last_char = Some(' ');") == 1
|
||
for name in ['js.rs', 'css.rs', 'html.rs', 'svg.rs', 'json.rs']:
|
||
assert (root / 'src/transform' / name).is_file(), name
|
||
for name in ['js.rs', 'css.rs']:
|
||
minifier_source = (root / 'src/transform' / name).read_text()
|
||
assert 'Vec<char>' not in minifier_source and '.chars().collect' not in minifier_source, name
|
||
assert 'into_bytes()' in minifier_source, name
|
||
assert 'pub mod transform;' in rust_source(root, 'src/lib.rs')
|
||
for marker in ['cache-cold-gc-interval', 'cache-cold-gc-max-delete', 'settings-tab-transform', 'transform-form', 'transform-memory-budget', 'transform-max-inflight', 'data-transform-preset="uber"', 'cache-cleanup-dialog']:
|
||
assert marker in html, marker
|
||
for marker in ['cache-transient-memory', 'cache-cold-queue-bytes', 'cache-eviction-limit', 'data-cache-memory-preset="recommended"', 'cache-memory-warning']:
|
||
assert marker in html, marker
|
||
for marker in ['effectiveCacheMemory', 'cacheMemoryHeadroom', 'cachePresetValues', 'applyCacheMemoryPreset', 'cacheMemoryPresetTooSmall', 'cacheObjectOverTransientWarning', 'cgroup_memory_max_bytes', 'transient_max_bytes', 'cold_queue_max_bytes', 'hot_eviction_max_objects']:
|
||
assert marker in js, marker
|
||
for marker in ['selectedTransformPreset', 'transformPresetValues', 'applyTransformPreset', 'updateTransformResourceSizing', 'memory_budget_bytes', 'max_inflight']:
|
||
assert marker in js, marker
|
||
assert 'envelope?.config.cache?.enabled === true && cacheToggle.value !== "off"' in vhosts_js
|
||
for marker in ['transform: { enabled: false }', 'vhTransform', 'collectTransform', 'compression: { enabled: null, gzip: null, br: null, zstd: null, deflate: null }', 'vh-compression-enabled', 'vh-compression-zstd', 'vh-compression-br', 'vh-compression-gzip', 'vh-compression-deflate', 'collectCompression']:
|
||
assert marker in vhosts_js, marker
|
||
docker_js = (root / 'src/admin/web/docker.js').read_text()
|
||
assert 'transform_enabled' in docker_js
|
||
cache_panel_js = (root / 'src/admin/web/cache-panel.js').read_text()
|
||
for marker in ['/api/cache/cold/cleanup', 'removed_entries', 'freed_bytes', '/api/cache/invalidate', '/api/cache/bans', 'cache-invalidation-operation', 'cache-invalidation-selector', 'cache-ban-ttl', 'confirm_wide', 'cachePurgeUrl']:
|
||
assert marker in cache_panel_js or marker in html, marker
|
||
for marker in ['cp-hot-entries-meter', 'cp-memory', 'cp-cold-entries-meter', 'cp-cold-disk']:
|
||
assert marker in html and marker in cache_panel_js, marker
|
||
for marker in ['data.settings?.max_entries', 'data.settings?.cold_max_entries', 'data.settings?.cold_max_bytes', 'setMeter(']:
|
||
assert marker in cache_panel_js, marker
|
||
compact_css = css.replace(" ", "")
|
||
for marker in ['progress::-webkit-progress-bar', 'progress::-webkit-progress-value', 'progress::-moz-progress-bar', '-webkit-appearance:none']:
|
||
assert marker in compact_css, marker
|
||
transform_panel_js = (root / 'src/admin/web/transform-panel.js').read_text()
|
||
for marker in ['/api/transform', 'tp-cache-hits', 'transform-reason-list']:
|
||
assert marker in transform_panel_js, marker
|
||
for marker in ['id="cache-live-chart"', 'id="cache-live-activity-chart"', 'id="cache-live-hit-line"', 'id="cache-live-miss-line"', 'id="cache-live-bypass-line"', 'id="cache-live-samples"', 'data-i18n="cacheLiveChartTitle"']:
|
||
assert marker in html, marker
|
||
for marker in ['id="transform-live-chart"', 'id="transform-live-activity-chart"', 'id="transform-live-applied-line"', 'id="transform-live-cache-line"', 'id="transform-live-skipped-line"', 'id="transform-live-error-line"', 'id="transform-live-samples"', 'data-i18n="transformLiveChartTitle"']:
|
||
assert marker in html, marker
|
||
assert '<details class="card function-live-chart" id="cache-live-chart">' in html and '<details class="card function-live-chart" id="transform-live-chart">' in html
|
||
for marker in ['liveChartSamples = { hits: [], misses: [], bypasses: [] }', 'LIVE_CHART_LIMITS = new Set([60, 120, 300, 600])', 'rustproxy.cache.liveChartSamples', 'performance.now()', 'sparklinePoints(liveChartSamples.hits, 900, 180, 10, peak)', 'slice(-liveChartLimit)']:
|
||
assert marker in cache_panel_js, marker
|
||
for marker in ['liveChartSamples = { applied: [], cache_hits: [], skipped: [], errors: [] }', 'LIVE_CHART_LIMITS = new Set([60, 120, 300, 600])', 'rustproxy.transform.liveChartSamples', 'performance.now()', 'sparklinePoints(liveChartSamples.applied, 900, 180, 10, peak)', 'slice(-liveChartLimit)']:
|
||
assert marker in transform_panel_js, marker
|
||
admin_cache_source = rust_source(root, 'src/admin/mod.rs')
|
||
for marker in ['"transform" => {', 'transform_status', 'cache_cold_cleanup', 'cache_cold_cleanup_job', 'cache_invalidate', 'cache_bans', 'cache_ban_delete', 'selective_cache_invalidation_api_manages_bans_and_rejects_wide_rules_without_confirmation']:
|
||
assert marker in admin_cache_source, 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()',
|
||
]:
|
||
assert marker in cache_source, marker
|
||
assert 'impl Drop for Candidate' not in cache_source, 'fill accounting must not block moving Candidate fields during finish'
|
||
redirect_source = rust_source(root, "src/redirect.rs")
|
||
for marker in ["pub struct Host", "pub struct Rule", "strip_prefix", "cache_seconds", "unconditional self-redirect"]:
|
||
assert marker in redirect_source, marker
|
||
proxy_source = rust_source(root, "src/proxy/mod.rs")
|
||
assert 'state.cache.purge(None)' not in proxy_source, 'proxy requests must not globally purge cache'
|
||
assert 'request.method() == Method::HEAD && cache_ticket.is_some()' in proxy_source
|
||
assert 'RangeSelection::Single' in proxy_source and 'request_range(request.method(), request.headers())' in proxy_source
|
||
assert 'claim_fill(ticket, cache_settings)' in proxy_source and 'cache_collapse_timeout_origin' in proxy_source
|
||
assert 'schedule_proactive_refresh' in proxy_source and 'cache_proactive_refreshed' in proxy_source
|
||
assert 'classify_stale_error' in proxy_source and 'allows_stale_error_status' in proxy_source
|
||
assert 'Varnish built-in behavior treats non-GET/HEAD methods as pass' in proxy_source
|
||
config_source = rust_source(root, "src/config.rs")
|
||
assert 'plain HTTP listener' in config_source and 'tls_ports.contains(&443)' in config_source
|
||
assert '#[serde(default)]\n pub tls_verify: bool' in config_source
|
||
assert 'backend_tls_verification_defaults_off_and_can_be_enabled_explicitly' in config_source
|
||
assert 'backend TLS server name must be the DNS name present in the certificate' in config_source
|
||
assert 'pub accept_local_listener_risk: bool' in config_source
|
||
assert 'accept_local_listener_risk = true' in config_source
|
||
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',
|
||
'backend_diagnostic_flags_compile_to_frontend',
|
||
'backend_header: host.backend_header',
|
||
'backend_timing_header: host.backend_timing_header',
|
||
]:
|
||
assert marker in config_source, marker
|
||
vhost_source = rust_source(root, "src/vhost.rs")
|
||
assert 'is_legacy_main_fallback' in vhost_source
|
||
assert 'values.length ? values : [{ id: "root"' not in vhosts_js
|
||
for marker in [
|
||
'OriginBackend',
|
||
'apply_backend_diagnostic_headers',
|
||
'x-rustproxy-backend-time',
|
||
'backend_diagnostics_are_opt_in_and_strip_spoofed_values',
|
||
'admin_session_passthrough',
|
||
'admin_control_plane',
|
||
'Some("admin_control_plane")',
|
||
'restore_admin_cookie',
|
||
'BEFORE returning a successful RFC 9220 response',
|
||
'websocket_phase("tunnel_ready")',
|
||
]:
|
||
assert marker in proxy_source, marker
|
||
generic_headers_source = rust_source(root, "src/headers.rs")
|
||
assert '"x-rust-proxy-backend"' in generic_headers_source and '"x-rustproxy-backend-time"' in generic_headers_source
|
||
headers_source = rust_source(root, "src/proxy/headers.rs")
|
||
for marker in ['pub fn restore_admin_cookie', 'admin_cookie_can_be_restored_only_from_captured_fragment']:
|
||
assert marker in headers_source, marker
|
||
settings_source = rust_source(root, "src/settings.rs")
|
||
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_source, marker
|
||
auth_source = rust_source(root, "src/auth/mod.rs")
|
||
auth_store = rust_source(root, "src/auth/store.rs")
|
||
for marker in ['30 * 24 * 60 * 60', 'session_cookie(secret: &str, secure: bool, max_age_seconds: i64)', 'browser_session_lifetime_is_configurable_without_sliding_expiry', 'session_cookie_uses_configured_remaining_lifetime']:
|
||
assert marker in auth_source, marker
|
||
for marker in ['create_browser_session', 'session_at_with_browser_lifetime', 'browser session lifetime must be 7..30 days', "created_at + ?"]:
|
||
assert marker in auth_store, marker
|
||
upstream_source = rust_source(root, "src/upstream.rs")
|
||
for marker in ['pub accept_local_listener_risk: bool', '!self_target || self.accept_local_listener_risk', 'explicit_risk_acceptance_allows_local_listener_but_not_private_policy_bypass']:
|
||
assert marker in upstream_source, marker
|
||
transport_tests = (root / "tests/transport_config.rs").read_text()
|
||
assert 'local_listener_backend_requires_explicit_risk_acceptance' in transport_tests and '127.0.0.1:9090' in transport_tests
|
||
runtime_source = rust_source(root, "src/runtime.rs")
|
||
for marker in ['fn cache_revision', 'CacheInvalidation::None', 'operational_saves_keep_cache_identity_but_cache_affecting_saves_rotate_it']:
|
||
assert marker in runtime_source, marker
|
||
admin = rust_source(root, "src/admin/mod.rs")
|
||
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 '"/assets/streams.js"' in admin and '"metrics": stream_metrics' in admin
|
||
for marker in ['"/api/config/{section}"', 'async fn update_section', 'section_updates_are_revisioned_and_do_not_require_full_config_payloads', 'panel_subpage_routes_survive_browser_refresh', 'fn panel_document_path', 'panel_document_path(&path)', 'certificate_metadata', 'inspected_metadata', 'session_at_with_browser_lifetime', 'session_lifetime_seconds', 'HeaderValue::from_str(&auth::session_cookie(']:
|
||
assert marker in admin, marker
|
||
for route in ['/overview', '/vhosts', '/redirects', '/404-hosts', '/streams', '/acl-profiles', '/configuration', '/tls', '/cache', '/transform', '/logging', '/settings']:
|
||
assert f'.route("{route}", get(public::index))' in admin, route
|
||
app_js = read_app_js(root)
|
||
for marker in ['const PANEL_PATHS', 'history.pushState', 'window.addEventListener("popstate"', 'async function saveSection', 'async function revalidateSession', 'Object.values(PANEL_PATHS), "/configuration"', 'certificateCovers', 'covered_hosts', 'refreshCertificateInventory', 'certificateDetails', 'certificate-domain-pills', 'certificateIssuerName', 'certificateDaysRemaining', 'certificateRenewalStatus', 'recoverInvalidOrigin', 'auth.invalid_origin', 'ORIGIN_RECOVERY_KEY']:
|
||
assert marker in app_js, marker
|
||
for marker in ['.certificate-details', '.certificate-domain-pills', '.certificate-card .pill', '.certificate-operation-card', '.renewal-meta', '#panel-tls .certificate-tabs']:
|
||
assert marker in css, marker
|
||
acme_js = (root / "src/admin/web/acme.js").read_text()
|
||
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")', 'selectTab("installed")']:
|
||
assert marker in acme_js, marker
|
||
for marker in ['resetIssueFormJob = value.job?.id || null', 'value.status === "ready"', 'resetIssueForm(); resetIssueFormJob = null', 'value.status === "failed"']:
|
||
assert marker in acme_js, marker
|
||
assert 'render(value.job); resetIssueForm();' not in acme_js, "ACME form must not reset before issuance actually succeeds"
|
||
for marker in ['id="acme-runtime-card"', 'id="acme-runtime-message"', 'id="acme-runtime-state"', 'id="acme-runtime-path"']:
|
||
assert marker in html, marker
|
||
for marker in ['function renderRuntime(value)', 'runtimeAvailable', 'certificateRuntimeUnavailable', 'acme.runtime_checking']:
|
||
assert marker in acme_js, marker
|
||
assert 'rust-proxy --data-dir ./data admin setup-token' in html
|
||
assert 'rust-proxy --data-dir /var/lib/rustproxy admin setup-token' in html
|
||
login_html_source = (root / "src/admin/web/login.html").read_text(encoding="utf-8")
|
||
assert 'rust-proxy --data-dir ./data admin setup-token' in login_html_source
|
||
assert 'rust-proxy --data-dir /var/lib/rustproxy admin setup-token' in login_html_source
|
||
login_js = (root / "src/admin/web/login.js").read_text()
|
||
assert 'const panelPaths = new Set' in login_js and 'panelPaths.has(location.pathname)' in login_js
|
||
assert '"/404-hosts"' in login_js
|
||
assert 'async function redirectExistingSession' in login_js and '/api/auth/session' in login_js
|
||
settings_js = (root / "src/admin/web/settings.js").read_text()
|
||
assert '["/settings", "/configuration", "/logging"].includes(location.pathname)' in settings_js
|
||
assert 'new URLSearchParams(location.search).get("tab")' in settings_js and 'function syncLocation()' in settings_js and 'history.replaceState' in settings_js
|
||
assert 'url.pathname = "/settings"' in settings_js and 'url.searchParams.set("tab", active)' in settings_js
|
||
assert 'data-settings-tab="certificate-api"' in html and 'id="settings-page-certificate-api"' in html
|
||
for marker in ['data-cache-settings-tab="general"', 'data-cache-settings-tab="resilience"', 'data-cache-settings-tab="cold"', 'data-cache-settings-tab="diagnostics"']:
|
||
assert marker in html, marker
|
||
for marker in ['data-settings-tab="connections"', 'data-http-settings-tab="http2"', 'data-http-settings-tab="http3"', 'id="http2-form"', 'id="http2-listener-rows"', 'id="http3-form"', 'id="http3-listener-rows"']:
|
||
assert marker in html, marker
|
||
for marker in ['const tabAliases = { http2: "connections" }', 'data-http-settings-tab', 'selectHttpTab("http2")']:
|
||
assert marker in settings_js or marker in html, marker
|
||
for marker in ['renderHttp2Settings', 'collectHttp2Listeners', 'http2SettingsControls', 'preservedHttp2', 'listener-h2', 'h2Configure', 'selectHttpTab?.("http2")', 'renderHttp3Settings', 'collectHttp3Listeners', 'http3SettingsControls', 'preservedHttp3', 'listener-h3', 'h3Configure', 'selectHttpTab?.("http3")']:
|
||
assert marker in integrations_js, marker
|
||
h2_settings_controls = integrations_js.split('function http2SettingsControls', 1)[1].split('const h3Defaults', 1)[0]
|
||
assert 'listener-h2' not in h2_settings_controls, 'HTTP/2 enablement belongs to the listener editor, not the tuning page'
|
||
h3_settings_controls = integrations_js.split('function http3SettingsControls', 1)[1].split('function addRow', 1)[0]
|
||
assert 'h3Enable' not in h3_settings_controls, 'HTTP/3 enablement belongs to the listener editor, not the tuning page'
|
||
for marker in ['https://git.linuxiarz.pl/gru/RustProxy', 'https://rustproxy.linuxiarz.pl/', 'data-settings-tab="system-info"', 'id="runtime-python"', 'id="acme-runtime-components"']:
|
||
assert marker in html, marker
|
||
for language in ['langs/en_US.json', 'langs/pl_pl.json']:
|
||
language_text = (root / language).read_text(encoding='utf-8')
|
||
assert './dev.sh tls deploy' not in language_text
|
||
assert '/api/certificates/{certificate_id}/reload' in language_text
|
||
assert '/etc/letsencrypt/live/example.com/fullchain.pem' in language_text
|
||
startup_source = rust_source(root, 'src/main/admin_runtime.rs')
|
||
for marker in ['https://git.linuxiarz.pl/gru/RustProxy', 'https://rustproxy.linuxiarz.pl/']:
|
||
assert marker in startup_source, marker
|
||
assert 'Python: {} (ACME mode:' not in startup_source, 'startup banner must not print speculative ACME runtime state'
|
||
for marker in ['const protocols = ["H1"]', 'endpoint.http2', 'protocols.push("H2")', 'endpoint.h2c', 'protocols.push("H2C")', 'protocols.push("H3/UDP")']:
|
||
assert marker in app_js, 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
|
||
assert '"certificate_runtime": state.acme.runtime_view()' in rust_source(root, 'src/admin/status.rs')
|
||
assert 'data-i18n="processDiagnostics">Process<' in html
|
||
for marker in ['function setOptionalMetric', 'setOptionalMetric("process-rss-anon"', 'setOptionalMetric("system-swap"', 'closest(".mini-live-chart").hidden']:
|
||
assert marker in app_js, marker
|
||
for marker in ['/api/certificate-api', 'certificate-api-form', 'certificate-api-revoke-all', 'certificate-api-credentials', 'data-cache-settings-tab']:
|
||
assert marker in settings_js or marker in html, marker
|
||
db_source = rust_source(root, "src/db.rs")
|
||
acme_source = rust_source(root, "src/acme.rs")
|
||
acme_runtime_source = (root / "src/acme/runtime_env.rs").read_text(encoding="utf-8") + (root / "src/acme/runtime_bootstrap.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")
|
||
assert 'include!("runtime_bootstrap.rs")' in (root / "src/acme/runtime_env.rs").read_text(encoding="utf-8")
|
||
for marker in ['CERTBOT_PACKAGES', 'certbot==5.8.0', 'certbot-dns-cloudflare==5.8.0', 'certbot-dns-ovh==5.8.0', 'certbot-dns-rfc2136==5.8.0', 'join("acme-python")', 'virtualenv>=20,<21', 'system_has_pip', 'distro_certbot_available', 'acme.runtime_python_missing', 'acme.runtime_pip_missing', 'acme.runtime_venv_failed', 'acme.runtime_certbot_install_failed', '--break-system-packages']:
|
||
assert marker in acme_runtime_source, marker
|
||
for marker in ['venv_certbot', 'version.arg("--version")', 'RUSTPROXY_CERTBOT_EXECUTABLE', 'managed_certbot_executable']:
|
||
assert marker in acme_runtime_source, marker
|
||
for marker in ['ACME runtime check complete', 'status = runtime.status', 'mode = runtime.mode', 'python = runtime.python', 'pip = runtime.pip', 'certbot = runtime.certbot', 'managed_env = runtime.managed_directory.as_deref().unwrap_or("-")']:
|
||
assert marker in acme_runtime_source, marker
|
||
main_source = rust_source(root, "src/main.rs")
|
||
logging_source = rust_source(root, "src/logging.rs")
|
||
secret_store_source = rust_source(root, "src/secret_store.rs")
|
||
backup_source = rust_source(root, "src/backup.rs")
|
||
config_source = rust_source(root, "src/config.rs")
|
||
acme_worker_source = (root / "scripts/runtime/acme_provider.py").read_text()
|
||
for marker in ['def certbot_prefix', 'RUSTPROXY_CERTBOT_EXECUTABLE', 'return [managed_certbot]', '[*command_prefix, "renew"', 'certbot_command(value, work, credential_file, command_prefix)']:
|
||
assert marker in acme_worker_source, marker
|
||
for feature_marker in [
|
||
"AES_256_GCM", "master.key", "O_NOFOLLOW", "migrate_acme_credentials",
|
||
"secret_store", "SecretMetadata",
|
||
]:
|
||
assert feature_marker in secret_store_source or feature_marker in db_source, feature_marker
|
||
for feature_marker in [
|
||
"Argon2", "RPROXYB1", "RPROXYA1", "backup create", "secret-store-key.enc",
|
||
"validate_backup_version", "stage", "ARCHIVE_VERSION: u32 = 2", "EntryKind::Symlink",
|
||
"try_lock_directory", "validate_snapshot_symlinks", "validate_symlink_target",
|
||
]:
|
||
assert feature_marker in backup_source or feature_marker in main_source, feature_marker
|
||
assert "VACUUM INTO" in db_source and "consistent SQLite snapshot" in db_source
|
||
for feature_marker in [
|
||
"ErrorLogSettings", "ERROR_LOG_QUEUE", "RedactingStderr", "open_error_file",
|
||
"ErrorLogLayer", "ERROR",
|
||
]:
|
||
assert feature_marker in logging_source, feature_marker
|
||
assert 'regex::Regex::new(r#"(?i)((?:authorization|' in logging_source, "KV redaction regex must use a hash-delimited raw string"
|
||
assert '[^\\s,;\"\']+" )' not in logging_source, "do not reintroduce the unterminated raw-string form in KV redaction"
|
||
assert "pub error_log: crate::logging::ErrorLogSettings" in config_source
|
||
assert "credential_ini_b64" in acme_source and "credential_ini_b64" in acme_worker_source
|
||
assert "STANDARD.encode(credential.as_slice())" in acme_source, "Base64 input must be an unambiguous byte slice"
|
||
assert "STANDARD.encode(credential.as_ref())" not in acme_source, "Zeroizing<Vec<u8>>::as_ref() is ambiguous for Engine::encode"
|
||
assert "&mut *ciphertext" in secret_store_source, "ring seal must receive the underlying Vec<u8>, not an ambiguous AsMut target"
|
||
assert "&mut *encrypted" in backup_source, "ring seal must receive the underlying Vec<u8>, not an ambiguous AsMut target"
|
||
assert "seal_in_place_append_tag(\n Nonce::assume_unique_for_key(nonce),\n Aad::from(aad(secret_id, provider)),\n ciphertext.as_mut()," not in secret_store_source
|
||
assert "seal_in_place_append_tag(\n Nonce::assume_unique_for_key(nonce_bytes),\n Aad::from(KEY_AAD),\n encrypted.as_mut()," not in backup_source
|
||
assert "finally:" in acme_worker_source and "credential_file.unlink" in acme_worker_source
|
||
for marker in ['certificate_api_credentials', 'upsert_certificate_api_credential', 'revoke_certificate_api_credential', 'verify_certificate_api_credential', '0005_certificate_api.sql', '0006_certificate_api_credentials.sql']:
|
||
assert marker in db_source, marker
|
||
for marker in ['/api/certificate-api', 'certificate_api_credentials', 'certificate_api_operation', '/api/live-logs', 'CertificateApiBearer']:
|
||
assert marker in admin or marker in openapi, marker
|
||
for marker in ['/api/certificates/{certificate_id}/reload', 'certificate_reload_one', 'reload_certificate(&certificate_id)', 'CertificateReloadOneResult', '/api/certificate-api/{key_id}']:
|
||
assert marker in admin or marker in openapi, marker
|
||
for marker in ['metric-bytes-down', 'metric-bytes-up', 'connection-chart', 'network-interface-hide', 'rp-hidden-network-interfaces']:
|
||
assert marker in html or marker in app_js or marker in (root / 'src/admin/web/style.css').read_text(), marker
|
||
assert 'valid_certificate_api_key_id' in admin and 'sleep_until(next_auth_check)' in admin
|
||
for marker in [
|
||
'livelogs: bool', 'LiveLogFilters', 'handle_live_command', 'live_logs_command',
|
||
'create_live_log_session', 'command_input_open', 'buffer.extend_from_slice(&data)',
|
||
'sse_parser_tolerates_utf8_split_across_transport_frames',
|
||
'LocalAdminCertificateVerifier', 'detect_local_admin_scheme',
|
||
'local_admin_scheme_from_probe', 'admin.uri("/api/live-logs")',
|
||
]:
|
||
assert marker in main_source or marker in rust_source(root, 'src/auth/store.rs'), marker
|
||
assert '.uri(format!("http://{addr}/api/live-logs"))' not in main_source
|
||
assert 'http://{addr}/healthz' in main_source and 'StatusCode::PERMANENT_REDIRECT' in main_source
|
||
assert 'Ok(line) if command_tx.send(line).is_ok()' not in main_source, 'do not move String from a match guard in --livelogs stdin handling'
|
||
assert 'Ok(line) => {' in main_source and 'command_tx.send(line).is_err()' in main_source
|
||
for marker in ['LIVE_STREAM_CAPACITY', 'broadcast::Sender<LogEntry>', 'pub fn subscribe']:
|
||
assert marker in logging_source, marker
|
||
assert 'SCHEMA_VERSION: i64 = 9' 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 and '0009_certificate_api_grants.sql' in db_source
|
||
for marker in ['replace_as_restore', 'config.restore', 'source_revision']:
|
||
assert marker in db_source, marker
|
||
for marker in ['restore_config_history', '/api/config/history/restore', 'expected_revision']:
|
||
assert marker in admin, marker
|
||
for marker in ['pub async fn restore_as', 'replace_as_restore']:
|
||
assert marker in runtime_source, marker
|
||
for marker in ['restoreConfigRevision', 'config-history-restore', 'configHistoryRestoreConfirm']:
|
||
assert marker in app_js or marker in html, marker
|
||
assert '"/api/config/history/restore"' in openapi
|
||
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 'let cert_dir = Some(directory.clone());' in main_source, 'certificate directory must remain available after ValidationPolicy construction'
|
||
assert 'accept-remote-admin-risk' in main_source and 'hide = true' in main_source
|
||
for marker in [
|
||
'RUST_PROXY_UPDATE_REPOSITORY_URL',
|
||
'RUST_PROXY_UPDATE_VERSION_URL',
|
||
'RUST_PROXY_UPDATE_CHECK_INTERVAL_SECS',
|
||
'rust_proxy::update_check::Config::embedded()?.with_overrides(',
|
||
'state.start_update_check(update_check);',
|
||
]:
|
||
assert marker in main_source, marker
|
||
runtime_source = rust_source(root, "src/runtime.rs")
|
||
assert 'pub software_update: Arc<crate::update_check::SharedStatus>' in runtime_source
|
||
assert 'pub fn start_update_check(self: &Arc<Self>, config: crate::update_check::Config)' in runtime_source
|
||
assert 'payload["software_update"] = json!(state.software_update.snapshot());' in admin
|
||
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 and '_unmatched' 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 and '204 | 205 | 304' in pages_rust
|
||
assert 'store.load_certificate_renewals().await?' in main_source and 'state.acme.restore_renewals(renewal_observations)' in main_source
|
||
assert 'renewals.json' not in main_source
|
||
upgrade_source = (root / 'scripts/deploy/upgrade.py').read_text()
|
||
assert 'legacy = certs_dir / "renewals.json"' in upgrade_source and '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_source and 'flush_hot_to_cold_for_update' in main_source
|
||
tls_source = rust_source(root, "src/tls.rs")
|
||
assert 'pub fn validate_certificate_removals' in tls_source and 'administration panel' in tls_source
|
||
assert 'pub fn effective_cipher_suites' in tls_source
|
||
vhost_source = rust_source(root, 'src/vhost.rs')
|
||
assert 'pub websocket_rfc9220: bool' in vhost_source and 'RFC 9220 requires WebSocket support to be enabled' in vhost_source
|
||
for marker in ['pub struct ProxyTimeouts', 'connect_timeout_seconds', 'response_header_timeout_seconds', 'read_timeout_seconds', 'send_timeout_seconds']:
|
||
assert marker in vhost_source, marker
|
||
docker_source = 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)', 'poll_interval_seconds.saturating_mul(1000)']:
|
||
assert marker in docker_source, marker
|
||
docker_js = (root / 'src/admin/web/docker.js').read_text()
|
||
for marker in ['docker-live-refresh', 'saveAssignment(agentId, row, publish)', 'docker-acl-profile', 'docker-proxy-read', 'poll_interval_seconds']:
|
||
assert marker in docker_js, marker
|
||
agent_installer = (root / 'docker-agent/install.sh').read_text()
|
||
assert 'Bearer token:' in agent_installer and 'RUSTPROXY_DOCKER_AGENT_TOKEN=' in agent_installer
|
||
assert 'Kind::Builtin' in pages_rust and 'builtin_application_page' in pages_rust
|
||
assert 'pub covered_hosts: Vec<String>' in tls_source and 'fn certificate_dns_names' in tls_source and 'pub fn certificate_covered_hosts' in tls_source and 'legacy_certificate_coverage_can_be_read_from_existing_pem' in tls_source and 'assert_eq!(a.covered_hosts' in tls_source
|
||
acl_source = rust_source(root, 'src/acl.rs')
|
||
assert '/Ubuntu%2014.04%20LTS/' in acl_source and 'repeated_slashes_are_normalized_for_routing' in acl_source and 'merge_slashes' in acl_source
|
||
for marker in ['pub fn allows_with_country(', 'ip_action == Some(Action::Deny)', 'CountryMode::Allow', 'ip_action == Some(Action::Allow) || country_listed', 'country_allow_and_ip_allow_are_alternatives_inside_one_scope', 'country_block_and_ip_deny_are_alternatives_inside_one_scope', 'plain_host_addresses_are_valid_acl_rules', 'fn parse_network(']:
|
||
assert marker in acl_source, marker
|
||
policies_source = (root / 'tests/policies.rs').read_text()
|
||
assert 'percent_encoded_spaces_are_valid_request_paths' in policies_source and 'repeated_slashes_route_normally_but_reach_upstream_unchanged' in policies_source and 'local_location_response_uses_response_rules_without_opening_backend' in policies_source and 'local_location_response_still_respects_route_acl' in policies_source
|
||
http3_source = rust_source(root, "src/http3.rs")
|
||
drop_impl = http3_source.split('impl Drop for ReceiveBody', 1)[1].split('#[cfg(test)]', 1)[0]
|
||
assert 'self.stream.stop_sending(' not in drop_impl
|
||
for marker in ['h3::quinn::RecvStream', 'websocket_rfc9220_on(local)', 'run_h3', 'H3_REQUEST_CANCELLED']:
|
||
assert marker in http3_source or marker in rust_source(root, 'src/proxy/websocket.rs'), marker
|
||
assert 'h3_quinn' not in http3_source and 'h3_quinn' not in (root / 'tests/http3.rs').read_text()
|
||
h3_test = (root / 'tests/http3.rs').read_text()
|
||
assert 'RFC 9220: Extended CONNECT negotiates WebSocket on the same H3 stream.' in h3_test and '"websocket_rfc9220":true' in h3_test
|
||
assert 'Method::CONNECT' in h3_test and 'h3::ext::Protocol' in h3_test and '.send_data(' in h3_test
|
||
admin_source = rust_source(root, 'src/admin/mod.rs')
|
||
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', 'trusted_https_reverse_proxy_accepts_external_origin_and_refreshes_secure_cookie']:
|
||
assert marker in admin_source, marker
|
||
ws_source = rust_source(root, 'src/proxy/websocket.rs')
|
||
assert 'upstream: Upgraded' in ws_source
|
||
assert 'timeout(Duration::from_secs(10), upstream)' not in ws_source
|
||
for marker in ['websocket_phase("tunnel_started")', 'websocket_phase("tunnel_closed")', 'h3_reset("H3_REQUEST_CANCELLED")']:
|
||
assert marker in ws_source, marker
|
||
for marker in ['http_version', 'transport', 'websocket_bootstrap', 'websocket_phase', 'websocket_error', 'h3_reset_code', 'upstream_status']:
|
||
assert marker in rust_source(root, 'src/logging.rs'), marker
|
||
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 ['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_source, marker
|
||
prometheus_source = rust_source(root, 'src/prometheus.rs')
|
||
for marker in ['allow_trusted_network_without_token', 'include_traffic', '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', 'passive_http_5xx_total', 'passive_timeouts_total', 'passive_connect_errors_total', 'passive_io_errors_total', 'passive_slow_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', 'rust_proxy_traffic_country_requests', 'rust_proxy_traffic_user_agent_family_requests_total', 'rust_proxy_traffic_source_requests_total', 'rust_proxy_traffic_country_capture_enabled', 'rust_proxy_traffic_user_agent_capture_enabled', 'rust_proxy_traffic_route_requests', 'rust_proxy_web_services_configured', 'rust_proxy_web_service_info', 'rust_proxy_vhost_source_info', 'retry_attempts_total', 'retry_successes_total', 'upstream_client_generation', 'upstream_client_generation_requests', 'upstream_client_generation_age_seconds', 'upstream_client_rotations_total']:
|
||
assert marker in prometheus_source, marker
|
||
assert 'if docker_targets.contains_key(&pool.id)' in prometheus_source
|
||
assert 'if docker_targets.contains_key(id)' in prometheus_source
|
||
assert 'rust_proxy_docker_vhost_exchange_duration_seconds' in prometheus_source
|
||
settings_source = (root / 'src/admin/web/settings.js').read_text()
|
||
assert 'prom-trusted-no-token' in settings_source and 'include_traffic' in settings_source and 'prom-traffic' in settings_source and 'include_docker_vhosts' in settings_source and 'include_streams' in settings_source and 'include_logging' in settings_source
|
||
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 ['validate_h3', 'H3Tunnel', 'h3_client_to_backend', 'backend_to_h3', 'rfc9220_bootstrap_uses_extended_connect_without_h1_proof_headers']:
|
||
assert marker in ws_source, marker
|
||
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_source, marker
|
||
for marker in ['local_location_response', 'route.rewritten_path(&path)', 'encoded_origin_path', 'client_ip.to_string()']:
|
||
assert marker in proxy_source, marker
|
||
assert proxy_source.index('RustProxy: denied by access policy') < proxy_source.index('if let Some(spec) = &route.response') < proxy_source.index('let (backend, backend_lease, affinity_cookie)')
|
||
for marker in ['location-help-dialog', 'cache-memory-sparkline', 'cache-hit-sparkline', 'cache-miss-sparkline', 'cache-bypass-sparkline', 'process-cpu-sparkline', 'process-rss-sparkline', 'network-rx-sparkline', 'latency-sparkline', 'sourceIp']:
|
||
assert marker in html, marker
|
||
for marker in ['locationMatchRegex', 'headersVisualBuilder', 'headersAdvancedJson', 'openLocationHelp', 'locationPreviewTitle', 'location-preview-grid', 'vh-header-rule-fields', 'cloudflare-client-ip', 'timing-headers']:
|
||
assert marker in vhosts_js, marker
|
||
for marker in ['lastApiLatencyMs', 'cacheMemorySamples', 'cacheTransientSamples', 'cacheColdStorageSamples', 'cacheHitSamples', 'processCpuSamples', 'processRssSamples', 'networkRxSamples', 'responseLatencySamples', 'client_ip']:
|
||
assert marker in app_js, marker
|
||
logging_source = rust_source(root, 'src/logging.rs')
|
||
assert 'pub client_ip: Option<String>' in logging_source and '"client_ip" => entry' in logging_source and '.client_ip' in logging_source
|
||
for marker in ['check_rustproxy_api', 'response time', 'backend']:
|
||
assert marker in (root / 'nagios/README.md').read_text(), marker
|
||
stream_source = rust_source(root, "src/stream.rs")
|
||
for marker in ["pub struct Metrics", "pub struct MetricsView", "struct ActiveSession", "sessions_total", "sessions_completed", "packets_in", "packets_out", "metrics_view_reports_l4_counters"]:
|
||
assert marker in stream_source, marker
|
||
transport_doc = (root / "docs/TRANSPORT.md").read_text()
|
||
security_doc = (root / "docs/SECURITY.md").read_text()
|
||
api_access_doc = (root / "docs/API_ACCESS.md").read_text()
|
||
assert 'accept_local_listener_risk: true' in transport_doc and '127.0.0.1:9090' in transport_doc
|
||
assert 'accept_local_listener_risk=true' in security_doc and 'including port 9090' in security_doc
|
||
assert '7–30' in api_access_doc and 'default 30' in api_access_doc
|
||
not_found_source = rust_source(root, 'src/not_found.rs')
|
||
for marker in ['pub struct Host', 'pub struct CompiledHost', 'default_status', 'crate::pages::Page']:
|
||
assert marker in not_found_source, marker
|
||
pages_source = rust_source(root, 'src/pages.rs')
|
||
assert 'pub struct MinimalWelcome' in pages_source and 'HeaderValue::from_static("no-store")' in pages_source
|
||
assert 'builtin_welcome_is_minimal_and_never_cacheable' in pages_source
|
||
assert 'minimal_welcome' in proxy_source and 'cache-control' in proxy_source
|
||
make_zip = (root / "make_zip.py").read_text()
|
||
for marker in ["SOURCE_DATE_EPOCH", "ZipInfo", "deterministic_mode", "compresslevel=9"]:
|
||
assert marker in make_zip, marker
|
||
assert "'debian'" not in make_zip and "'grafana'" in make_zip and "'docker-agent'" in make_zip
|
||
assert "'UPDATE_CHECK.toml'" in make_zip
|
||
# Debian packaging is maintained outside this source tree.
|
||
assert not (root / 'debian').exists()
|
||
grafana = json.loads((root / 'grafana/rustproxy-overview.json').read_text())
|
||
assert grafana['uid'] == 'rustproxy-0170' and grafana['schemaVersion'] >= 39
|
||
expressions = '\n'.join(target.get('expr','') for panel in grafana['panels'] for target in panel.get('targets',[]))
|
||
for marker in ['rust_proxy_process_rss_bytes', 'rust_proxy_cache_transient_bytes', 'rust_proxy_backend_latency_seconds', 'rust_proxy_stream_active', 'rust_proxy_log_sink_queue_dropped_total', 'rust_proxy_traffic_source_requests_total', 'rust_proxy_traffic_country_requests', 'rust_proxy_traffic_user_agent_family_requests_total', 'rust_proxy_traffic_country_capture_enabled', 'rust_proxy_traffic_user_agent_capture_enabled', 'rust_proxy_traffic_route_requests', '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 expressions, marker
|
||
assert any(variable.get('name') == 'traffic_vhost' for variable in grafana['templating']['list'])
|
||
assert any(panel.get('title') == 'Traffic analytics and Web Server' for panel in grafana['panels'])
|
||
assert any(panel.get('title') == 'Traffic capture state' for panel in grafana['panels'])
|
||
assert any(panel.get('title') == 'Top configured routes' for panel in grafana['panels'])
|
||
# 0.27.3 compression/protocol/dashboard UX regressions.
|
||
compression_source = rust_source(root, "src/compression.rs")
|
||
route_cache_source = rust_source(root, "src/proxy/forward/route_cache.rs")
|
||
backup_source = rust_source(root, "src/backup.rs")
|
||
rate_limit_source = rust_source(root, "src/rate_limit.rs")
|
||
metrics_source = rust_source(root, "src/metrics.rs")
|
||
admin_source = rust_source(root, "src/admin/mod.rs")
|
||
style_source = (root / "src/admin/web/style.css").read_text()
|
||
docker_ui = (root / "src/admin/web/docker.js").read_text()
|
||
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]",
|
||
"fn preferred_encoding", "pub struct Overrides", "pub fn scoped", "pub fn resolve",
|
||
"defaults_enable_modern_compression_without_deflate",
|
||
"equal_quality_uses_zstd_then_br_then_gzip_then_deflate",
|
||
"quality_weights_win_before_server_priority",
|
||
"cookies_authorization_and_private_cache_policy_do_not_disable_compression",
|
||
"existing_content_encoding_is_never_recompressed",
|
||
"no_transform_range_and_sse_are_not_compressed",
|
||
"age_header_survives_downstream_compression",
|
||
"CompressionLevel::Fastest",
|
||
]:
|
||
assert marker in compression_source, marker
|
||
assert "request.method() == Method::GET || request.method() == Method::HEAD" in route_cache_source
|
||
for marker in ['"acme-python"', '"acme_python"']:
|
||
assert marker in backup_source, marker
|
||
for marker in [
|
||
"buckets: HashMap<String, HashMap<IpAddr, Bucket>>",
|
||
"state.sweep_expired(now, false)",
|
||
"state.sweep_expired(now, true)",
|
||
]:
|
||
assert marker in rate_limit_source, marker
|
||
for forbidden in ["pub struct AllowCookie", "private_or_no_transform"]:
|
||
assert forbidden not in compression_source, forbidden
|
||
compression_finalize_source = rust_source(root, "src/proxy/upstream_helpers.rs")
|
||
for marker in [
|
||
"let global_compression = &snapshot.envelope.config.compression",
|
||
"frontend.compression.resolve(global_compression, &compression)",
|
||
"service.compression.resolve(global_compression, &compression)",
|
||
]:
|
||
assert marker in compression_finalize_source, marker
|
||
assert "AllowCookie" not in compression_finalize_source
|
||
openapi_0270 = json.loads((root / "src/admin/openapi.json").read_text())
|
||
compression_override_properties = openapi_0270["components"]["schemas"]["CompressionOverrides"]["properties"]
|
||
for prop in ["enabled", "zstd", "br", "gzip", "deflate"]:
|
||
assert prop in compression_override_properties, prop
|
||
docker_assignment_properties = openapi_0270["components"]["schemas"]["DockerAssignment"]["properties"]
|
||
for prop in ["compression_enabled", "compression_zstd", "compression_br", "compression_gzip", "compression_deflate"]:
|
||
assert prop in docker_assignment_properties, prop
|
||
web_service_ui_0270 = (root / "src/admin/web/web-server.js").read_text()
|
||
for marker in [
|
||
'vh-compression-options',
|
||
'compressionOptions.hidden = disabled',
|
||
'input.disabled = disabled',
|
||
]:
|
||
assert marker in vhosts_js, marker
|
||
for marker in [
|
||
'compressionEnabled.checked = assignment?.compression_enabled !== false',
|
||
'compression_zstd: null, compression_br: null, compression_gzip: null, compression_deflate: null',
|
||
'switches.append(preserveLabel, forceSslField, websocketField, cacheField, compressionField, transformField)',
|
||
]:
|
||
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 'id="web-service-compression-options"' in html
|
||
for marker in [
|
||
'function syncCompression()',
|
||
'$("web-service-compression-options").hidden = disabled',
|
||
'$( `web-service-compression-${name}` ).disabled = disabled',
|
||
'$("web-service-compression").addEventListener("change", syncCompression)',
|
||
]:
|
||
assert marker in web_service_ui_0270, marker
|
||
settings_source_0270 = rust_source(root, "src/settings.rs")
|
||
config_model_0270 = rust_source(root, "src/config/model.rs")
|
||
for marker in ['mode: ServerMode::Custom', 'value: "RustProxy".into()', 'legacy_compat_default']:
|
||
assert marker in settings_source_0270, marker
|
||
assert 'default = "legacy_server_header_default"' in config_model_0270
|
||
headers_source_0267 = rust_source(root, "src/headers.rs")
|
||
vhost_core_0267 = (root / "src/admin/web/vhosts/core.js").read_text()
|
||
for marker in [
|
||
'pub fn apply_wire_age',
|
||
'wire_only: bool',
|
||
'wire_only: direction == Direction::Response',
|
||
'rule.name.eq_ignore_ascii_case("age")',
|
||
'if !rule.wire_only',
|
||
]:
|
||
assert marker in headers_source_0267, marker
|
||
assert '["host", "connection", "content-length"' in vhost_core_0267, "UI protected header set must stay intact"
|
||
assert '["host", "age", "connection"' not in vhost_core_0267, "0.26.6 Age rules must remain UI-loadable"
|
||
assert 'apply_response_age_rules(' in compression_finalize_source, "Age wire rules must run in final response stage"
|
||
compression_tester_0267 = (root / "tools/dev/test_compression.py").read_text()
|
||
for marker in ["--revalidate", "if revalidate:", 'headers["Cache-Control"] = "no-cache"', "headers.get('Age', '-')"]:
|
||
assert marker in compression_tester_0267, marker
|
||
assert "cache_hit_and_dashboard_age_advance_from_storage_clock" in read_cache_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_source
|
||
assert "sparkline-h1" in html and "sparkline-h2" in html and "sparkline-h3" in html
|
||
assert 'id="open-proxy"' not in html and '$("open-proxy")' not in app_js
|
||
assert "protocolSamples" in app_js and "m.http1_requests" in app_js
|
||
assert "renderNetworkInterfaceCharts" in app_js and "networkInterfaceSamples" in app_js
|
||
assert 'id="network-interface-charts"' in html
|
||
assert 'compressionCountersHelp' not in html
|
||
assert "dockerOpenApp" in docker_ui and "proxiedUrl(agent, hostname)" in docker_ui
|
||
# 0.19.0 Docker-vhost controls: chip aliases, default agent ACL, redirect/WebSockets/cache.
|
||
docker_source = rust_source(root, "src/docker.rs")
|
||
management_ui = (root / "src/admin/web/management.js").read_text()
|
||
for marker in ["function aliasEditor(", "host-chip-editor docker-alias-editor", ".docker-force-ssl", ".docker-websockets", ".docker-cache-enabled", ".docker-compression-enabled", ".docker-agent-acl-profile"]:
|
||
assert marker in docker_ui, marker
|
||
for marker in ['"force_ssl": assignment.force_ssl.unwrap_or(false)', '"websockets": assignment.websockets', '"enabled": assignment.compression_enabled', '"cache": {"enabled": assignment.cache_enabled}', '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
|
||
for marker in ["published_assignment_stays_routable_as_503_when_service_is_missing", "failed_agent_observation_with_last_inventory_keeps_backend_materialized", "disabling_agent_stops_discovery_but_keeps_published_hostname_as_503"]:
|
||
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
|
||
assert 'payload["backend_pool_domains"]' in admin_source and "status.backend_pool_domains" in vhosts_js
|
||
assert 'body[data-density="compact"]' in style_source and '--ui-control: 22px' in style_source
|
||
assert 'rust_proxy_http1_requests_total' in expressions
|
||
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_js
|
||
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 '<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 'viewBox: "0 0 300 60"' in app_js and 'M0 15H300 M0 30H300 M0 45H300' in app_js
|
||
assert '.network-interface-row {' in style_source and 'background: transparent' in style_source
|
||
assert 'function checkboxField(key, control)' in docker_ui and 'checkboxField("dockerPreserveHost", preserve)' in docker_ui and 'checkboxField("dockerAclInline", inline)' in docker_ui
|
||
assert 'label = el("label", undefined, "check vh-check")' in docker_ui and 'check compact-check' not in docker_ui.split('function checkboxField(key, control)', 1)[1].split('function aliasEditor(', 1)[0]
|
||
assert '.docker-route-grid input:not([type="checkbox"])' in style_source and '.docker-checkbox-field .vh-check {' in style_source
|
||
assert 'docker-switch-row' in docker_ui and '.docker-switch-row {' in style_source and 'repeat(5, minmax(110px, 160px))' in style_source
|
||
assert '.docker-passive-health-editor .docker-health-grid {' in style_source
|
||
assert '.docker-health-grid input:not([type="checkbox"])' in style_source and '.docker-passive-health-editor .docker-checkbox-field input[type="checkbox"]' in style_source
|
||
assert 'id="sparkline-transform"' in html and 'id="rate-transform"' in html and 'liveTransformed' in html
|
||
assert 'payload["transform_metrics"] = json!(state.transform_metrics.total())' in admin_source
|
||
for marker in ['payload["transform_resources"]', 'payload["memory_accounting"]', 'non_cache_rss_includes_transform']:
|
||
assert marker in admin_source, 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_js, 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 'id="panel-transform"' in html and 'data-panel="transform"' in html and 'include_str!("web/transform-panel.js")' in (root / 'src/admin/router.rs').read_text()
|
||
assert '/api/transform' in transform_panel_js and 'tp-cache-hits' in transform_panel_js and 'transform-reason-list' in transform_panel_js
|
||
assert '--transform-chart:' in css and '--chart-quaternary:' in css and 'stroke: var(--chart-quaternary)' in css and 'background: var(--transform-chart)' in css
|
||
assert '.cache-transform-badge {' in css and 'border: 1px solid var(--pill-line)' in css and 'border-radius: 3px' in css
|
||
assert 'pub transformed: bool' in cache_source and 'annotate_transformed' in cache_source and 'format!("{base}; transformed")' in cache_source
|
||
assert 'x-cache-status' in cache_source and 'Cache-Status (RFC 9211)' in html
|
||
assert 'generated_vhost_inherits_global_server_header_policy' in docker_source and 'effective.vhosts[0].server_header.is_none()' in docker_source
|
||
assert '.insert("x-rustproxy-backend-time", value)' in proxy_source and '.insert("x-rust-proxy-backend-time", value)' not in proxy_source
|
||
assert 'x-rustproxy-backend-time' in rust_source(root, "src/headers.rs")
|
||
# Expanded upstream connection diagnostics must retain nested Hyper/rustls/io causes.
|
||
for marker in [
|
||
"fn format_error_chain(",
|
||
"MAX_UPSTREAM_ERROR_DETAIL: usize = 2048",
|
||
"error_chain={chain}",
|
||
"request_origin={}",
|
||
"tls_verify={}",
|
||
"upstream_error_chain_keeps_nested_connector_cause",
|
||
"tracing::warn!(",
|
||
]:
|
||
assert marker in proxy_source, marker
|
||
assert "pub tls_verify: bool" in config_source and "tls_verify: backend.tls_verify" in config_source
|
||
assert 'data-i18n="logUpstreamError"' in html and 'e.upstream_error || "-"' in management_ui
|
||
assert '.route("/api/preferences", get(preferences).put(update_preferences))' in admin
|
||
assert 'admin_preferences' in (root / "migrations/0004_admin_preferences.sql").read_text()
|
||
assert 'id="log-column-options"' in html and 'data-log-column="upstream_error"' in html
|
||
assert 'check log-column-option' in management_ui
|
||
assert 'input[type="checkbox"],\ninput[type="radio"] {' in style_source and 'width: 15px;' in style_source and '.log-column-option input[type="checkbox"]' not in style_source
|
||
assert '.log-tools input:not([type="checkbox"]):not([type="radio"])' in style_source
|
||
assert '.log-tools input {\n' not in style_source, "Live Logs text-input rules must not match checkbox/radio controls"
|
||
footer_block = app_js.split('function renderFooter(s) {', 1)[1].split('let lastLogs', 1)[0]
|
||
assert 'SQLite / HTTP/1.1' not in footer_block and 'coldQ:' not in footer_block and 'streamFallback' not in footer_block
|
||
assert '`${apiLatency} / ${t("logsDropped")}: ${s.logs_dropped}`' in footer_block
|
||
assert 'update?.update_available && update.latest_version' in footer_block and 'footer-update' in footer_block
|
||
assert '.page-footer .footer-update {' in style_source
|
||
assert '.logs-dialog {' in style_source and 'overflow: visible' in style_source
|
||
assert '/api/preferences' in app_js and 'live_log_columns' in management_ui and 'ADMIN_THEMES = new Set(["dark", "light"])' in app_js
|
||
assert 'id="ui-theme"' in html and 'data-i18n="themeLight"' in html
|
||
assert 'html[data-theme="light"] {' in style_source and '--field-bg:' in style_source and '--sidebar-bg:' in style_source
|
||
assert 'overflow: visible;' in style_source and 'max-height: min(38dvh, 220px)' not in style_source
|
||
assert 'body[data-density="compact"] input:not([type="checkbox"]):not([type="radio"])' in style_source
|
||
assert 'input:not([type="checkbox"]):not([type="radio"]),\nselect,\ntextarea {' in style_source
|
||
assert '#[serde(default = "default_admin_theme")]' in admin_source and 'matches!(preferences.theme.as_str(), "dark" | "light")' in admin_source
|
||
# 0.22.3 dashboard/config/cache/log-history regressions.
|
||
assert 'bindSettingsForm("pages-form", next => { next.pages = global.read(); }, "pages")' in management_ui
|
||
assert '"pages" => {' in admin_source and 'config.pages =' in admin_source
|
||
assert 'default_pages_section_accepts_the_standard_value_payload' in admin_source
|
||
logging_source = rust_source(root, "src/logging.rs")
|
||
assert 'pub const LIVE_WINDOW: usize = 256;' in logging_source
|
||
assert 'pub const HISTORY_CAPACITY: usize = 4096;' in logging_source
|
||
assert 'retained_history_is_larger_than_the_live_snapshot_but_still_bounded' in logging_source
|
||
assert 'id="logs-load-history"' in html and 'api("/api/logs?limit=4096")' in management_ui
|
||
assert 'const STATUS_NAMES = { 200: "OK"' in management_ui and '404: "Not Found"' in management_ui and 'statusLabel(code)' in management_ui
|
||
assert 'let paused = null, expanded = null' in management_ui and 'function mergeExpanded(data)' in management_ui and 'entries.length >= 4096' in management_ui
|
||
assert 'dialog.addEventListener("close", () => { if (pinning) return; expanded = null; paused = null;' in management_ui
|
||
cache_source = read_cache_source()
|
||
assert 'hash_scheme.as_bytes()' in cache_source and 'cache_key_separates_frontend_scheme' in cache_source
|
||
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
|
||
cache_panel_js = (root / "src/admin/web/cache-panel.js").read_text()
|
||
traffic_panel_js = (root / "src/admin/web/traffic-panel.js").read_text()
|
||
assert 'id="panel-traffic"' in html and 'data-panel="traffic"' in html
|
||
for marker in ['/api/traffic', 'trafficTopCountries', 'trafficTopUserAgents', 'trafficTopClientIps', 'traffic-donut', 'rustProxyTrafficPanel']:
|
||
assert marker in traffic_panel_js or marker in html, marker
|
||
traffic_source = rust_source(root, "src/traffic.rs")
|
||
for marker in ['QUEUE_CAPACITY: usize = 2048', 'TOP_CAPACITY: usize = 32', 'MAX_VHOSTS: usize = 64', 'self.tx.try_send(sample)', 'client_ips: TopTable']:
|
||
assert marker in traffic_source, marker
|
||
forwarding_source = rust_source(root, "src/forwarding.rs")
|
||
for marker in ['pub fn cloudflare_country', 'headers.get_all("cf-ipcountry")', 'country != "XX"']:
|
||
assert marker in forwarding_source, marker
|
||
forward_entry_source = rust_source(root, "src/proxy/forward_entry.rs")
|
||
for marker in ['cloudflare_country = crate::forwarding::cloudflare_country', '.or_else(|| state.geoip.load().country(context.client_ip))']:
|
||
assert marker in forward_entry_source, marker
|
||
assert forward_entry_source.count('crate::acl::allows_with_country(') >= 4, "profile/vhost/location access scopes must use composed IP+country evaluation"
|
||
route_cache_source = rust_source(root, "src/proxy/forward/route_cache.rs")
|
||
assert 'Do not re-evaluate IP ACLs in isolation here' in route_cache_source
|
||
assert 'if (envelope) render();' in (root / "src/admin/web/web-server.js").read_text()
|
||
assert 'pub capture_user_agent: bool' in logging_source and 'pub capture_country: bool' in logging_source
|
||
assert 'spawn_blocking(crate::diagnostics::trim_allocator)' in rust_source(root, 'src/runtime.rs')
|
||
assert 'fn trim_hot_idle(&self)' in cache_source and 'hot_auto_trim_target_percent' in cache_source
|
||
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
|
||
assert 'groups = d.domains || [], shownGroups = visibleDomains(data, groups)' in cache_panel_js
|
||
assert 'replaceChildren(...shownGroups.map(row =>' in cache_panel_js
|
||
balance_source = rust_source(root, 'src/balance.rs')
|
||
assert 'traffic_rate_drops_to_zero_after_an_idle_sample_window' in balance_source and 'if delta == 0 {' in balance_source and 'sample.rate = 0.0;' in balance_source
|
||
all_admin_js = "\n".join(path.read_text() for path in (root / "src/admin/web").glob("*.js"))
|
||
assert not re.search(r'(?<![A-Za-z])(?:window\.)?(?:alert|confirm)\s*\(', all_admin_js), "native browser alert/confirm returned"
|
||
assert 'function appConfirm(message, options = {})' in app_js and 'function apiErrorMessage(path, response, value)' in app_js
|
||
assert '.app-confirm-dialog {' in style_source and '.login-section #auth-message.error {' in style_source
|
||
assert 'classList.toggle("error", Boolean(value))' in login_js
|
||
assert 'class="auth-page auth-loading"' in (root / 'src/admin/web/login.html').read_text() and 'const reveal = () => document.body.classList.remove("auth-loading")' in login_js
|
||
assert '"/docker-vhosts"' in login_js and 'settingsTabs = new Set' in login_js and '`/settings?tab=${encodeURIComponent(tab)}`' in login_js
|
||
assert 'class="app-loading"' in html and 'document.documentElement.classList.remove("app-loading")' in app_js
|
||
assert 'id="mobile-menu-toggle"' in html and 'id="main-navigation"' in html and 'aria-controls="main-navigation"' in html
|
||
assert '.sidebar.mobile-nav-open .main-navigation' in style_source and 'display: none;' in style_source and 'grid-template-columns: repeat(2, minmax(0, 1fr))' in style_source
|
||
assert 'function setMobileMenu(open)' in app_js and 'setPanel(button.dataset.panel); setMobileMenu(false);' in app_js and 'event.key === "Escape"' in app_js
|
||
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_js and '$("about-close").addEventListener("click"' in app_js
|
||
assert '.about-dialog {' in style_source and '.about-meta {' in style_source and '.top-actions {\n width: 100%;\n flex-wrap: wrap;' in style_source
|
||
assert 'function withFormSubmitState(form, submitter, task)' in app_js and 'button.textContent = t("saving")' in app_js
|
||
assert 'function fieldError(target, message)' in app_js and 'function showFieldError(error, form = null)' in app_js
|
||
assert 'function authReturnDestination()' in app_js and 'pendingAuthDestination || authReturnDestination()' in app_js
|
||
assert '$("login").hidden = navigating' in app_js and 'id="auth-message"' in html
|
||
assert 'settings-tabs button.has-unsaved::after' in style_source and 'function syncSettingsDirtyIndicators()' in app_js
|
||
assert 'setSettingsDirty("config-editor")' in app_js and '!settingsDirty.has("config-editor")' in app_js
|
||
assert 'throw fieldError($("tls-policy-12")' in settings_js
|
||
integrations_ui_0194 = (root / 'src/admin/web/integrations.js').read_text()
|
||
docker_ui_0194 = (root / 'src/admin/web/docker.js').read_text()
|
||
assert 'readAddresses.input' in integrations_ui_0194
|
||
assert 'form.reset(); window.rustProxyCertificates?.close();' in integrations_ui_0194
|
||
assert '$("cert-import-pem").value = ""' not in integrations_ui_0194 and '$("cert-import-key").value = ""' not in integrations_ui_0194
|
||
assert 'throw fieldError(row.querySelector(".docker-agent-listener-choices")' in docker_ui_0194
|
||
assert '#docker-agents-form>.form-actions {' in style_source and 'justify-content: flex-start' in style_source
|
||
deploy_source = read_deploy_source()
|
||
assert "SHELL_PATH_PROFILE = Path('/etc/profile.d/rustproxy-path.sh')" in deploy_source
|
||
assert 'install_shell_path(prefix)' in deploy_source and 'check_shell_path(prefix)' in deploy_source and 'install_logrotate(' in deploy_source and "'shell_path_action':'install-or-repair'" in deploy_source
|
||
assert 'grid-template-columns: minmax(0, 1fr)' in style_source and '.log-column-option:hover' in style_source
|
||
assert '.check>span {' in style_source and 'transform: translateY(-1px)' in style_source and '.cache-toolbar-status .check' in style_source and '.docker-toolbar .check' in style_source and 'grid-column: span 3' in style_source
|
||
assert '.docker-alias-field>span:first-child {' in style_source and 'margin-bottom: 0' in style_source and '.docker-alias-editor {' in style_source and 'min-height: 38px' in style_source and 'height: 30px' in style_source
|
||
assert 'field.append(box, el("small", t("dockerAliasesHelp")' not in docker_ui
|
||
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_source and 'LocalGeneratedResponse' in proxy_source and 'get::<crate::pages::PageServed>()' in proxy_source and '.is_none()' in proxy_source
|
||
assert 'locally_generated_pages_remove_cache_metadata' in proxy_source and '"x-cache-reason"' in proxy_source and '"cache-status"' in proxy_source
|
||
assert 'local_page_method_rejection_is_still_marked_non_cacheable' in pages_rust and 'response.extensions_mut().insert(PageServed);' in pages_rust
|
||
assert "rel.as_posix() == 'docker-agent/docker-agent'" in (root / 'make_zip.py').read_text()
|
||
screenshots = {path.name for path in (root / "docs/screenshots").iterdir() if path.is_file()}
|
||
assert "README.md" in screenshots, screenshots
|
||
assert screenshots <= {"README.md", "login.png", "location-headers.png"}, screenshots
|
||
assert 'Historical or version-labelled context' in (root / "docs/README.md").read_text()
|
||
assert '<span class="version">' not in html, "duplicate top-right version label returned"
|
||
assert '<div class="edition">INDEPENDENT BY DESIGN <span>' not in html, "release version returned beside the dashboard brand"
|
||
assert '<div class="edition">INDEPENDENT BY DESIGN</div>' in html
|
||
# tls_verify=false must not feed legacy appliance certificates back through webpki.
|
||
upstream_source = rust_source(root, "src/upstream.rs")
|
||
assert "BackendCertificateVerificationDisabled" in upstream_source and "supported_schemes" in upstream_source
|
||
disabled_verifier = upstream_source.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 '.docker-route-grid {' in style_source and 'align-items: start' in style_source and '.docker-acl-grid {' in style_source
|
||
|
||
# 0.22.3 cache-modal, RFC8441/h2c and active-tunnel contracts.
|
||
server_source = rust_source(root, "src/server.rs")
|
||
websocket_source = rust_source(root, "src/proxy/websocket.rs")
|
||
vhost_source = rust_source(root, "src/vhost.rs")
|
||
integrations_0221 = (root / "src/admin/web/integrations.js").read_text()
|
||
http2_test = (root / "tests/http2.rs").read_text()
|
||
transport_test = (root / "tests/transport_config.rs").read_text()
|
||
for marker_0221 in [
|
||
'id="cache-invalidation-open"', 'id="cache-bans-open"',
|
||
'id="cache-invalidation-dialog"', 'id="cache-bans-dialog"',
|
||
'id="cache-toolbar-operation-status"', 'id="tunnels-dialog"',
|
||
'id="tunnel-kill-reason"', 'id="metric-ws-h1"', 'id="metric-ws-h2"',
|
||
'id="metric-ws-h3"', 'id="metric-ws-total"',
|
||
]:
|
||
assert marker_0221 in html, marker_0221
|
||
for marker_0221 in [
|
||
'const operationMessage = (id, value = "", error = false) => {',
|
||
'if (!box) return;', 'cache-toolbar-operation-status',
|
||
'runInvalidation(payload, messageTarget = "cache-invalidation-status")',
|
||
'async function copyText(value)', 'function makeCopyCell(value)',
|
||
'target = makeCopyCell(targetValue)', 'makeCopyCell(keyTags)',
|
||
]:
|
||
assert marker_0221 in cache_panel_js, marker_0221
|
||
assert html.count('class="stats-grid"') >= 1
|
||
assert 'stats-grid transport-stats' not in html and 'transport-websocket' not in html
|
||
assert 'transport-action' not in html and '.transport-grid .transport-action' not in style_source
|
||
assert '#panel-cache .cache-copy-cell' in style_source and 'cursor: copy' in style_source
|
||
assert '.operation-message' in style_source and '.operation-message.error' in style_source
|
||
for marker_0221 in [
|
||
'api("/api/tunnels", context?.signal ? { signal: context.signal } : {})', 'tunnel-kill-reason', 'tunnelKillReasonDefault',
|
||
'api(`/api/tunnels/${encodeURIComponent(item.id)}/kill`', 'startTunnelRefresh()',
|
||
]:
|
||
assert marker_0221 in app_js, marker_0221
|
||
assert 'pub websocket_rfc8441: bool' in vhost_source
|
||
assert 'pub h2c: bool' in vhost_source
|
||
assert 'h2c is available only on cleartext listeners' in vhost_source
|
||
for marker_0221 in [
|
||
'websocket_rfc8441_on(&self, local: SocketAddr, tls: bool)',
|
||
'binding.h2c', 'HTTP/2-capable listener',
|
||
'websocket_rfc8441: host.websocket_rfc8441',
|
||
]:
|
||
assert marker_0221 in config_source, marker_0221
|
||
for marker_0221 in [
|
||
'HTTP2_PRIOR_KNOWLEDGE_PREFACE', 'h2_prior_knowledge_prefix',
|
||
'builder.enable_connect_protocol()', 'websocket_rfc8441_on(local, false)',
|
||
'websocket_rfc8441_on(local, true)',
|
||
]:
|
||
assert marker_0221 in server_source, marker_0221
|
||
for marker_0223 in [
|
||
'StatusCode::SWITCHING_PROTOCOLS', 'H2cUpgradeIo', 'HTTP2-Settings',
|
||
'h2c_upgrade_bootstraps_request_as_http2_stream_one',
|
||
'malformed_h2c_upgrade_is_rejected_before_switching_protocols',
|
||
]:
|
||
assert marker_0223 in server_source, marker_0223
|
||
for memory_marker_0230 in [
|
||
'H2ConnectionState', 'H2ActivityIo', 'ActiveHttp2Connection',
|
||
'http2_active_connections',
|
||
]:
|
||
assert memory_marker_0230 in server_source or memory_marker_0230 in metrics_source, memory_marker_0230
|
||
cache_fill_0230 = rust_source(root, 'src/cache/fill.rs')
|
||
assert 'let body_allocation = self.data.capacity().max(self.data.len());' in cache_fill_0230
|
||
assert 'let cost = hot_entry_cost(' in cache_fill_0230
|
||
assert 'fn hot_entry_cost(' in cache_source
|
||
for marker_0221 in [
|
||
'pub fn validate_h2(request: &Request)', 'hyper::ext::Protocol',
|
||
'bridge_h2_to_h1', 'pub struct TunnelInfo', 'pub struct TunnelRegistry',
|
||
'register_pending', 'spawn_h2_idle_watch', 'h2-rfc8441-h1-upstream',
|
||
'h2-rfc8441-h2-upstream', 'h3-rfc9220',
|
||
]:
|
||
assert marker_0221 in websocket_source, marker_0221
|
||
for marker_0221 in [
|
||
'websocket_upstream_h2', 'validate_h2_response', 'wrap_h2_request_body',
|
||
'wrap_h2_response_body', 'bridge_h2_to_h1',
|
||
]:
|
||
assert marker_0221 in proxy_source, marker_0221
|
||
for marker_0221 in [
|
||
'.route("/api/tunnels", get(active_tunnels))',
|
||
'"/api/tunnels/{id}/kill"', '"tunnel.kill"',
|
||
'reason.len() > 256', 'state.tunnel_registry.kill(id)',
|
||
]:
|
||
assert marker_0221 in admin, marker_0221
|
||
for marker_0221 in [
|
||
'websocket_h1_tunnels_total', 'websocket_h1_tunnels_active',
|
||
'websocket_h2_rfc8441_tunnels_total', 'websocket_h2_rfc8441_tunnels_active',
|
||
'websocket_h3_rfc9220_tunnels_total', 'websocket_h3_rfc9220_tunnels_active',
|
||
]:
|
||
assert marker_0221 in metrics_source, marker_0221
|
||
for marker_0221 in [
|
||
'"websocket_rfc8441"', '"/api/tunnels"', '"/api/tunnels/{id}/kill"',
|
||
'"TunnelInfo"', '"TunnelKillRequest"',
|
||
]:
|
||
assert marker_0221 in openapi, marker_0221
|
||
for marker_0233 in [
|
||
'pub websocket_legacy_support: bool',
|
||
'legacy WebSocket compatibility requires WebSocket support to be enabled',
|
||
]:
|
||
assert marker_0233 in vhost_source, marker_0233
|
||
for marker_0233 in [
|
||
'pub websocket_legacy_support: bool',
|
||
'websocket_legacy_support: host.websocket_legacy_support',
|
||
]:
|
||
assert marker_0233 in config_source, marker_0233
|
||
for marker_0233 in [
|
||
'fn legacy_websocket_origin(',
|
||
'websocket_legacy_support && !websocket_upstream_h2',
|
||
'request.headers_mut().insert("origin", origin)',
|
||
'websocket::validate_response(',
|
||
]:
|
||
assert marker_0233 in proxy_source, marker_0233
|
||
for marker_0233 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_0233 in websocket_source, marker_0233
|
||
for marker_0233 in [
|
||
'websocket_legacy_support', 'vhWebsocketLegacySupport', 'vhWebsocketLegacySupportHelp',
|
||
]:
|
||
assert marker_0233 in js or marker_0233 in openapi, marker_0233
|
||
assert 'hyper::ext::Protocol::from_static(protocol)' in http2_test
|
||
assert 'rfc8441_requires_an_h2_capable_listener_and_h2c_is_explicit' in transport_test
|
||
assert 'h2c' in integrations_0221 and 'H2C' in integrations_0221
|
||
transport_doc = (root / "docs/TRANSPORT.md").read_text()
|
||
assert 'prior knowledge' in transport_doc and 'Upgrade: h2c' in transport_doc and 'stream 1' in transport_doc
|
||
assert (root / "docs/V0.24.1_AUDIT.md").is_file() and (root / "docs/V0.24.0_AUDIT.md").is_file() and (root / "docs/V0.23.2_AUDIT.md").is_file() and (root / "docs/V0.23.1_AUDIT.md").is_file() and (root / "docs/V0.23.0_AUDIT.md").is_file() and (root / "docs/V0.22.3_AUDIT.md").is_file()
|
||
|
||
# 0.22.3 replay-safe retry and ordinary H2 upstream contracts.
|
||
balance_0222 = rust_source(root, "src/balance.rs")
|
||
for marker_0222 in [
|
||
"pub struct RetryPolicy", "pub max_retries: u8", "pub retry_statuses: Vec<u16>",
|
||
"pub fn select_retry", "retry_attempts_total", "retry_successes_total",
|
||
"failover_selection_never_reuses_an_attempted_backend",
|
||
]:
|
||
assert marker_0222 in balance_0222, marker_0222
|
||
for marker_0222 in [
|
||
"fn retry_safe_method", "Method::GET", "Method::HEAD", "Method::OPTIONS",
|
||
"request_body_empty", "route.backend_id.is_none()", "select_retry_wait(",
|
||
"upstream_request_version", "retrying replay-safe request on another backend",
|
||
"explicit_http2_backend_uses_http2_request_version",
|
||
]:
|
||
assert marker_0222 in proxy_source, marker_0222
|
||
upstream_0222 = rust_source(root, "src/upstream.rs")
|
||
assert "explicit_h2c_backend_uses_http2_prior_knowledge" in upstream_0222
|
||
vhosts_0222 = read_vhosts_js(root)
|
||
for marker_0222 in ["retry-max", "retry-connect", "retry-timeouts", "retry-statuses"]:
|
||
assert marker_0222 in vhosts_0222, marker_0222
|
||
for marker_0222 in [
|
||
'"max_retries"', '"retry_connect_errors"', '"retry_timeouts"', '"retry_statuses"'
|
||
]:
|
||
assert marker_0222 in openapi, marker_0222
|
||
for marker_0222 in [
|
||
"max_concurrent_requests", "queue_timeout_ms", "capacity_rejections_total",
|
||
"dns_errors_total", "last_failure_detail", "last_recovered_ms",
|
||
"backend_concurrency_limit_queues_then_rejects_and_recovers",
|
||
]:
|
||
assert marker_0222 in balance_0222, marker_0222
|
||
assert "let mut item = serde_json::Map::new();" in balance_0222
|
||
assert 'json!({"id":m.definition.id, "url":m.definition.url' not in balance_0222
|
||
for marker_0222 in ["member-max-concurrent", "member-queue-timeout", "vhLastFailure", "vhLastRecovery", "vhClientGeneration", "vhGenerationRequests", "vhGenerationAge", "vhClientRotations"]:
|
||
assert marker_0222 in vhosts_0222 or marker_0222 in html, marker_0222
|
||
for marker_0256 in ["upstream_client_generation", "upstream_client_generation_requests", "upstream_client_generation_age_seconds", "upstream_client_rotations_total"]:
|
||
assert marker_0256 in vhosts_0222, marker_0256
|
||
for marker_0222 in ["CertificateMetadata", "certificate_public_metadata", "x509_time_parser_handles_utc_and_generalized_time_with_offsets"]:
|
||
assert marker_0222 in rust_source(root, "src/tls.rs"), marker_0222
|
||
for marker_0222 in [
|
||
'"max_concurrent_requests"', '"queue_timeout_ms"', '"certificate_issuer"',
|
||
'"days_remaining"', '"expiry_status"', '"last_renewal_error"'
|
||
]:
|
||
assert marker_0222 in openapi, marker_0222
|
||
|
||
for path in (root / "src/admin/web").glob("*.js"):
|
||
subprocess.run(["node", "--check", str(path)], check=True)
|
||
connection = sqlite3.connect(":memory:")
|
||
connection.executescript((root / "migrations/0002_admin.sql").read_text())
|
||
connection.executescript((root / "migrations/0003_certificate_renewals.sql").read_text())
|
||
connection.executescript((root / "migrations/0004_admin_preferences.sql").read_text())
|
||
connection.executescript((root / "migrations/0005_certificate_api.sql").read_text())
|
||
connection.executescript((root / "migrations/0006_certificate_api_credentials.sql").read_text())
|
||
connection.executescript((root / "migrations/0007_secret_store.sql").read_text())
|
||
connection.executescript((root / "migrations/0008_config_history.sql").read_text())
|
||
connection.executescript((root / "migrations/0009_certificate_api_grants.sql").read_text())
|
||
connection.execute("PRAGMA user_version = 9")
|
||
assert connection.execute("PRAGMA quick_check").fetchone()[0] == "ok"
|
||
assert connection.execute("SELECT COUNT(*) FROM auth_throttle WHERE id=1").fetchone()[0] == 1
|
||
assert connection.execute("SELECT COUNT(*) FROM certificate_renewals").fetchone()[0] == 0
|
||
assert connection.execute("SELECT COUNT(*) FROM admin_preferences").fetchone()[0] == 0
|
||
assert connection.execute("PRAGMA user_version").fetchone()[0] == 9
|
||
connection.close()
|
||
for marker_0223 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_0223 in admin, marker_0223
|
||
runtime_0223 = rust_source(root, 'src/runtime.rs')
|
||
for marker_0223 in ['pub async fn reload_certificates', 'configured certificates reloaded from disk']:
|
||
assert marker_0223 in runtime_0223, marker_0223
|
||
main_0223 = rust_source(root, 'src/main.rs')
|
||
for marker_0223 in ['TlsCommand::Deploy', '/api/certificates/deploy', '/api/certificates/reload', 'read_external_pem']:
|
||
assert marker_0223 in main_0223, marker_0223
|
||
tls_0223 = rust_source(root, 'src/tls.rs')
|
||
for marker_0223 in ['pub fn stage_external_pair', 'pub fn reconcile_external_pair', 'ExternalPairDeployment::Unchanged', 'external-', 'remove_external_pair']:
|
||
assert marker_0223 in tls_0223, marker_0223
|
||
for marker_0223 in ['"/api/certificates/reload"', '"CertificateReloadResult"', '"/api/certificates/deploy"', '"CertificateDeploy"', '"CertificateDeployResult"']:
|
||
assert marker_0223 in openapi, marker_0223
|
||
assert 'api("/api/certificates/reload"' in app_js
|
||
assert 'certificateAdminRestartRequired' in app_js
|
||
|
||
# 0.24.0 H2 lifecycle contracts retained by 0.24.1.
|
||
server_0240 = rust_source(root, "src/server.rs")
|
||
settings_0240 = rust_source(root, "src/settings.rs")
|
||
config_0240 = rust_source(root, "src/config.rs")
|
||
for marker_0240 in [
|
||
"H2ConnectionState", "H2StreamLease", "H2TrackedBody",
|
||
"wait_for_h2_idle", "graceful_shutdown()", "H2ShutdownCause::Lifetime",
|
||
"h2_idle_connection_sends_goaway_before_closing",
|
||
"h2_lifetime_goaway_is_bounded_by_drain_timeout",
|
||
"h2_lifetime_allows_active_stream_to_finish_during_drain",
|
||
]:
|
||
assert marker_0240 in server_0240, marker_0240
|
||
for marker_0240 in ["Http2Connections", "idle_timeout_seconds", "max_connection_lifetime_seconds", "drain_timeout_seconds"]:
|
||
assert marker_0240 in settings_0240, marker_0240
|
||
assert "pub http2: crate::settings::Http2Connections" in config_0240
|
||
assert "pub http2_settings: Option<crate::settings::Http2Connections>" in rust_source(root, "src/vhost.rs")
|
||
assert "serve_frontend_with_http2" in main_source and "binding.http2_settings.unwrap_or_default()" in main_source
|
||
assert 'renderHttp2Settings' in integrations_js and 'collectHttp2Listeners' in integrations_js
|
||
assert '"Http2ConnectionSettings"' in openapi and '"max_connection_lifetime_seconds"' in openapi
|
||
|
||
# 0.24.1 shared statistics/runtime-info/settings-navigation contracts.
|
||
system_stats_0241 = rust_source(root, "src/system_stats/mod.rs")
|
||
linux_stats_0241 = rust_source(root, "src/system_stats/linux.rs")
|
||
main_0241 = rust_source(root, "src/main.rs")
|
||
admin_0241 = rust_source(root, "src/admin/mod.rs")
|
||
settings_js_0241 = (root / "src/admin/web/settings.js").read_text()
|
||
assert not (root / "src/system_stats/bsd.rs").exists(), "per-BSD parser backend must not return"
|
||
assert manifest["target"]['cfg(any(target_os = "linux", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "macos", target_os = "windows"))']["dependencies"]["systemstat"] == "=0.2.7"
|
||
metrics_dep = manifest["dependencies"]["metrics-process"]
|
||
assert metrics_dep["version"] == "=2.4.3" and metrics_dep["default-features"] is False
|
||
assert metrics_dep["features"] == ["dummy"]
|
||
sysinfo_dep = manifest["dependencies"]["sysinfo"]
|
||
assert sysinfo_dep["version"] == "0.39" and sysinfo_dep["default-features"] is False
|
||
assert set(sysinfo_dep["features"]) == {"network", "system"}
|
||
for marker_0241 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", "FreeBSD", "OpenBSD", "NetBSD", "DragonFly BSD",
|
||
]:
|
||
assert marker_0241 in system_stats_0241, marker_0241
|
||
for marker_0241 in ["/proc/stat", "/proc/meminfo", "RssAnon", "VmData", "VmSwap", "pub(crate) fn augment"]:
|
||
assert marker_0241 in linux_stats_0241, marker_0241
|
||
for marker_0266_lxc 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_0266_lxc in linux_stats_0241, marker_0266_lxc
|
||
assert 'const processPlatform = [d.platform, d.environment].filter(Boolean).join(" / ");' in app_js
|
||
for forbidden in ["sysctl", "netstat", "Command::new"]:
|
||
assert forbidden not in system_stats_0241, f"shared statistics adapter must not shell/parse per platform: {forbidden}"
|
||
for marker_0241 in ["installed_data_dir_candidates", '"/var/lib/rustproxy"', '"/var/db/rustproxy"', "resolve_installed_data_dir"]:
|
||
assert marker_0241 in main_0241, marker_0241
|
||
for marker_0241 in ['"runtime"', '"platform"', '"statistics_source"', '"data_directory"', '"database_path"']:
|
||
assert marker_0241 in admin_0241 or marker_0241 in openapi, marker_0241
|
||
assert 'id="process-platform"' in html and 'd.platform' in app_js
|
||
assert 'id="panel-logging"' not in html
|
||
assert 'id="settings-tab-logging"' in html and 'id="settings-page-logging"' in html and 'id="logging-form"' in html and 'id="error-log-form"' in html
|
||
assert 'data-i18n="errorLogHelp"' not in html
|
||
assert 'bindSettingsForm("error-log-form"' in app_js and '}, "logging", selectLoggingSection);' in app_js
|
||
assert 'const selectLoggingSection = next => ({ logging: next.logging, error_log: next.error_log });' in app_js
|
||
assert '"error_log" => {' not in admin_0241 and 'request("/api/config/error_log")' in admin_0241
|
||
assert '"error_log"' in openapi and 'logging, error_log, forwarding' not in openapi
|
||
assert 'logging_section_persists_error_log_settings_atomically' in admin_0241 and 'error_log_is_not_a_standalone_configuration_section' in admin_0241
|
||
assert 'location.pathname === "/logging"' in settings_js_0241
|
||
assert '"RuntimeInformation"' in openapi and '"version": "0.31.2"' in openapi
|
||
|
||
# 0.24.1 compiler and hostname-cardinality regressions.
|
||
tls_0241 = rust_source(root, "src/tls.rs")
|
||
vhost_0241 = rust_source(root, "src/vhost.rs")
|
||
redirect_0241 = rust_source(root, "src/redirect.rs")
|
||
not_found_0241 = rust_source(root, "src/not_found.rs")
|
||
docker_0241 = rust_source(root, "src/docker.rs")
|
||
integrations_0241 = (root / "src/admin/web/integrations.js").read_text()
|
||
docker_js_0241 = (root / "src/admin/web/docker.js").read_text()
|
||
assert "future::Future" not in server_0240
|
||
assert "uptime.as_secs()" in system_stats_0241 and "whole_seconds()" not in system_stats_0241
|
||
for forbidden_0241 in [
|
||
"cert.hosts.len() <= 32", "settings.certificates.len() <= 64",
|
||
"hosts.len() <= 32", "domains.len() <= 16",
|
||
]:
|
||
assert forbidden_0241 not in tls_0241, forbidden_0241
|
||
for source_0241 in [vhost_0241, redirect_0241, not_found_0241]:
|
||
assert "hosts.len() <= 16" not in source_0241
|
||
assert "allowed_hosts.len() <= 32" not in settings_0240
|
||
assert "MAX_ALIASES" not in docker_0241 and "aliases.len() <= 32" not in docker_0241
|
||
assert "const add = (raw, changed = true) => { const value = normalize(raw); if (!value || values.includes(value)) return; values.push(value);" in integrations_0241
|
||
assert "values.length >= 32" not in docker_js_0241
|
||
for marker_0241 in [
|
||
"certificate_hostname_bindings_are_not_artificially_capped",
|
||
"acme_validation_has_no_rustproxy_domain_count_ceiling",
|
||
]:
|
||
assert marker_0241 in tls_0241, marker_0241
|
||
assert "vhost_hostname_count_is_not_artificially_capped" in vhost_0241
|
||
assert "administration_hostname_count_is_not_artificially_capped" in settings_0240
|
||
assert "docker_alias_count_is_not_artificially_capped" in docker_0241
|
||
openapi_0241 = json.loads(openapi)
|
||
for schema_name, property_path in [
|
||
("CertificateUpload", ("hosts",)),
|
||
("AcmeIssue", ("domains",)),
|
||
("DockerAssignment", ("aliases",)),
|
||
]:
|
||
node = openapi_0241["components"]["schemas"][schema_name]["properties"]
|
||
for part in property_path:
|
||
node = node[part]
|
||
assert "maxItems" not in node, (schema_name, property_path)
|
||
for schema_name in ["RedirectHost", "NotFoundHost"]:
|
||
node = openapi_0241["components"]["schemas"][schema_name]["properties"]["frontend"]["properties"]["hosts"]
|
||
assert "maxItems" not in node, schema_name
|
||
|
||
# 0.27.3 maintenance regressions: restore ownership, production paths, GeoIP,
|
||
# explicit startup binds, strict WebSocket 101 framing and fail-soft update checks.
|
||
bootstrap_0273 = rust_source(root, "src/main/bootstrap.rs")
|
||
settings_0273 = rust_source(root, "src/settings.rs")
|
||
websocket_0273 = rust_source(root, "src/proxy/websocket/handshake.rs")
|
||
websocket_tests_0273 = (root / "src/proxy/websocket/tests.rs").read_text()
|
||
backup_validation_0273 = (root / "src/backup/validation.rs").read_text()
|
||
backup_owner_0273 = (root / "src/backup/ownership.rs").read_text()
|
||
geoip_update_0273 = (root / "src/geoip/update.rs").read_text()
|
||
geoip_http_0273 = (root / "src/geoip/update_http.rs").read_text()
|
||
update_check_0273 = rust_source(root, "src/update_check.rs")
|
||
admin_runtime_0273 = rust_source(root, "src/main/admin_runtime.rs")
|
||
dev_0273 = (root / "dev.sh").read_text()
|
||
style_0273 = (root / "src/admin/web/style.css").read_text()
|
||
settings_web_0273 = (root / "src/admin/web/settings.js").read_text()
|
||
assert 'default_value = "/var/lib/rustproxy"' in bootstrap_0273
|
||
assert 'RUST_PROXY_DATA_DIR="${RUST_PROXY_DATA_DIR:-$ROOT/data}"' in dev_0273
|
||
assert 'let restore_owner = if data_dir.exists()' in backup_validation_0273
|
||
assert 'set_tree_owner(&stage, owner)?;' in backup_validation_0273
|
||
assert 'libc::lchown' in backup_owner_0273 and 'restore_owner_is_applied_recursively' in (root / "src/backup/tests.rs").read_text()
|
||
assert 'explicit_startup_bind_replaces_canonical_saved_binding' in settings_0273
|
||
assert 'endpoint.id == canonical_id' in settings_0273
|
||
assert 'strict_upstream_101_body_framing_matrix' in websocket_tests_0273
|
||
assert '!headers.contains_key("transfer-encoding")' in websocket_0273
|
||
assert 'value.trim() == "0"' in websocket_0273
|
||
for marker_0273 in [
|
||
'DEFAULT_IPV4_URL', 'DEFAULT_IPV6_URL', 'DEFAULT_MMDB_URL', 'DEFAULT_MAX_BYTES',
|
||
'DEFAULT_INTERVAL_SECONDS', '.geoip-update.lock', 'pub async fn poll_loop',
|
||
'GeoIP update failed; proxy traffic is unaffected',
|
||
]:
|
||
assert marker_0273 in geoip_update_0273, marker_0273
|
||
for marker_0273 in ['pub(super) async fn download', 'expected_digest: Option<&str>', 'written <= max_bytes', 'Sha256::new()']:
|
||
assert marker_0273 in geoip_http_0273, marker_0273
|
||
assert 'state.start_geoip_updates();' in admin_runtime_0273
|
||
assert admin_runtime_0273.index('let admin_listener = TcpListener::bind(admin_addr)') < admin_runtime_0273.index('state.start_update_check(update_check);')
|
||
assert 'tracing::warn!(' in update_check_0273 and 'update check failed; retrying on the next interval' in update_check_0273
|
||
assert 'calc(100vh / var(--ui-scale, 1))' in style_0273
|
||
for marker_0273 in ['geoip-form', 'geoip_updates', '268435456', '86400']:
|
||
assert marker_0273 in settings_web_0273 or marker_0273 in html, marker_0273
|
||
assert '"geoip"' in openapi and 'GeoIpUpdateSettings' in openapi
|
||
|
||
# 0.28.0 retained operator controls and cache-policy regressions.
|
||
geoip_tests_0280 = (root / "src/geoip/update_tests.rs").read_text()
|
||
cache_settings_0280 = (root / "src/cache/model/settings.rs").read_text()
|
||
cache_entry_0280 = (root / "src/cache/model/entry.rs").read_text()
|
||
runtime_types_0280 = (root / "src/runtime/types.rs").read_text()
|
||
runtime_tests_0280 = (root / "src/runtime/tests.rs").read_text()
|
||
vhost_core_0280 = (root / "src/admin/web/vhosts/core.js").read_text()
|
||
runtime_web_0280 = (root / "src/admin/web/app/runtime.js").read_text()
|
||
cache_web_0280 = (root / "src/admin/web/app/settings.js").read_text()
|
||
for marker_0280 in [
|
||
'pub async fn update_now', 'publish_database(&app.geoip, database)',
|
||
'UpdateOutcome::Updated', 'UpdateOutcome::Current', 'UpdateOutcome::Busy',
|
||
]:
|
||
assert marker_0280 in geoip_update_0273, marker_0280
|
||
assert 'publishing_a_downloaded_database_hot_swaps_future_geoip_lookups' in geoip_tests_0280
|
||
assert 'state.geoip.load().country(context.client_ip)' in rust_source(root, "src/proxy/forward_entry.rs")
|
||
assert '.route("/api/geoip/update", post(geoip_update))' in admin_0241
|
||
assert 'id="geoip-update-now"' in html and 'api("/api/geoip/update", { method: "POST" })' in settings_web_0273
|
||
assert 'GeoIpUpdateResult' in openapi and '"/api/geoip/update"' in openapi
|
||
for marker_0280 in [
|
||
'DatabaseFormat::CsvPair', 'DatabaseFormat::Mmdb', 'ipv4_checksum_url: Option<String>',
|
||
'mmdb_checksum_url: Option<String>', 'validate_directory_format',
|
||
]:
|
||
assert marker_0280 in geoip_update_0273 or marker_0280 in (root / 'src/geoip.rs').read_text(), marker_0280
|
||
assert 'country.iso_code' in (root / 'langs/en_US.json').read_text()
|
||
assert 'user-country.mmdb' in settings_web_0273 and 'geoip-mmdb-url' in html
|
||
assert 'publisher checksums are intentionally not required' in (root / 'scripts/deploy/deploy_core.py').read_text().lower()
|
||
assert 'pub via_header: Option<String>' in cache_settings_0280 and 'via_header: Some("RustProxy".into())' in cache_settings_0280
|
||
assert 'http::header::VIA' in cache_entry_0280 and 'headers.append(' in cache_entry_0280
|
||
for marker_0280 in ['cache.bypass_query = false', 'cache.bypass_prefixes.clear()', 'cache.via_header = None']:
|
||
assert marker_0280 in runtime_types_0280, marker_0280
|
||
assert 'cache_invalidation(&base, &wire_only)' in runtime_tests_0280 and 'CacheInvalidation::None' in runtime_tests_0280
|
||
assert 'listener-active-item' in runtime_web_0280 and 'listener-active-feature' in runtime_web_0280
|
||
assert 'cache-via-enabled' in cache_web_0280 and 'via_header:' in cache_web_0280
|
||
assert 'location-header-direction location-header-editor' in vhost_core_0280
|
||
|
||
# 0.29.3 internal-error/multipart/PATH_INFO contracts.
|
||
vhost_model_0291 = rust_source(root, "src/vhost.rs")
|
||
vhost_validation_0291 = rust_source(root, "src/vhost/validation.rs")
|
||
config_snapshot_0291 = rust_source(root, "src/config/internal_routing.rs")
|
||
proxy_types_0291 = rust_source(root, "src/proxy/error_fallback.rs")
|
||
proxy_response_0291 = rust_source(root, "src/proxy/forward/response.rs")
|
||
web_headers_0291 = rust_source(root, "src/web_server/response/headers.rs")
|
||
web_file_0291 = rust_source(root, "src/web_server/response/file.rs")
|
||
php_protocol_0291 = rust_source(root, "src/web_server/fastcgi/protocol.rs")
|
||
php_model_0291 = rust_source(root, "src/web_server/config/model.rs")
|
||
prometheus_0291 = rust_source(root, "src/prometheus/core.rs")
|
||
for marker_0291 in ['pub struct ErrorFallback', 'ERROR_FALLBACK_STATUSES', 'pub internal: bool', 'pub error_fallback: Option<ErrorFallback>']:
|
||
assert marker_0291 in vhost_model_0291, marker_0291
|
||
assert 'recursive error fallback loop detected' in vhost_validation_0291
|
||
assert 'select_internal_on' in config_snapshot_0291
|
||
for marker_0291 in ['MAX_ERROR_FALLBACK_DEPTH', 'MAX_ERROR_FALLBACK_REPLAY_BODY_BYTES', 'execute_error_fallback']:
|
||
assert marker_0291 in proxy_types_0291, marker_0291
|
||
assert 'execute_error_fallback' in proxy_response_0291
|
||
assert 'MAX_BYTE_RANGES: usize = 16' in web_headers_0291 and 'parse_ranges' in web_headers_0291
|
||
assert 'multipart/byteranges' in web_file_0291
|
||
assert 'pub path_info: bool' in php_model_0291 and 'PATH_INFO' in php_protocol_0291
|
||
for marker_0291 in ['rust_proxy_vhost_error_fallback_triggered_total', 'rust_proxy_vhost_error_fallback_successful_total', 'rust_proxy_vhost_error_fallback_failed_total']:
|
||
assert marker_0291 not in prometheus_0291, marker_0291
|
||
assert 'unavailable: host.transient_unavailable && host.source.is_reverse_proxy()' in rust_source(root, "src/config/routing.rs")
|
||
assert 'filter(|_| needs_proxy_pool)' in rust_source(root, "src/config/routing.rs")
|
||
assert (root / 'docs/V0.29.1_IMPLEMENTATION.md').is_file()
|
||
assert '"ErrorFallback"' in openapi and '"path_info"' in openapi and '"internal"' in openapi
|
||
|
||
# Retained 0.29.0 redirect/health/cache/gzip-log contracts.
|
||
vhost_model_0290 = rust_source(root, "src/vhost/model.rs")
|
||
vhost_validation_0290 = rust_source(root, "src/vhost/validation.rs")
|
||
config_model_0290 = rust_source(root, "src/config/model.rs")
|
||
config_routing_0290 = rust_source(root, "src/config/routing.rs")
|
||
proxy_redirect_0290 = rust_source(root, "src/proxy/redirect_helpers.rs")
|
||
route_cache_0290 = rust_source(root, "src/proxy/forward/route_cache.rs")
|
||
cache_entry_0290 = rust_source(root, "src/cache/model/entry.rs")
|
||
cache_request_0290 = rust_source(root, "src/cache/policy/request.rs")
|
||
file_logs_0290 = rust_source(root, "src/admin/file_logs.rs")
|
||
file_logs_io_0290 = rust_source(root, "src/admin/file_logs_io.rs")
|
||
for marker_0290 in ['www_redirect', 'redirect: Option<LocationRedirect>']:
|
||
assert marker_0290 in vhost_model_0290, marker_0290
|
||
for marker_0290 in ['routing_hosts', 'www_redirect_pair']:
|
||
assert marker_0290 in vhost_validation_0290, marker_0290
|
||
assert 'www_redirect: Option<(String, String)>' in config_model_0290
|
||
assert 'health_check.host.is_none()' in config_routing_0290 and 'HealthMode::Http' in rust_source(root, "src/config/routing.rs")
|
||
assert 'canonical_host_redirect' in proxy_redirect_0290 and 'PERMANENT_REDIRECT' in proxy_redirect_0290
|
||
assert 'redirect_location' in rust_source(root, "src/config/snapshot.rs") and 'local_redirect' in route_cache_0290
|
||
assert 'pub struct UnsafeForce' in cache_entry_0290 and 'unsafe_force: UnsafeForce' in cache_entry_0290
|
||
assert 'forced_request_policy' in cache_request_0290 and 'cache_miss_forced' in rust_source(root, "src/proxy/mod.rs")
|
||
assert 'compressed: bool' in file_logs_0290 and 'GzDecoder' in file_logs_io_0290
|
||
for path_0290 in ['src/vhost/redirect.rs', 'docs/V0.29.0_IMPLEMENTATION.md']:
|
||
assert (root / path_0290).is_file(), path_0290
|
||
assert 'static_vhost_page_can_serve_html_with_503_status' in rust_source(root, "src/pages.rs")
|
||
assert 'upstream_uri_preserves_nested_url_query_without_rejecting_valid_separators' in rust_source(root, "src/proxy/mod.rs")
|
||
assert '"www_redirect"' in openapi and '"unsafe_force"' in openapi
|
||
assert '"redirect"' in openapi and 'Backend-less client redirect for this location.' in openapi
|
||
|
||
# 0.28.2 external-certificate ACL/path hardening and least-privilege Certificate API.
|
||
tls_types_0282 = (root / "src/tls/types.rs").read_text()
|
||
tls_external_0282 = (root / "src/tls/external_paths.rs").read_text()
|
||
tls_server_0282 = (root / "src/tls/server_config.rs").read_text()
|
||
tls_tests_0282 = (root / "src/tls/tests.rs").read_text() + (root / "src/tls/tests/external_paths.rs").read_text()
|
||
backup_snapshot_0282 = (root / "src/backup/snapshot.rs").read_text()
|
||
certificate_admin_0282 = (root / "src/admin/certificates.rs").read_text()
|
||
certificate_api_db_0282 = (root / "src/db/certificate_api.rs").read_text()
|
||
certificate_session_0282 = (root / "src/admin/session.rs").read_text()
|
||
deploy_core_0282 = (root / "scripts/deploy/deploy_core.py").read_text()
|
||
for marker_0282 in [
|
||
'pub fn certificate_files_are_external',
|
||
'cert_external == key_external',
|
||
]:
|
||
assert marker_0282 in tls_external_0282, marker_0282
|
||
for marker_0282 in [
|
||
'validate_external_parent_chain',
|
||
'fs::canonicalize(configured)',
|
||
'libc::O_NOFOLLOW',
|
||
'mode & 0o022 == 0',
|
||
'mode & 0o004 == 0',
|
||
'POSIX ACL masks are reflected',
|
||
]:
|
||
assert marker_0282 in tls_external_0282, marker_0282
|
||
assert 'certificate_files_are_external(cert, key)?;' in tls_server_0282
|
||
for marker_0282 in [
|
||
'external_absolute_paths_follow_certbot_style_symlinks_on_each_reload',
|
||
'external_key_permissions_accept_acl_style_group_mask_but_reject_unsafe_write_or_world_read',
|
||
'external_paths_reject_writable_non_sticky_parent_directories',
|
||
]:
|
||
assert marker_0282 in tls_tests_0282, marker_0282
|
||
assert 'certificate_files_are_external(' in backup_snapshot_0282 and 'External certificate material is intentionally outside' in backup_snapshot_0282
|
||
assert 'uses external file paths; renew those files in place and call the certificate reload endpoint instead of deploy' in certificate_admin_0282
|
||
for marker_0282 in [
|
||
'certificate_api_grants',
|
||
'allow_deploy',
|
||
'certificate_ids',
|
||
'value != "*"',
|
||
]:
|
||
assert marker_0282 in certificate_api_db_0282, marker_0282
|
||
for marker_0282 in [
|
||
'certificate_api.target_required',
|
||
'certificate_api.certificate_forbidden',
|
||
'certificate_api.deploy_forbidden',
|
||
]:
|
||
assert marker_0282 in certificate_session_0282, marker_0282
|
||
assert 'ReadOnlyPaths=-/etc/letsencrypt -/etc/ssl' in deploy_core_0282
|
||
for marker_0282 in [
|
||
'id="certificate-file-source"',
|
||
'certificateSourceManaged',
|
||
'certificateSourceExternal',
|
||
'/etc/letsencrypt/live/example.com/fullchain.pem',
|
||
'certificate-external-acl-example',
|
||
'certificate-api-certificate-ids',
|
||
'certificate-api-allow-deploy',
|
||
]:
|
||
assert marker_0282 in html or marker_0282 in runtime_web_0280 or marker_0282 in (root / 'langs/en_US.json').read_text(), marker_0282
|
||
assert 'Zewnętrzne ścieżki plików' in (root / 'langs/pl_pl.json').read_text()
|
||
assert 'setfacl -m u:rustproxy:--x /etc/ssl/' in (root / 'docs/V0.28.2_IMPLEMENTATION.md').read_text()
|
||
|
||
print("PASS: 0.31.2 manifest/package, shared system-statistics/runtime metadata, Settings logging, retained H2, external-packaging/Grafana/Prometheus/404-host/custom-location/path/cache/dashboard/Nagios UI/source contracts, stopped legacy-renewal removal, session/admin/transport contracts, deterministic package source and embedded SQLite schema-v9 migrations.")
|
||
print("NOT VERIFIED: Rust parsing, types, compilation, linking or runtime behavior. Run ./dev.sh test.")
|