This commit is contained in:
Mateusz Gruszczyński committed 2026-10-10 21:48:39 +02:00
1 parent 76b8bb7ee2
commit 24af7478b8
29 files changed
+321 -171

No files matched your search

+1 -1
View File
@@ -111,4 +111,4 @@ for fragment in ['class="metric-info cache-maintenance-info"',
'cacheHttpInvalidationCurlLabel',
'curl -i -X PURGE', 'curl -i -X BAN']:
must(html, fragment)
print(f'PASS: 0.31.0 admin/HTTP maintenance security source contracts; scanned {len(js_files)} first-party JS files. Actual Rust/wire behavior not tested.')
print(f'PASS: admin/HTTP maintenance security source contracts; scanned {len(js_files)} first-party JS files. Actual Rust/wire behavior not tested.')
+13 -13
View File
@@ -24,7 +24,7 @@ def read_docker_agent_source() -> str:
preflight(root)
manifest = tomllib.loads((root / "Cargo.toml").read_text())
assert manifest["package"]["version"] == "0.31.0"
assert manifest["package"]["version"] == "0.31.1"
assert manifest["dependencies"]["quinn"]["version"] == "0.11.12"
h3dep = manifest["dependencies"]["h3"]
assert h3dep["package"] == "hpx-h3" and h3dep["version"] == "=2.5.19" and "quinn" in h3dep["features"]
@@ -32,14 +32,14 @@ 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.0"' in lock
assert 'name = "rust-proxy"\nversion = "0.31.1"' 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.0",
"CHANGELOG.md": "## 0.31.0",
"ROADMAP.md": "## 0.31.0",
"AGENTS.md": "Current source version: 0.31.0.",
"docs/TEST_REPORT.md": "# Verification report - RustProxy 0.31.0",
"README.md": "# RustProxy 0.31.1",
"CHANGELOG.md": "## 0.31.1",
"ROADMAP.md": "## 0.31.1",
"AGENTS.md": "Current source version: 0.31.1.",
"docs/TEST_REPORT.md": "# Verification report - RustProxy 0.31.1",
}.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"
@@ -177,7 +177,7 @@ for source_name, markers in {
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 '/assets/docker.js' in html and 'cache-live-toggle' 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()
@@ -216,7 +216,7 @@ for lang_name in ["en_US.json", "pl_pl.json"]:
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 '/assets/streams.js' in html and '/assets/redirects.js' in html and '/assets/host404.js' 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
@@ -285,7 +285,7 @@ assert 'Opt-in health decisions based on ordinary user traffic. Defaults to disa
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.0"', '"/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']:
for marker in ['"version": "0.31.1"', '"/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
@@ -980,7 +980,7 @@ 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 '/assets/transform-panel.js' 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
@@ -1396,7 +1396,7 @@ assert '"error_log" => {' not in admin_0241 and 'request("/api/config/error_log"
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.0"' in openapi
assert '"RuntimeInformation"' in openapi and '"version": "0.31.1"' in openapi
# 0.24.1 compiler and hostname-cardinality regressions.
tls_0241 = rust_source(root, "src/tls.rs")
@@ -1635,5 +1635,5 @@ for marker_0282 in [
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.0 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("PASS: 0.31.1 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.")
@@ -0,0 +1,89 @@
#!/usr/bin/env node
// Release-independent, real-source startup ordering and recovery contract.
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { runInNewContext } from 'node:vm';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const root = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
const read = path => readFileSync(resolve(root, path), 'utf8');
const html = read('src/admin/web/index.html');
const runtime = read('src/admin/web/app/runtime.js');
const core = read('src/admin/web/app/core.js');
const router = read('src/admin/router.rs');
const bundle = read('tools/checks/web_assets.py');
const version = read('Cargo.toml').match(/^version\s*=\s*"([\d.]+)"/m)?.[1];
assert(version);
assert.equal((html.match(/<script defer="" src="\/assets\//g) || []).length, 1, 'one atomic dashboard script');
assert.match(html, /src="\/assets\/app\.js\?v=__RUSTPROXY_VERSION__"/);
assert.match(html, /href="\/assets\/style\.css\?v=__RUSTPROXY_VERSION__"/);
assert.match(router, /admin_vhosts_javascript\(\)/);
assert.match(bundle, /"integrations\.js", "vhosts\.js"/);
assert(router.indexOf('include_str!("web/integrations.js")') < router.indexOf('admin_vhosts_javascript(),'));
assert(router.indexOf('admin_vhosts_javascript(),') < router.indexOf('include_str!("web/host404.js")'));
const prefix = runtime.slice(0, runtime.indexOf('function certificateUsesExternalFiles'));
assert(prefix.includes('document.addEventListener("DOMContentLoaded", startAdminPanel'));
function simulate(complete) {
let bootstrap, calls = 0, activated = 0, failed = null;
const modules = {};
for (const [name, action] of [
['rustProxyIntegrations','bindings'], ['rustProxyVhosts','render'],
['rustProxySettings','render'], ['rustProxyCachePanel','refresh'],
['rustProxyTrafficPanel','refresh'], ['rustProxyPreferences','load'],
['rustProxyRefresh','start'],
]) if (complete || name !== 'rustProxyIntegrations') modules[name] = { [action]() {} };
const ctx = {
window: modules, document: { readyState: 'loading', addEventListener(name, fn) {
assert.equal(name, 'DOMContentLoaded'); bootstrap = fn;
} },
api: async () => { calls++; return { csrf_token: 'abc' }; },
clearInvalidOriginRecovery() {}, loadLanguages: async () => {},
translate() {}, syncAuthForm() {}, activate: async () => { activated++; },
recoverInvalidOrigin: () => false, showStartupFailure: e => { failed = e; },
location: { pathname: '/overview', search: '', replace() { throw new Error('unexpected redirect'); } },
};
runInNewContext(prefix, ctx);
assert.equal(calls, 0, 'zero API calls before all defer/module readiness');
assert.equal(activated, 0);
assert(bootstrap, 'registered DOMContentLoaded bootstrap');
return { bootstrap, state: () => ({ calls, activated, failed }) };
}
const success = simulate(true);
success.bootstrap();
for (let i = 0; i < 8; i++) await Promise.resolve();
assert.equal(success.state().calls, 1);
assert.equal(success.state().activated, 1);
assert.equal(success.state().failed, null);
const missing = simulate(false);
missing.bootstrap();
for (let i = 0; i < 8; i++) await Promise.resolve();
assert.equal(missing.state().calls, 0, 'do not initialize partially loaded modules');
assert.match(missing.state().failed.message, /rustProxyIntegrations/);
// Exercise real activate on temporary config API errors and on persistent failure.
const activation = core.slice(core.indexOf('async function activate(info) {'), core.indexOf('async function loadAudit()'));
assert(activation.startsWith('async function activate(info)'));
async function configRetry(failures) {
const nodes = new Map();
const node = key => { if (!nodes.has(key)) nodes.set(key, { hidden: true, value: '' }); return nodes.get(key); };
let config = 0, fallback = 0, status = 0;
const ctx = {
$: node, authenticated: false, csrf: '', authEpoch: 0, sessionInfo: null, pendingAuthDestination: null,
window: {}, document: { documentElement: { classList: { remove() {} } } },
authMessage() {}, renderSession() {}, rustProxyRefresh: { start() {} },
loadAdminPreferences: async () => ({}),
loadConfig: async () => { config++; if (config <= failures) throw new Error('Temporary proxy failure'); },
refresh: async () => { status++; },
setTimeout: fn => { fn(); return 1; },
showStartupFailure: () => { fallback++; },
toast() { throw new Error('A transient failure must not be hidden in a toast'); },
};
runInNewContext(activation, ctx);
await ctx.activate({ username: 'owner', csrf_token: 'token' });
for (let i=0;i<4;i++) await Promise.resolve();
return { config, fallback, status };
}
assert.deepEqual(await configRetry(2), { config: 3, fallback: 0, status: 1 });
assert.deepEqual(await configRetry(3), { config: 3, fallback: 1, status: 0 });
console.log(`PASS: ${version} one versioned JS payload, DOM/module-gated startup, retries and visible failure`);
@@ -1,5 +1,5 @@
#!/usr/bin/env node
// Network-independent browser bootstrap contract: visible recovery and early shell.
// Browser bootstrap contract: visible recovery and early shell.
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { runInNewContext } from 'node:vm';
+3 -3
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Rust-independent documentation/Reader consistency checks for the 0.31.0 release."""
"""Rust-independent documentation/Reader consistency checks for the 0.31.1 release."""
from pathlib import Path
import re
from rust_source import rust_source
@@ -29,7 +29,7 @@ required = [
DOCS / "V0.25.2_AUDIT.md",
DOCS / "V0.25.1_AUDIT.md",
DOCS / "V0.25.0_AUDIT.md",
DOCS / "IMPLEMENTATION_0.31.0.md",
DOCS / "IMPLEMENTATION_0.31.1.md",
DOCS / "TEST_REPORT.md",
DOCS / "CHANGELOG_TECHNICAL_ARCHIVE.md",
DOCS / "ROADMAP_HISTORY.md",
@@ -65,7 +65,7 @@ assert '"/docs/content.json"' in admin
readme = (ROOT / "README.md").read_text(encoding="utf-8")
roadmap = (ROOT / "ROADMAP.md").read_text(encoding="utf-8")
changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8")
assert readme.startswith("# RustProxy 0.31.0")
assert readme.startswith("# RustProxy 0.31.1")
assert "docs/WEB_SERVER.md" in readme and "docs/README.md" in readme
assert readme.count("## What changed in ") == 1, "README duplicates historical release notes; use CHANGELOG/archive"
assert "## Release history" in readme and "docs/ROADMAP_HISTORY.md" in readme
+2 -2
View File
@@ -75,10 +75,10 @@ def check(root: Path, repair_known: bool = False) -> list[str]:
raise ValueError(f"Conflicting Rust modules: {flat.relative_to(root)} and {module.relative_to(root)}")
for path in ["src/auth/mod.rs", "src/auth/store.rs", "src/backup/snapshot.rs", "src/upstream.rs", "src/tls.rs", "src/config/internal_routing.rs", "src/proxy/websocket.rs", "src/proxy/error_fallback.rs", "src/cache/mod.rs", "src/cache/model.rs", "src/cache/lifecycle.rs", "src/cache/cold.rs", "src/cache/dashboard.rs", "src/cache/tickets.rs", "src/cache/lookup.rs", "src/cache/policy.rs", "src/cache/fill.rs", "src/cache/tests.rs", "src/settings.rs", "src/compression.rs", "src/diagnostics.rs", "src/traffic.rs", "src/system_stats/mod.rs", "src/system_stats/linux.rs", "src/vhost.rs", "src/vhost/error_fallback.rs", "src/web_server/mod.rs", "src/web_server/config.rs", "src/web_server/filesystem.rs", "src/web_server/security.rs", "src/web_server/response.rs", "src/web_server/request.rs", "src/web_server/routing.rs", "src/web_server/fastcgi.rs", "src/web_server/htaccess.rs", "src/web_server/serve.rs", "src/web_server/tests.rs", "src/balance.rs", "src/security.rs", "src/admin/web/vhosts/core.js", "src/admin/web/vhosts/health.js", "src/admin/web/vhosts/editor.js", "src/admin/web/vhosts/render.js", "src/admin/web/web-server.js", "src/admin/web/streams.js", "src/admin/web/host404.js", "src/admin/web/app/core.js", "src/admin/web/app/preferences.js", "src/admin/web/app/config-history.js", "src/admin/web/app/runtime.js", "src/admin/web/app/settings.js", "src/admin/web/management.js", "src/admin/web/drafts.js", "src/pages.rs", "tools/dev/mock_server.py", "tools/dev/mock_auth.py", "src/forwarding.rs", "src/tinyauth.rs", "src/http3.rs", "src/stream.rs", "src/admin/web/integrations.js", "src/admin/web/traffic-panel.js", "src/prometheus.rs", "src/admin/web/settings.js", "src/admin/web/docker.js", "src/docker.rs", "docker-agent/main.go", "docker-agent/model.go", "docker-agent/docker.go", "docker-agent/network.go", "docker-agent/certificate.go", "docker-agent/server.go", "docker-agent/main_test.go", "docker-agent/rustproxy-docker-agent.service", "docker-agent/install.sh", "docs/DOCKER_DISCOVERY.md", "docs/WEB_SERVER.md", "docs/PHP_FPM.md", "docs/README.md", "docs/TODO.md", "docs/V0.25.5_AUDIT.md", "docs/V0.25.4_AUDIT.md", "docs/V0.25.3_AUDIT.md", "docs/V0.25.2_AUDIT.md", "docs/CHANGELOG_TECHNICAL_ARCHIVE.md", "docs/ROADMAP_HISTORY.md", "tools/checks/docs_check.py", "tools/checks/web_server_contracts.py", "tools/checks/rust_source.py", "tools/checks/rust_module_contracts.py", "tools/checks/source_module_contracts.py", "scripts/runtime/acme_provider.py", "src/basic_auth.rs", "scripts/deploy/deploy.py", "scripts/deploy/deploy_core.py", "scripts/deploy/deploy_runtime.py", "scripts/migration/migrate_npm.py", "scripts/migration/migrate_npm_core.py", "scripts/migration/migrate_npm_api.py", "docs/NPM_MIGRATION.md", "docs/STREAMS.md", "install.sh", "install-debian13.sh", "update.sh", "build.rs", "scripts/build/swagger_assets.py", "src/admin/documentation.rs", "src/admin/openapi.json", "langs/en_US.json", "langs/pl_pl.json", "Dockerfile", ".dockerignore", "docs/CONTAINER.md", "docs/PROMETHEUS.md", "docs/NOT_FOUND_HOSTS.md", "grafana/rustproxy-overview.json"]:
if not (root / path).is_file():
raise ValueError(f"Incomplete RustProxy 0.31.0 source tree: missing {path}")
raise ValueError(f"Incomplete RustProxy 0.31.1 source tree: missing {path}")
text = (root / "src/lib.rs").read_text(encoding="utf-8")
if '#[path = "auth/mod.rs"]' not in text:
raise ValueError("src/lib.rs is not the 0.31.0 entry point; extract the complete package, not individual files.")
raise ValueError("src/lib.rs is not the 0.31.1 entry point; extract the complete package, not individual files.")
return messages
+7 -9
View File
@@ -135,7 +135,7 @@ def upgrade_smoke(tmp: Path) -> None:
assert backup.is_dir()
assert (backup / "src/obsolete.rs").is_file()
assert not (target / "src/obsolete.rs").exists()
assert 'version = "0.31.0"' in (target / "Cargo.toml").read_text(encoding="utf-8")
assert 'version = "0.31.1"' in (target / "Cargo.toml").read_text(encoding="utf-8")
assert (target / "data/keep.txt").read_text(encoding="utf-8") == "preserve\n"
assert (target / ".env").read_text(encoding="utf-8") == "RUST_PROXY_DEMO=0\n"
assert (target / "langs/site.json").is_file()
@@ -146,7 +146,7 @@ def upgrade_smoke(tmp: Path) -> None:
assert (target / "docker-agent/certificate.go").is_file()
assert (target / "docker-agent/server.go").is_file()
manifest = json.loads((backup / "upgrade.json").read_text(encoding="utf-8"))
assert manifest["to_version"] == "0.31.0"
assert manifest["to_version"] == "0.31.1"
assert manifest["removed_obsolete_runtime_state"] is True
assert not legacy.exists()
assert not (backup / "legacy-data").exists()
@@ -396,9 +396,7 @@ def ui_contract_smoke() -> None:
for marker in ["rustProxyStreams", "streamTrafficChart", "streamPendingRestart", "chartHistory", "sessions_total", "bytes_in", "bytes_out"]:
assert marker in streams, marker
assert '$("stream-listener-add").addEventListener("click", () => { setPanel("settings"); window.rustProxySettings?.select("listeners"); });' in streams
assert "/assets/streams.js" in html
assert "/assets/redirects.js" in html
assert "/assets/host404.js" in html
assert all(f'include_str!("web/{name}.js")' in (ROOT / 'src/admin/router.rs').read_text() for name in ('streams', 'redirects', 'host404'))
for marker in ["rustProxyRedirects", 'saveSection("redirects"', "redirectTargetType", "redirectPathMode", "redirectQueryMode", "redirectCacheSeconds"]:
assert marker in redirects, marker
for marker in ["rustProxyNotFound", 'saveSection("not_found_hosts"', "rustProxyIntegrations.hostnames", "rustProxyIntegrations.bindings", "notfound-mode", "notfound-status", "BUILTIN_STATUSES", "ERROR_STATUSES", 'rustProxyDrafts.register("notfound"', 'rustProxyEnableControl($("notfound-enabled"))']:
@@ -619,7 +617,7 @@ def source_contract_smoke() -> None:
assert marker in admin, marker
anonymous_block = admin.split("fn anonymous_allowed", 1)[1].split("const CERTIFICATE_API_KEY_HEADER", 1)[0]
assert '"/api/status"' not in anonymous_block
assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.31.0"' in openapi_0241
assert '"RuntimeInformation"' in openapi_0241 and '"version": "0.31.1"' in openapi_0241
assert 'id="panel-logging"' not in html
for marker in ['id="settings-tab-logging"', 'id="settings-page-logging"', 'id="logging-form"', 'id="error-log-form"']:
assert marker in html, marker
@@ -737,7 +735,7 @@ def source_contract_smoke() -> None:
assert '.docker-passive-health-editor .docker-health-grid {' in ui_css
assert '.docker-health-grid input:not([type="checkbox"])' in ui_css and '.docker-passive-health-editor .docker-checkbox-field input[type="checkbox"]' in ui_css
assert 'id="sparkline-transform"' in html and 'id="rate-transform"' in html
assert 'id="panel-transform"' in html and 'data-panel="transform"' in html and '/assets/transform-panel.js' in html
assert '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 and 'tp-cache-hits' in transform_panel
for marker in ['transform-memory-budget', 'transform-max-inflight', 'data-transform-preset="conservative"', 'data-transform-preset="recommended"', 'data-transform-preset="performance"', 'data-transform-preset="uber"']:
assert marker in html, marker
@@ -1110,7 +1108,7 @@ def source_contract_smoke() -> None:
assert '.managed-update' in main and "flush_hot_to_cold_for_update" in main
openapi = (ROOT / "src/admin/openapi.json").read_text(encoding="utf-8")
assert '"/api/config/history/restore"' in openapi
for marker in ['"version": "0.31.0"', '"memory_preset"', '"cold_preset"', '"memory_budget_bytes"', '"max_inflight"', '"preset"', '"session_days"', '"session_lifetime_seconds"', '"accept_local_listener_risk"', '"backend_header"', '"backend_timing_header"', '"cold_on_age"', '"match_type"', '"regex_insensitive"', '"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
for marker in ['"version": "0.31.1"', '"memory_preset"', '"cold_preset"', '"memory_budget_bytes"', '"max_inflight"', '"preset"', '"session_days"', '"session_lifetime_seconds"', '"accept_local_listener_risk"', '"backend_header"', '"backend_timing_header"', '"cold_on_age"', '"match_type"', '"regex_insensitive"', '"NotFoundHost"', '"not_found_hosts"', '"PrometheusSettings"', '"allow_trusted_network_without_token"', '"include_docker_vhosts"', '"include_streams"', '"include_logging"']:
assert marker in openapi, marker
assert '"software_update"' in openapi and '"update_available"' in openapi
for marker in ['"/api/certificates/deploy"', '"CertificateDeploy"', '"CertificateDeployResult"', '"/api/certificates/reload"']:
@@ -1315,7 +1313,7 @@ def main() -> None:
source_contract_smoke()
container_contract_smoke()
grafana_contract_smoke()
print("PASS: 0.31.0 SQLite/schema-9/upgrade, local-response 404 hosts, Prometheus/Grafana diagnostics, external Debian-packaging separation, cache/dashboard/logging/Nagios, retained admin/H3/stream/TLS contracts and reproducible source archives.")
print("PASS: 0.31.1 SQLite/schema-9/upgrade, local-response 404 hosts, Prometheus/Grafana diagnostics, external Debian-packaging separation, cache/dashboard/logging/Nagios, retained admin/H3/stream/TLS contracts and reproducible source archives.")
if __name__ == "__main__":
+3
View File
@@ -53,6 +53,9 @@ if node:
for path in sorted(standalone):
subprocess.run([node, "--check", str(path)], check=True, env=env, stdout=subprocess.DEVNULL)
from web_assets import read_app_js, read_vhosts_js
browser_markup = (ROOT / "src/admin/web/index.html").read_text(encoding="utf-8")
assert browser_markup.count('<script defer="" src="/assets/') == 1, "one private JS bundle prevents module race"
assert '/assets/app.js?v=__RUSTPROXY_VERSION__' in browser_markup
with tempfile.TemporaryDirectory(prefix="rustproxy-js-contract-") as directory:
temporary = Path(directory)
for name, content in (("app.js", read_app_js(ROOT)), ("vhosts.js", read_vhosts_js(ROOT))):
+3 -3
View File
@@ -43,7 +43,7 @@ const code = js.slice(js.indexOf(' const runtime = latestStatus?.runtime || {
const elements = new Map();
const dollar = id => { if (!elements.has(id)) elements.set(id, { textContent: '' }); return elements.get(id); };
const status = {
version: '0.31.0', uptime_seconds: 4201,
version: '0.31.1', uptime_seconds: 4201,
runtime: {
platform: 'Linux', operating_system: 'Debian GNU/Linux 13', kernel_version: '6.12.0', architecture: 'x86_64',
statistics_source: 'systemstat+sysinfo', data_directory: '/var/lib/rustproxy', database_path: '/var/lib/rustproxy/config.sqlite3',
@@ -63,7 +63,7 @@ const context = {
vm.runInNewContext(code, context);
assert.equal(dollar('runtime-operating-system').textContent, 'Debian GNU/Linux 13');
assert.equal(dollar('runtime-kernel-version').textContent, '6.12.0');
assert.equal(dollar('runtime-version').textContent, '0.31.0');
assert.equal(dollar('runtime-version').textContent, '0.31.1');
assert.equal(dollar('runtime-logical-cpus').textContent, '8');
assert.equal(dollar('runtime-cpu-usage').textContent, '17.2%');
assert.equal(dollar('runtime-python').textContent, 'Available · 3.13.5');
@@ -124,4 +124,4 @@ for (const key of ['python_version','pip_version','certbot_version']) {
assert.ok(runtimeSchema.properties.certificate_runtime.properties[key]);
assert.equal(runtimeSchema.properties.certificate_runtime.properties[key].nullable, true);
}
console.log('PASS: 0.31.0 legend palette, About cleanup, system tab/navigation, read-only API schema and simulated status rendering.');
console.log('PASS: 0.31.1 legend palette, About cleanup, system tab/navigation, read-only API schema and simulated status rendering.');
+16 -5
View File
@@ -1,14 +1,25 @@
from pathlib import Path
APP_PARTS = ("core.js", "preferences.js", "config-history.js", "runtime.js", "settings.js")
APP_PARTS = ("core.js", "refresh.js", "preferences.js", "config-history.js", "runtime.js", "settings.js")
PANEL_PARTS = (
"management.js", "drafts.js", "integrations.js", "vhosts.js",
"web-server-profiles.js", "web-server.js", "docker.js", "streams.js",
"redirects.js", "host404.js", "traffic-panel.js", "cache-panel.js",
"transform-panel.js", "acme.js", "settings.js", "file-logs.js",
)
VHOST_PARTS = ("core.js", "health.js", "editor.js", "render.js")
def _read(root: Path, folder: str, parts: tuple[str, ...]) -> str:
return "\n".join((root / "src/admin/web" / folder / part).read_text(encoding="utf-8") for part in parts)
def read_app_js(root: Path) -> str:
return _read(root, "app", APP_PARTS)
def read_vhosts_js(root: Path) -> str:
body = _read(root, "vhosts", VHOST_PARTS)
return "(() => {\n" + body + "\n})();\n"
return "(() => {\n" + body + "\n})();"
def read_app_js(root: Path) -> str:
primary = _read(root, "app", APP_PARTS)
panel = "\n".join(
read_vhosts_js(root) if name == "vhosts.js" else (root / "src/admin/web" / name).read_text(encoding="utf-8")
for name in PANEL_PARTS
)
return primary + "\n" + panel