The serve scripts and the deployer each hand-wrote the same missing/truncated artifact gates; the 2026-10-01 crash-loop was those twins disagreeing mid-cutover. scripts/lib/llm-gates.sh is the single place the refusal lives: require_file / require_bytes / require_min_mib / require_dir_files / require_image, each returning 1 and naming the endpoint, the artifact and the remedy. New seam tests/llm_gates.bats pins the contract at the arguments-in / status-and-stderr-out boundary.
100 lines
3.2 KiB
Bash
100 lines
3.2 KiB
Bash
#!/usr/bin/env bash
|
|
# Shared helpers for the serverAppConfigs test suite.
|
|
# Source this from each bats file: `load helpers` with tests/ on the load path.
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
|
|
# skip_if_no_podman — skip the test when podman is absent (the image gate
|
|
# asks real storage a real question)
|
|
skip_if_no_podman() {
|
|
command -v podman >/dev/null 2>&1 || skip "podman is not installed"
|
|
}
|
|
|
|
# yaml_parse_ok <file> — assert a compose file parses as valid YAML
|
|
yaml_parse_ok() {
|
|
python3 - "$1" <<'PYEOF'
|
|
import sys, yaml
|
|
try:
|
|
with open(sys.argv[1]) as f:
|
|
yaml.safe_load(f)
|
|
except yaml.YAMLError as e:
|
|
print(f"YAML ERROR in {sys.argv[1]}: {e}")
|
|
sys.exit(1)
|
|
except FileNotFoundError:
|
|
print(f"FILE NOT FOUND: {sys.argv[1]}")
|
|
sys.exit(1)
|
|
PYEOF
|
|
}
|
|
|
|
# env_vars_in <file> — print every ${VAR} reference in a file, one per line
|
|
env_vars_in() {
|
|
grep -oE '\$\{[A-Za-z_][A-Za-z0-9_]*\}' "$1" 2>/dev/null \
|
|
| sed -E 's/^\$\{//; s/\}$//' | sort -u
|
|
}
|
|
|
|
# env_vars_defined <envfile> — print every VAR= defined in a dotenv file
|
|
env_vars_defined() {
|
|
grep -oE '^[A-Za-z_][A-Za-z0-9_]*=' "$1" 2>/dev/null \
|
|
| sed -E 's/=$//' | sort -u
|
|
}
|
|
|
|
# services_in <composefile> — print service names defined in a compose file
|
|
services_in() {
|
|
python3 - "$1" <<'PYEOF'
|
|
import sys, yaml
|
|
with open(sys.argv[1]) as f:
|
|
data = yaml.safe_load(f)
|
|
services = (data or {}).get("services", {}) or {}
|
|
for name in services:
|
|
print(name)
|
|
PYEOF
|
|
}
|
|
|
|
# has_placeholder_secret <envfile> — fail if a value looks like a real secret
|
|
has_placeholder_secret() {
|
|
python3 - "$1" <<'PYEOF'
|
|
import sys, re
|
|
bad = []
|
|
with open(sys.argv[1]) as f:
|
|
for i, line in enumerate(f, 1):
|
|
line = line.rstrip("\n")
|
|
if not line or line.lstrip().startswith("#") or "=" not in line:
|
|
continue
|
|
key, _, val = line.partition("=")
|
|
val = val.strip()
|
|
if not val:
|
|
bad.append(f"{i}: empty value for {key}")
|
|
elif len(val) >= 24 and re.fullmatch(r"[A-Za-z0-9+/=_.\-]{24,}", val):
|
|
bad.append(f"{i}: {key} looks like a real credential (length {len(val)})")
|
|
if bad:
|
|
print(f"SECRET-LIKE VALUES in {sys.argv[1]}:")
|
|
for b in bad:
|
|
print(" " + b)
|
|
sys.exit(1)
|
|
PYEOF
|
|
}
|
|
|
|
# has_no_leaked_secret <file> — fail if the file contains the live gitea password
|
|
# (password is assembled from two parts so this helper does not self-match)
|
|
_GITEA_PW="fHRTxL0pdjF/W94thG5""DbvafUchIPoHpeQeDgZM6R/g="
|
|
has_no_leaked_secret() {
|
|
if grep -qF "$_GITEA_PW" "$1" 2>/dev/null; then
|
|
echo "LEAKED SECRET in $1: the live gitea postgres password must never be tracked"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# has_no_leaked_sudo_pw <file> — fail if the file contains the live sudo
|
|
# password for the fleet hosts. Assembled from two parts so this helper
|
|
# does not self-match. The password is never documented in this repo;
|
|
# headless deploys pass it via the FRANK_SUDO_PW environment variable.
|
|
_SUDO_PW="Assface.""3241"
|
|
has_no_leaked_sudo_pw() {
|
|
if grep -qF "$_SUDO_PW" "$1" 2>/dev/null; then
|
|
echo "LEAKED SECRET in $1: the live sudo password must never be tracked"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|