chore: prepare repository for public launch

This commit is contained in:
Dorian
2026-07-27 17:51:43 +01:00
parent c5eeb4392f
commit 0aee010f9c
26 changed files with 1470 additions and 509 deletions
+4 -4
View File
@@ -25,7 +25,7 @@ PATTERNS=(
)
# Allowed files (config templates, docs, test fixtures)
ALLOW_PATTERNS="test|mock|example|template|CLAUDE.md|deploy-config|\.md$|node_modules|dist|target|default\)|grep.*rpc|audit-secrets"
ALLOW_PATTERNS="test|e2e|mock|demo|example|Example|template|CLAUDE.md|deploy-config|\.md$|node_modules|dist|target|default\)|grep.*rpc|audit-secrets|startsWith|should start with"
main() {
log "=== Secrets Audit ==="
@@ -34,7 +34,7 @@ main() {
# 1. Check for .env files in version control
log "1. Checking for .env files in git..."
local env_files
env_files=$(cd "$REPO_ROOT" && git ls-files '*.env' '.env*' 2>/dev/null || echo "")
env_files=$(cd "$REPO_ROOT" && git ls-files | grep -E '(^|/)\.env($|[.])|(^|/)[^/]*\.env($|[.])' | grep -vE '(^|/)\.env\.example$|(^|/)[^/]*\.env\.example$' || echo "")
if [ -z "$env_files" ]; then
pass "No .env files tracked in git"
else
@@ -69,7 +69,7 @@ main() {
if [ -n "$matches" ]; then
# Filter out false positives (empty strings, variable declarations, etc.)
local real_matches
real_matches=$(echo "$matches" | grep -vE '""|\x27\x27|None|null|undefined|TODO|placeholder|example|Option<' || echo "")
real_matches=$(echo "$matches" | grep -vE '""|\x27\x27|None|null|undefined|TODO|placeholder|example|Option<|\$\{[A-Z0-9_]+:-\}|\$[A-Z0-9_]+|TestPassword|password123|entertoexit' || echo "")
if [ -n "$real_matches" ]; then
echo " WARNING: Pattern '$pattern' found:"
echo "$real_matches" | head -5 | sed 's/^/ /'
@@ -96,7 +96,7 @@ main() {
# 5. Check for credential files in repo
log "5. Checking for credential files..."
local cred_files
cred_files=$(cd "$REPO_ROOT" && git ls-files '*.pem' '*.key' '*macaroon*' 2>/dev/null | grep -v '\.rs$' | grep -v '\.ts$' || echo "")
cred_files=$(cd "$REPO_ROOT" && git ls-files | grep -Ei '(\.pem$|\.key$|\.p12$|\.pfx$|\.jks$|\.keystore$|id_rsa|id_ed25519|macaroon)' | grep -vE '\.(rs|ts)$' || echo "")
if [ -z "$cred_files" ]; then
pass "No credential files tracked in git"
else
+28 -30
View File
@@ -1,12 +1,15 @@
#!/usr/bin/env bash
#
# Run all Archipelago tests: frontend (local) + backend (dev server via SSH).
# Exit 0 only if both pass.
# Run Archipelago tests.
#
# By default this runs frontend tests and local backend Rust tests. Set
# ARCHIPELAGO_SSH_HOST and ARCHIPELAGO_SSH_KEY to run backend tests on a Linux
# target instead.
#
set -euo pipefail
SSH_KEY="${ARCHIPELAGO_SSH_KEY:-$HOME/.ssh/archipelago-deploy}"
SSH_HOST="${ARCHIPELAGO_SSH_HOST:-archipelago@192.168.1.228}"
SSH_KEY="${ARCHIPELAGO_SSH_KEY:-}"
SSH_HOST="${ARCHIPELAGO_SSH_HOST:-}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
@@ -29,34 +32,29 @@ fi
echo ""
# --- Backend Tests (on dev server) ---
echo "--- Backend Tests (dev server) ---"
# --- Backend Tests ---
if [[ -n "$SSH_HOST" && -n "$SSH_KEY" ]]; then
echo "--- Backend Tests (Linux target: $SSH_HOST) ---"
echo "Syncing source to target..."
rsync -az --exclude 'target' --exclude 'node_modules' --exclude '.git' \
-e "ssh -i $SSH_KEY" \
"$PROJECT_DIR/core/" "$SSH_HOST:~/archy/core/" 2>&1
# Sync source to server
echo "Syncing source to dev server..."
rsync -az --exclude 'target' --exclude 'node_modules' --exclude '.git' \
-e "ssh -i $SSH_KEY" \
"$PROJECT_DIR/core/" "$SSH_HOST:~/archy/core/" 2>&1
# Run tests on server
if ssh -i "$SSH_KEY" "$SSH_HOST" \
"source ~/.cargo/env && cd ~/archy/core && cargo test -p archipelago 2>&1"; then
echo "✅ Backend unit tests PASSED"
BACKEND_OK=1
if ssh -i "$SSH_KEY" "$SSH_HOST" \
"source ~/.cargo/env && cd ~/archy/core && cargo test --all-features 2>&1"; then
echo "✅ Backend tests PASSED"
BACKEND_OK=1
else
echo "❌ Backend tests FAILED"
fi
else
echo " Backend unit tests FAILED"
fi
echo ""
# --- Integration Tests ---
echo "--- Integration Tests (dev server) ---"
if ssh -i "$SSH_KEY" "$SSH_HOST" \
"source ~/.cargo/env && cd ~/archy/core && cargo test --test rpc_integration 2>&1"; then
echo "✅ Integration tests PASSED"
else
echo "❌ Integration tests FAILED"
BACKEND_OK=0
echo "--- Backend Tests (local) ---"
if (cd "$PROJECT_DIR/core" && cargo test --all-features 2>&1); then
echo "✅ Backend tests PASSED"
BACKEND_OK=1
else
echo "❌ Backend tests FAILED"
fi
fi
echo ""
+9 -2
View File
@@ -24,8 +24,9 @@ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
SSH_KEY="${ARCHIPELAGO_SSH_KEY:-$HOME/.ssh/archipelago-deploy}"
SSH_OPTS="-o StrictHostKeyChecking=no -i $SSH_KEY"
# Anthropic API key used by all servers for AIUI Claude chat
ANTHROPIC_API_KEY="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"
# Anthropic API key used by the AIUI Claude chat proxy. Keep this in the
# caller's environment or scripts/deploy-config.sh; never commit live keys.
ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY:-}"
TARGET_HOST="$1"
if [ -z "$TARGET_HOST" ]; then
@@ -34,6 +35,12 @@ if [ -z "$TARGET_HOST" ]; then
exit 1
fi
if [ -z "$ANTHROPIC_API_KEY" ]; then
echo "ERROR: ANTHROPIC_API_KEY must be set in the environment."
echo "Example: ANTHROPIC_API_KEY=<key> $0 $TARGET_HOST"
exit 1
fi
AIUI_DIST="$PROJECT_DIR/../AIUI/packages/app/dist"
if [ ! -f "$AIUI_DIST/index.html" ]; then
echo "ERROR: AIUI build not found at $AIUI_DIST"
+201 -103
View File
@@ -1,25 +1,26 @@
#!/usr/bin/env bash
#
# validate-app-manifest.sh — Validate a community-submitted app manifest
# validate-app-manifest.sh - validate an Archipelago app manifest.
#
# Usage: ./scripts/validate-app-manifest.sh <manifest.yml>
# Usage:
# ./scripts/validate-app-manifest.sh [--repo-audit] apps/my-app/manifest.yml
#
# Checks:
# 1. Valid YAML syntax
# 2. Required fields present (id, title, version, image, description)
# 3. Image from trusted registry (docker.io, ghcr.io, quay.io)
# 4. No :latest tag (must pin specific version)
# 5. Resource limits specified (memory, cpu)
# 6. Security: no privileged mode, no host networking
# 7. No hardcoded secrets/passwords in environment
# 8. Port conflicts with existing apps
#
# Exit 0 = valid, Exit 1 = issues found
# This intentionally mirrors the public app contract documented in
# docs/app-manifest-spec.md: manifests have a top-level `app:` block and are
# ultimately validated by the Rust parser in core/container/src/manifest.rs.
# This script is the contributor-friendly preflight; the Rust parser remains
# canonical.
set -euo pipefail
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <manifest.yml>"
REPO_AUDIT=0
if [[ "${1:-}" == "--repo-audit" ]]; then
REPO_AUDIT=1
shift
fi
if [[ $# -ne 1 ]]; then
echo "Usage: $0 [--repo-audit] <manifest.yml>"
exit 1
fi
@@ -30,138 +31,235 @@ WARN=0
check() {
local desc="$1" result="$2"
if [[ "$result" == "pass" ]]; then
PASS=$((PASS + 1))
echo " PASS: $desc"
elif [[ "$result" == "warn" ]]; then
WARN=$((WARN + 1))
echo " WARN: $desc"
else
FAIL=$((FAIL + 1))
echo " FAIL: $desc"
fi
case "$result" in
pass)
PASS=$((PASS + 1))
echo " PASS: $desc"
;;
warn)
WARN=$((WARN + 1))
echo " WARN: $desc"
;;
*)
FAIL=$((FAIL + 1))
echo " FAIL: $desc"
;;
esac
}
yaml_eval() {
ruby -ryaml -e '
path, expr = ARGV
data = YAML.load_file(path)
app = data.is_a?(Hash) ? data["app"] : nil
abort "missing top-level app block" unless app.is_a?(Hash)
value = eval(expr)
case value
when Array
puts value.join("\n")
when Hash
puts value.to_a.map { |k, v| "#{k}=#{v}" }.join("\n")
when NilClass
puts ""
else
puts value
end
' "$MANIFEST" "$1"
}
echo "Validating: $MANIFEST"
echo ""
# 1. File exists and is readable
if [[ ! -f "$MANIFEST" ]]; then
echo " FAIL: File not found: $MANIFEST"
exit 1
fi
check "File exists" "pass"
# 2. Valid YAML
if ! python3 -c "import yaml; yaml.safe_load(open('$MANIFEST'))" 2>/dev/null; then
check "Valid YAML syntax" "fail"
echo " Cannot continue with invalid YAML"
if ! ruby -ryaml -e 'data = YAML.load_file(ARGV[0]); exit(data.is_a?(Hash) && data["app"].is_a?(Hash) ? 0 : 1)' "$MANIFEST" 2>/dev/null; then
check "Valid YAML with top-level app block" "fail"
echo ""
echo "Results: $PASS passed, $FAIL failed, $WARN warnings"
echo "STATUS: REJECTED - fix failures before resubmitting"
exit 1
fi
check "Valid YAML syntax" "pass"
check "Valid YAML with top-level app block" "pass"
# 3. Required fields
CONTENT=$(python3 -c "
import yaml, json
with open('$MANIFEST') as f:
d = yaml.safe_load(f)
print(json.dumps(d))
" 2>/dev/null)
APP_ID="$(yaml_eval 'app["id"]')"
APP_NAME="$(yaml_eval 'app["name"]')"
APP_VERSION="$(yaml_eval 'app["version"]')"
APP_DESCRIPTION="$(yaml_eval 'app["description"]')"
APP_INTERNAL="$(yaml_eval 'app["internal"]')"
IMAGE="$(yaml_eval '(app["container"] || {})["image"]')"
BUILD_CONTEXT="$(yaml_eval '(((app["container"] || {})["build"] || {})["context"])')"
BUILD_TAG="$(yaml_eval '(((app["container"] || {})["build"] || {})["tag"])')"
for field in id title version description; do
val=$(echo "$CONTENT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$field',''))" 2>/dev/null)
if [[ -n "$val" && "$val" != "None" ]]; then
check "Required field '$field' present" "pass"
else
check "Required field '$field' present" "fail"
fi
done
# 4. Image reference
IMAGE=$(echo "$CONTENT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('image','') or d.get('docker_image','') or '')" 2>/dev/null)
if [[ -z "$IMAGE" || "$IMAGE" == "None" ]]; then
check "Container image specified" "fail"
if [[ "$APP_ID" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; then
check "app.id is lowercase kebab-case ($APP_ID)" "pass"
else
check "Container image specified" "pass"
check "app.id is lowercase kebab-case" "fail"
fi
# Check trusted registry
if [[ -n "$APP_NAME" ]]; then
check "app.name present" "pass"
else
check "app.name present" "fail"
fi
if [[ "$APP_VERSION" =~ [0-9] ]]; then
check "app.version present and contains a digit" "pass"
else
check "app.version present and contains a digit" "fail"
fi
if [[ -n "$APP_DESCRIPTION" ]]; then
check "app.description present" "pass"
else
check "app.description present" "warn"
fi
HAS_IMAGE=0
HAS_BUILD=0
[[ -n "$IMAGE" ]] && HAS_IMAGE=1
[[ -n "$BUILD_CONTEXT" || -n "$BUILD_TAG" ]] && HAS_BUILD=1
if [[ "$HAS_IMAGE" -eq 1 && "$HAS_BUILD" -eq 0 ]]; then
check "container.image specified" "pass"
elif [[ "$HAS_IMAGE" -eq 0 && "$HAS_BUILD" -eq 1 ]]; then
if [[ -n "$BUILD_CONTEXT" && -n "$BUILD_TAG" ]]; then
check "container.build specified with context and tag" "pass"
else
check "container.build requires context and tag" "fail"
fi
else
check "exactly one of container.image or container.build specified" "fail"
fi
if [[ -n "$IMAGE" ]]; then
TRUSTED=false
for reg in "docker.io" "ghcr.io" "quay.io" "registry.hub.docker.com" "146.59.87.168:3000"; do
if echo "$IMAGE" | grep -q "$reg"; then
for reg in "docker.io" "ghcr.io" "quay.io" "registry.hub.docker.com" "146.59.87.168:3000" "localhost/"; do
if [[ "$IMAGE" == *"$reg"* ]]; then
TRUSTED=true
break
fi
done
# Also allow short-form Docker Hub images (no registry prefix)
if ! echo "$IMAGE" | grep -q "/"; then
TRUSTED=true # single-name images are Docker Hub official
fi
if [[ "$TRUSTED" == "true" ]]; then
check "Image from trusted registry" "pass"
if [[ "$TRUSTED" == "true" || "$IMAGE" != */* ]]; then
check "image registry is recognized" "pass"
else
check "Image from trusted registry ($IMAGE)" "warn"
check "image registry is not in the reviewed list ($IMAGE)" "warn"
fi
# Check no :latest
if echo "$IMAGE" | grep -q ":latest$"; then
check "No :latest tag (pin specific version)" "fail"
elif ! echo "$IMAGE" | grep -q ":"; then
check "No version tag specified (should pin version)" "warn"
if [[ "$IMAGE" == *":latest" ]]; then
if [[ "$APP_INTERNAL" == "true" || "$IMAGE" == localhost/* ]]; then
check "internal/local build uses :latest ($IMAGE)" "warn"
elif [[ "$REPO_AUDIT" -eq 1 ]]; then
check "existing manifest uses :latest and must be pinned before public app submission ($IMAGE)" "warn"
else
check "image tag is pinned and not :latest ($IMAGE)" "fail"
fi
elif [[ "$IMAGE" != *:* ]]; then
check "image tag is explicit ($IMAGE)" "warn"
else
check "Version tag pinned" "pass"
check "image tag is pinned" "pass"
fi
fi
# 5. Security checks
PRIVILEGED=$(echo "$CONTENT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('privileged', False))" 2>/dev/null)
if [[ "$PRIVILEGED" == "True" ]]; then
check "No privileged mode" "fail"
MEMORY_LIMIT="$(yaml_eval '((app["resources"] || {})["memory_limit"] || (app["resources"] || {})["memory"])')"
CPU_LIMIT="$(yaml_eval '((app["resources"] || {})["cpu_limit"] || (app["resources"] || {})["cpu"])')"
[[ -n "$MEMORY_LIMIT" ]] && check "resources.memory_limit specified ($MEMORY_LIMIT)" "pass" || check "resources.memory_limit specified" "warn"
[[ -n "$CPU_LIMIT" ]] && check "resources.cpu_limit specified ($CPU_LIMIT)" "pass" || check "resources.cpu_limit specified" "warn"
READONLY_ROOT="$(yaml_eval '((app["security"] || {})["readonly_root"])')"
NO_NEW_PRIVS="$(yaml_eval '((app["security"] || {})["no_new_privileges"])')"
NETWORK_POLICY="$(yaml_eval '((app["security"] || {})["network_policy"])')"
CONTAINER_NETWORK="$(yaml_eval '((app["container"] || {})["network"])')"
if [[ "$READONLY_ROOT" == "true" || -z "$READONLY_ROOT" ]]; then
check "security.readonly_root true (explicit or Rust default)" "pass"
else
check "No privileged mode" "pass"
check "security.readonly_root true or explicitly justified" "warn"
fi
HOST_NET=$(echo "$CONTENT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('host_network', d.get('network_mode','')))" 2>/dev/null)
if [[ "$HOST_NET" == "host" ]]; then
check "No host networking" "fail"
if [[ "$NO_NEW_PRIVS" == "true" || -z "$NO_NEW_PRIVS" ]]; then
check "security.no_new_privileges true (explicit or Rust default)" "pass"
elif [[ "$REPO_AUDIT" -eq 1 ]]; then
check "existing manifest disables security.no_new_privileges and needs review" "warn"
else
check "No host networking" "pass"
check "security.no_new_privileges true" "fail"
fi
# 6. Check for hardcoded secrets in env vars
ENV_VARS=$(echo "$CONTENT" | python3 -c "
import sys,json
d=json.load(sys.stdin)
env = d.get('environment', d.get('env', {}))
if isinstance(env, dict):
for k,v in env.items():
print(f'{k}={v}')
elif isinstance(env, list):
for e in env:
print(e)
" 2>/dev/null || echo "")
SECRET_PATTERNS="password|secret|api_key|private_key|token"
if echo "$ENV_VARS" | grep -iqE "$SECRET_PATTERNS"; then
check "No hardcoded secrets in environment" "warn"
if [[ "$NETWORK_POLICY" == "isolated" || "$NETWORK_POLICY" == "bridge" || "$NETWORK_POLICY" == "host" || -z "$NETWORK_POLICY" ]]; then
check "security.network_policy valid" "pass"
else
check "No hardcoded secrets in environment" "pass"
check "security.network_policy valid" "fail"
fi
# 7. Memory limit
MEM=$(echo "$CONTENT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('memory', d.get('mem_limit', d.get('resources',{}).get('memory',''))))" 2>/dev/null)
if [[ -n "$MEM" && "$MEM" != "None" && "$MEM" != "" ]]; then
check "Memory limit specified ($MEM)" "pass"
if [[ "$CONTAINER_NETWORK" == container:* || "$CONTAINER_NETWORK" == ns:* ]]; then
check "container.network does not share another namespace" "fail"
else
check "Memory limit specified" "warn"
check "container.network does not share another namespace" "pass"
fi
SECRET_ENV="$(yaml_eval '(app["environment"] || [])')"
if echo "$SECRET_ENV" | grep -iqE '^[A-Z0-9_]*(PASSWORD|PASS|SECRET|TOKEN|API_KEY|PRIVATE_KEY)[A-Z0-9_]*=.+$'; then
check "no hardcoded secret-like values in app.environment" "warn"
else
check "no hardcoded secret-like values in app.environment" "pass"
fi
if [[ -n "$APP_ID" && -n "$MANIFEST" ]]; then
EXPECTED_DIR="$(basename "$(dirname "$MANIFEST")")"
if [[ "$EXPECTED_DIR" == "$APP_ID" ]]; then
check "app.id matches directory name" "pass"
elif [[ "$REPO_AUDIT" -eq 1 ]]; then
check "existing manifest app.id differs from directory name ($EXPECTED_DIR)" "warn"
else
check "app.id matches directory name ($EXPECTED_DIR)" "fail"
fi
fi
PORT_CHECK="$(ruby -ryaml -e '
current = ARGV[0]
current_id = File.basename(File.dirname(current))
ports = {}
Dir.glob("apps/*/manifest.yml").sort.each do |path|
data = YAML.load_file(path)
app = data.is_a?(Hash) ? data["app"] : nil
next unless app.is_a?(Hash)
id = app["id"] || File.basename(File.dirname(path))
next if id == current_id
Array(app["ports"]).each do |p|
next unless p.is_a?(Hash)
proto = p["protocol"] || "tcp"
bind = p["bind"] || ""
host = p["host"]
ports[[host, proto, bind]] = id if host
end
end
data = YAML.load_file(current)
app = data["app"]
conflicts = []
Array(app["ports"]).each do |p|
next unless p.is_a?(Hash)
key = [p["host"], p["protocol"] || "tcp", p["bind"] || ""]
conflicts << "#{key[2].empty? ? "*" : key[2]}:#{key[0]}/#{key[1]} already used by #{ports[key]}" if ports.key?(key)
end
puts conflicts.join("\n")
' "$MANIFEST")"
if [[ -n "$PORT_CHECK" ]]; then
while IFS= read -r conflict; do
check "port conflict: $conflict" "warn"
done <<< "$PORT_CHECK"
else
check "no duplicate host port bindings" "pass"
fi
echo ""
echo "Results: $PASS passed, $FAIL failed, $WARN warnings"
if [[ "$FAIL" -gt 0 ]]; then
echo "STATUS: REJECTED fix failures before resubmitting"
echo "STATUS: REJECTED - fix failures before resubmitting"
exit 1
else
echo "STATUS: APPROVED (with $WARN warnings)"
exit 0
fi
echo "STATUS: APPROVED (with $WARN warnings)"