204 lines
7.4 KiB
Bash
Executable File
204 lines
7.4 KiB
Bash
Executable File
#!/bin/sh
|
|
# validate.sh — local pre-ship validation for the Vestibule release.
|
|
# Mirrors the CI checks that can run without Windows/flatpak/cargo.
|
|
#
|
|
# Usage: sh scripts/validate.sh (from anywhere inside the repo copy)
|
|
# Exit: 0 all pass, 1 failure
|
|
|
|
REPO_ROOT=$(cd "$(dirname "$0")/.." && pwd)
|
|
cd "${REPO_ROOT}"
|
|
|
|
FAILURES=0
|
|
say() { printf '%s\n' "$1"; }
|
|
pass() { say " [pass] $1"; }
|
|
fail() { FAILURES=$((FAILURES+1)); say " [FAIL] $1"; }
|
|
|
|
say "==> POSIX sh syntax"
|
|
for f in scripts/*.sh scripts/vestibule-kiosk-launch packaging/vestibule-flatpak-cli packaging/build-flatpak.sh; do
|
|
if sh -n "$f" 2>/dev/null; then pass "$f"; else fail "$f"; fi
|
|
done
|
|
|
|
say "==> Python syntax"
|
|
for f in scripts/*.py; do
|
|
if python3 -m py_compile "$f" 2>/dev/null; then pass "$f"; else fail "$f"; fi
|
|
done
|
|
|
|
say "==> JSON validity"
|
|
for f in extension/manifest.json config/policies.json config/com.vestibule.usher.linux.json \
|
|
config/com.vestibule.usher.windows.json packaging/net.dcos.Vestibule.json; do
|
|
if python3 -c "import json; json.load(open('$f'))" 2>/dev/null; then pass "$f"; else fail "$f"; fi
|
|
done
|
|
|
|
say "==> XML validity"
|
|
if python3 -c "import xml.dom.minidom; xml.dom.minidom.parse('packaging/net.dcos.Vestibule.metainfo.xml')" 2>/dev/null; then
|
|
pass "metainfo.xml"
|
|
else
|
|
fail "metainfo.xml"
|
|
fi
|
|
|
|
say "==> YAML validity (ci.yml)"
|
|
if python3 -c "import yaml; yaml.safe_load(open('.github/workflows/ci.yml'))" 2>/dev/null; then
|
|
pass "ci.yml"
|
|
else
|
|
fail "ci.yml"
|
|
fi
|
|
|
|
say "==> Version consistency (1.2.2)"
|
|
v_cargo=$(sed -n 's/^version = "\(.*\)"/\1/p' helper/Cargo.toml | head -1)
|
|
v_manifest=$(python3 -c "import json; print(json.load(open('extension/manifest.json'))['version'])")
|
|
v_metainfo=$(python3 -c "import re; print(re.search(r'release version=\"([^\"]+)\"', open('packaging/net.dcos.Vestibule.metainfo.xml').read()).group(1))")
|
|
v_iss=$(sed -n 's/^#define MyAppVersion "\(.*\)"/\1/p' packaging/vestibule.iss | head -1)
|
|
v_cli=$(sed -n 's/^VERSION="\(.*\)"/\1/p' packaging/vestibule-flatpak-cli | head -1)
|
|
for pair in "Cargo.toml:$v_cargo" "manifest.json:$v_manifest" "metainfo:$v_metainfo" "vestibule.iss:$v_iss" "flatpak-cli:$v_cli"; do
|
|
name=${pair%%:*}; val=${pair#*:}
|
|
if [ "$val" = "1.2.2" ]; then pass "$name = $val"; else fail "$name = $val (expected 1.2.2)"; fi
|
|
done
|
|
|
|
say "==> PowerShell sanity (structure checks)"
|
|
if python3 - <<'PYEOF'
|
|
import sys, re
|
|
|
|
files = [
|
|
"scripts/provision-kiosk.ps1",
|
|
"scripts/deprovision-kiosk.ps1",
|
|
"scripts/kiosk-launch.ps1",
|
|
"packaging/build-installer.ps1",
|
|
]
|
|
problems = []
|
|
for path in files:
|
|
src = open(path, encoding="utf-8").read()
|
|
lines = src.splitlines()
|
|
# Here-string terminators must start at column 0.
|
|
for i, ln in enumerate(lines, 1):
|
|
if re.match(r'^\s+@"|^\s+@\'', ln):
|
|
problems.append(f"{path}:{i} here-string opener must be at column 0")
|
|
# Brace/paren/bracket balance outside strings and comments (heuristic).
|
|
# Order matters: strip quoted strings FIRST (strings may contain '#'),
|
|
# then strip trailing comments (comments may contain quotes).
|
|
cleaned = []
|
|
in_herestring = False
|
|
for ln in lines:
|
|
if in_herestring:
|
|
if ln.startswith('"@') or ln.startswith("'@"):
|
|
in_herestring = False
|
|
continue
|
|
if ln.lstrip().startswith('@"') or ln.lstrip().startswith("@'"):
|
|
in_herestring = True
|
|
continue
|
|
no_strings = re.sub(r'"[^"]*"', '""', ln)
|
|
no_strings = re.sub(r"'[^']*'", "''", no_strings)
|
|
no_comment = re.sub(r'#.*$', '', no_strings)
|
|
cleaned.append(no_comment)
|
|
blob = "\n".join(cleaned)
|
|
for open_c, close_c in [("{", "}"), ("(", ")"), ("[", "]")]:
|
|
if blob.count(open_c) != blob.count(close_c):
|
|
problems.append(
|
|
f"{path}: unbalanced {open_c}{close_c} "
|
|
f"({blob.count(open_c)} vs {blob.count(close_c)})")
|
|
if problems:
|
|
print("\n".join(problems))
|
|
sys.exit(1)
|
|
print(f"{len(files)} files structurally consistent")
|
|
PYEOF
|
|
then
|
|
pass "PowerShell structural checks"
|
|
else
|
|
fail "PowerShell structural checks"
|
|
fi
|
|
|
|
say "==> URL policy unit tests (node)"
|
|
# The engine is pure JavaScript with no browser dependencies; Node runs
|
|
# the same file the background script loads. CI runs this gate on every
|
|
# push; on a node-less machine it is reported, not silently skipped.
|
|
if command -v node >/dev/null 2>&1; then
|
|
if node scripts/test-url-policy.js >/tmp/vestibule-urlpolicy.log 2>&1; then
|
|
pass "scripts/test-url-policy.js"
|
|
else
|
|
fail "scripts/test-url-policy.js"
|
|
tail -20 /tmp/vestibule-urlpolicy.log
|
|
fi
|
|
else
|
|
say " [warn] node not found — URL policy unit tests skipped (CI runs them)"
|
|
fi
|
|
|
|
say "==> XPI build smoke test"
|
|
tmp_xpi=$(mktemp -u).xpi
|
|
if python3 - "$REPO_ROOT/extension" "$tmp_xpi" <<'PYEOF'
|
|
import os, sys, zipfile
|
|
src_dir, dst = sys.argv[1], sys.argv[2]
|
|
with zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED) as z:
|
|
for root, dirs, files in os.walk(src_dir):
|
|
dirs.sort()
|
|
for f in sorted(files):
|
|
full = os.path.join(root, f)
|
|
z.write(full, os.path.relpath(full, src_dir))
|
|
with zipfile.ZipFile(dst) as z:
|
|
m = __import__("json").loads(z.read("manifest.json"))
|
|
assert m["version"] == "1.2.2"
|
|
assert m["browser_specific_settings"]["gecko"]["id"] == "vestibule@vestibule.kiosk"
|
|
assert m["background"]["scripts"][0] == "url-policy.js", "policy engine must load first"
|
|
for name in ["background.js", "url-policy.js", "content.js", "admin.html", "unlock.html", "blocked.html"]:
|
|
assert name in z.namelist(), name
|
|
PYEOF
|
|
then
|
|
pass "XPI builds; manifest + all entry points present"
|
|
else
|
|
fail "XPI build"
|
|
fi
|
|
rm -f "$tmp_xpi"
|
|
|
|
say "==> Icon assets"
|
|
for f in packaging/icons/vestibule.ico packaging/icons/vestibule.svg packaging/icons/vestibule-256.png; do
|
|
if [ -s "$f" ]; then pass "$f"; else fail "$f"; fi
|
|
done
|
|
|
|
say "==> Historic artifact scrub (history/)"
|
|
# The 2001 VB6 artifact and every other file in the tree must stay free
|
|
# of the scrubbed identifiers: the school initials, the employer names,
|
|
# the original unlock code, and the employer-branded project name.
|
|
if python3 - <<'PYEOF'
|
|
import os
|
|
import sys
|
|
|
|
FORBIDDEN = ("gl" "ths", # school initials
|
|
"advanced technical " "solutions", # employer name
|
|
"ats" "inc", # original unlock code
|
|
"ats" "browser", # employer-branded project name
|
|
"school" "version") # renamed form's old filename
|
|
hits = []
|
|
for root, dirs, files in os.walk("."):
|
|
dirs[:] = [d for d in dirs if d not in (".git", "__pycache__")]
|
|
for name in files:
|
|
path = os.path.join(root, name)
|
|
try:
|
|
blob = open(path, "rb").read().decode("utf-8", "ignore").lower()
|
|
except OSError:
|
|
continue
|
|
hits.extend(f"{path}: {token}" for token in FORBIDDEN if token in blob)
|
|
if hits:
|
|
print("\n".join(hits))
|
|
sys.exit(1)
|
|
PYEOF
|
|
then
|
|
pass "no scrub-list identifiers anywhere in the tree"
|
|
else
|
|
fail "scrubbed identifiers present (see list above)"
|
|
fi
|
|
|
|
say "==> Rootless provision/deprovision integration test"
|
|
if sh scripts/test-provision-linux.sh > /tmp/vestibule-inttest.log 2>&1; then
|
|
pass "3-scenario integration test (LibreWolf native + Firefox native + Firefox Flatpak)"
|
|
else
|
|
fail "integration test"
|
|
tail -40 /tmp/vestibule-inttest.log
|
|
fi
|
|
|
|
say ""
|
|
if [ "${FAILURES}" -eq 0 ]; then
|
|
say "OK — all local validation passed."
|
|
exit 0
|
|
else
|
|
say "FAIL: ${FAILURES} check(s) failed"
|
|
exit 1
|
|
fi
|