v0.2.3
RustBox CI / rustbox (push) Canceled after 0s

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-31 19:40:19 +01:00
1 parent 5b22add1f3
commit 2c98b8f925
38 files changed
+915 -87

No files matched your search

+45 -13
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Static/offline release checks for RustBox 0.2.2.
"""Static/offline release checks for RustBox 0.2.3.
These checks intentionally avoid pretending to replace `cargo test`: they validate
configuration, SQL migrations, Docker examples, query-catalog parity and release
@@ -49,7 +49,7 @@ def extract_section(text: str, begin: str, end: str) -> str:
# Release/toolchain metadata.
cargo = tomllib.loads(read("Cargo.toml"))
require(cargo["package"]["version"] == "0.2.2", "Cargo package version is 0.2.2")
require(cargo["package"]["version"] == "0.2.3", "Cargo package version is 0.2.3")
require(cargo["package"]["rust-version"] == "1.88", "Cargo rust-version is 1.88")
toolchain = tomllib.loads(read("rust-toolchain.toml"))
require(toolchain["toolchain"]["channel"] == "1.88.0", "rust-toolchain pins Rust 1.88.0")
@@ -65,15 +65,26 @@ require("make_zip.py" in package_script and "sha256sum" in package_script, "rele
require("cargo generate-lockfile" in read(".github/workflows/ci.yml"), "CI regenerates Cargo.lock before --locked checks")
require("validate-release-static.py" in read("scripts/build-release.sh"), "release script runs offline/static release gate")
require("validate-release-static.py" in read(".github/workflows/ci.yml"), "CI runs offline/static release gate")
require(read("README.md").startswith("# RustBox 0.2.2"), "README identifies RustBox 0.2.2")
require(read("RELEASE-MANIFEST.txt").startswith("RustBox 0.2.2"), "release manifest identifies RustBox 0.2.2")
require(read("RELEASE-VALIDATION.txt").startswith("RustBox 0.2.2"), "release validation identifies RustBox 0.2.2")
require(read("docs/IMPLEMENTATION_STATUS.md").startswith("# RustBox 0.2.2"), "implementation status identifies RustBox 0.2.2")
require(read("README.md").startswith("# RustBox 0.2.3"), "README identifies RustBox 0.2.3")
require(read("RELEASE-MANIFEST.txt").startswith("RustBox 0.2.3"), "release manifest identifies RustBox 0.2.3")
require(read("RELEASE-VALIDATION.txt").startswith("RustBox 0.2.3"), "release validation identifies RustBox 0.2.3")
require(read("docs/IMPLEMENTATION_STATUS.md").startswith("# RustBox 0.2.3"), "implementation status identifies RustBox 0.2.3")
require((ROOT / "docs/STORAGE_SEARCH_OPERATIONS.md").is_file(), "storage/search operations guide exists")
require('imap_host = ""' not in read("config.dev.toml"), "development config does not override IMAP host with an invalid empty value")
require(not re.search(r"(?m)^IMAP_HOST=\s*$", read(".env.example")), "environment example does not set an invalid empty IMAP host")
require("pgp_enabled: bool" in read("src/routes/auth.rs"), "auth policy exposes OpenPGP availability")
require("setOpenPgpAvailability" in read("src/web/js/72-openpgp-identities.js"), "UI hides administratively disabled OpenPGP")
config_rs = read("src/config.rs")
routes_mod = read("src/routes/mod.rs")
require("pub sieve_enabled: bool" in config_rs and "pub diagnostics_enabled: bool" in config_rs and "pub about_enabled: bool" in config_rs, "configuration exposes Sieve, diagnostics and About feature gates")
require("pub security_headers_enabled: bool" in config_rs and "security_headers_enabled: false" in config_rs, "security headers are configurable and disabled by default")
require("if config.sieve_enabled" in routes_mod and "if config.diagnostics_enabled" in routes_mod, "disabled Sieve and diagnostics routes are not registered")
require(".fallback(|| async { axum::http::StatusCode::NOT_FOUND })" in routes_mod, "API namespace has an explicit 404 fallback before the SPA fallback")
require("if config.security_headers_enabled" in routes_mod and "add_security_headers" in routes_mod, "security-header middleware is opt-in")
require('.route("/settings/reset", post(settings::reset_all_settings))' in routes_mod, "authenticated settings reset route is registered")
require("db::settings::reset_all" in read("src/routes/settings.rs"), "settings reset route delegates preference reset to the DB layer")
require('$("#quick-filter-btn")?.classList.toggle("hidden", state.authPolicy.sieveEnabled === false)' in read("src/web/js/20-preferences-auth.js"), "Sieve quick-filter UI disappears when Sieve is disabled")
require("state.authPolicy.sieveEnabled === false ? []" in read("src/web/js/60-mail-actions-search.js"), "Sieve context-menu actions disappear when Sieve is disabled")
require("is_opensearch_backpressure_error" in read("src/search/engine.rs"), "OpenSearch worker classifies backpressure failures")
require("worker pass failed; backing off" in read("src/search/engine.rs"), "OpenSearch worker backs off after failures")
require("ENVIRONMENT: ${ENVIRONMENT:-development}" in read("docker/compose-postgres-s3-opensearch.yml"), "PostgreSQL Compose defaults direct HTTP to development cookie mode")
@@ -160,6 +171,7 @@ toml_files = [
"docker/configs/single-sqlite.toml",
"docker/configs/postgres-s3-opensearch.toml",
"docker/configs/mysql-nfs-opensearch.toml",
"docker/configs/ha-garage.toml",
]
for rel in toml_files:
try:
@@ -170,7 +182,7 @@ for rel in toml_files:
# Parse Docker Compose YAML and validate referenced config paths.
compose_files = sorted((ROOT / "docker").glob("compose-*.yml"))
require(len(compose_files) >= 3, "three Docker Compose deployment variants exist")
require(len(compose_files) >= 4, "four Docker Compose deployment variants exist")
if yaml is None:
errors.append("PyYAML is required for Docker Compose static validation")
else:
@@ -178,11 +190,14 @@ else:
try:
data = yaml.safe_load(path.read_text(encoding="utf-8"))
require(isinstance(data, dict) and "services" in data, f"Compose parses with services: {path.name}")
rustbox = data.get("services", {}).get("rustbox", {})
for volume in rustbox.get("volumes", []) or []:
if isinstance(volume, str) and volume.startswith("./configs/"):
config_rel = volume.split(":", 1)[0].removeprefix("./")
require((ROOT / "docker" / config_rel).is_file(), f"Compose config mount exists: {path.name} -> {config_rel}")
services = data.get("services", {})
for service_name, service in services.items():
if service_name != "rustbox" and not service_name.startswith("rustbox-"):
continue
for volume in service.get("volumes", []) or []:
if isinstance(volume, str) and volume.startswith("./configs/"):
config_rel = volume.split(":", 1)[0].removeprefix("./")
require((ROOT / "docker" / config_rel).is_file(), f"Compose config mount exists: {path.name}/{service_name} -> {config_rel}")
except Exception as exc:
errors.append(f"Compose parse failed: {path.name}: {exc}")
@@ -195,6 +210,23 @@ require(pg_s3["storage"]["backend"] == "s3" and pg_s3["search"]["backend"] == "o
require(my_nfs["cluster_mode"] is True and "data_dir" not in my_nfs, "MySQL/NFS cluster config omits data_dir")
require(my_nfs["storage"]["backend"] == "filesystem" and my_nfs["storage"]["shared"] is True, "MySQL cluster marks POSIX/NFS storage shared")
require(my_nfs["search"]["backend"] == "opensearch", "MySQL/NFS cluster uses OpenSearch")
ha = tomllib.loads(read("docker/configs/ha-garage.toml"))
require(ha["cluster_mode"] is True and ha["session_backend"] == "redis", "HA/Garage config enables cluster mode with Redis-compatible sessions")
require(ha["redis_url"] == "redis://valkey:6379/", "HA/Garage config targets Valkey for shared sessions")
require(ha["storage"]["backend"] == "s3" and ha["storage"]["endpoint"] == "http://garage:3900", "HA/Garage config stores blobs in Garage S3")
require(ha["search"]["backend"] == "opensearch", "HA/Garage config uses OpenSearch")
ha_compose = read("docker/compose-ha-garage.yml")
require("rustbox-1:" in ha_compose and "rustbox-2:" in ha_compose, "HA Compose defines two RustBox application nodes")
for service in ["postgres:", "valkey:", "opensearch:", "garage:", "envoy:"]:
require(service in ha_compose, f"HA Compose includes {service.removesuffix(':')}")
require("valkey/valkey:9.1.3-alpine" in ha_compose, "HA Compose pins Valkey 9.1.3")
require("opensearchproject/opensearch:3.9.0" in ha_compose, "HA Compose pins OpenSearch 3.9.0")
require("dxflrs/garage:v2.5.0" in ha_compose, "HA Compose pins Garage 2.5.0")
require("envoyproxy/envoy:v1.40.2" in ha_compose, "HA Compose pins Envoy 1.40.2")
require("X-RustBox-Backend" in read("docker/envoy/envoy.yaml"), "Envoy exposes the selected RustBox backend response header")
require("address: rustbox-1" in read("docker/envoy/envoy.yaml") and "address: rustbox-2" in read("docker/envoy/envoy.yaml"), "Envoy balances both RustBox nodes")
require('sieve_enabled = false' in read("docker/configs/ha-garage.toml"), "HA example disables optional Sieve by default")
require('security_headers_enabled = false' in read("docker/configs/ha-garage.toml"), "HA example keeps optional security headers disabled by default")
require("type: nfs" in read("docker/compose-mysql-nfs-opensearch.yml"), "NFS Compose uses a real NFS volume driver")
require("docker.io/pgsty/silo:RELEASE.2026-10-26T00-00-00Z" in read("docker/compose-postgres-s3-opensearch.yml"), "S3 Compose pins the maintained SILO integration image")
require("docker.io/pgsty/mc:RELEASE.2026-10-26T00-00-00Z" in read("docker/compose-postgres-s3-opensearch.yml"), "S3 Compose pins the matching SILO client image")
@@ -455,7 +487,7 @@ require(not missing_includes, f"Rust include_str/include_bytes targets exist ({'
# Shell syntax checks are useful offline and deterministic.
try:
shell_files = [ROOT / "dev.sh", *sorted((ROOT / "scripts").glob("*.sh"))]
shell_files = [ROOT / "dev.sh", *sorted((ROOT / "scripts").glob("*.sh")), ROOT / "docker/garage/init/init.sh"]
subprocess.run(["bash", "-n", *map(str, shell_files)], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
ok("shell scripts pass bash -n")
except Exception as exc: