ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto
16
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(docker): add secrets generator and smoke test scripts

byarielm.fyi 3fce9309 6b52bee1

verified
+248
+216
scripts/docker-smoke-test.sh
··· 1 + #!/bin/bash 2 + # ═══════════════════════════════════════════════════════ 3 + # ATlast Docker Stack Smoke Test 4 + # Validates the full stack is running and responding. 5 + # Usage: bash scripts/docker-smoke-test.sh 6 + # ═══════════════════════════════════════════════════════ 7 + 8 + set -euo pipefail 9 + 10 + # ── Configuration ────────────────────────────────────── 11 + COMPOSE_FILE="docker/docker-compose.yml" 12 + BASE_URL="http://localhost" 13 + MAX_WAIT_SECONDS=90 14 + POLL_INTERVAL=5 15 + 16 + # ── Color output ─────────────────────────────────────── 17 + RED='\033[0;31m' 18 + GREEN='\033[0;32m' 19 + YELLOW='\033[1;33m' 20 + NC='\033[0m' # No Color 21 + 22 + PASS=0 23 + FAIL=0 24 + 25 + # ── Helper functions ─────────────────────────────────── 26 + 27 + log_info() { echo -e "${YELLOW}[INFO]${NC} $1"; } 28 + log_pass() { echo -e "${GREEN}[PASS]${NC} $1"; PASS=$((PASS + 1)); } 29 + log_fail() { echo -e "${RED}[FAIL]${NC} $1"; FAIL=$((FAIL + 1)); } 30 + 31 + check_status() { 32 + local description="$1" 33 + local url="$2" 34 + local expected_status="$3" 35 + 36 + local actual_status 37 + actual_status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$url" 2>/dev/null || echo "000") 38 + 39 + if [ "$actual_status" = "$expected_status" ]; then 40 + log_pass "$description → HTTP $actual_status (expected $expected_status)" 41 + else 42 + log_fail "$description → HTTP $actual_status (expected $expected_status) — URL: $url" 43 + fi 44 + } 45 + 46 + check_json_field() { 47 + local description="$1" 48 + local url="$2" 49 + local field="$3" 50 + 51 + local response 52 + response=$(curl -s --max-time 10 "$url" 2>/dev/null || echo "{}") 53 + 54 + # Use node to parse JSON — available on any system running this stack. 55 + local value 56 + value=$(node -e " 57 + try { 58 + const data = JSON.parse(process.argv[1]); 59 + const keys = process.argv[2].split('.'); 60 + let val = data; 61 + for (const key of keys) { val = val[key]; } 62 + process.stdout.write(val !== undefined ? 'found' : 'missing'); 63 + } catch (e) { 64 + process.stdout.write('error'); 65 + } 66 + " "$response" "$field" 2>/dev/null || echo "error") 67 + 68 + if [ "$value" = "found" ]; then 69 + log_pass "$description → field '$field' present" 70 + else 71 + log_fail "$description → field '$field' missing or response unparseable" 72 + fi 73 + } 74 + 75 + wait_for_healthy() { 76 + local service_name="$1" 77 + local url="$2" 78 + local elapsed=0 79 + 80 + log_info "Waiting for $service_name to become healthy (up to ${MAX_WAIT_SECONDS}s)..." 81 + 82 + while [ $elapsed -lt $MAX_WAIT_SECONDS ]; do 83 + local actual_status 84 + actual_status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" 2>/dev/null || echo "000") 85 + 86 + # Accept 200 (health ok) or 401 (auth endpoint, service is up) as "alive" 87 + if [ "$actual_status" = "200" ] || [ "$actual_status" = "401" ]; then 88 + log_info "$service_name is responding (HTTP $actual_status) after ${elapsed}s" 89 + return 0 90 + fi 91 + 92 + sleep $POLL_INTERVAL 93 + elapsed=$((elapsed + POLL_INTERVAL)) 94 + log_info " ... still waiting (${elapsed}s / ${MAX_WAIT_SECONDS}s, HTTP $actual_status)" 95 + done 96 + 97 + echo -e "${RED}[ERROR]${NC} $service_name did not become healthy within ${MAX_WAIT_SECONDS}s" 98 + return 1 99 + } 100 + 101 + show_logs_for_service() { 102 + local service="$1" 103 + echo "" 104 + echo -e "${YELLOW}═══ Logs for: $service ═══${NC}" 105 + docker compose -f "$COMPOSE_FILE" logs --tail=30 "$service" 2>/dev/null || true 106 + echo "" 107 + } 108 + 109 + # ── Main ──────────────────────────────────────────────── 110 + 111 + echo "" 112 + echo -e "${YELLOW}╔══════════════════════════════════════════╗${NC}" 113 + echo -e "${YELLOW}║ ATlast Docker Stack Smoke Test ║${NC}" 114 + echo -e "${YELLOW}╚══════════════════════════════════════════╝${NC}" 115 + echo "" 116 + 117 + # ── Step 1: Build and start the stack ────────────────── 118 + log_info "Building and starting the stack..." 119 + docker compose -f "$COMPOSE_FILE" up --build -d 120 + 121 + echo "" 122 + log_info "Stack started. Waiting for services to initialize..." 123 + echo "" 124 + 125 + # ── Step 2: Wait for API health endpoint ─────────────── 126 + if ! wait_for_healthy "API" "$BASE_URL/api/health"; then 127 + echo "" 128 + echo -e "${RED}API did not start. Showing logs for debugging:${NC}" 129 + show_logs_for_service "api" 130 + show_logs_for_service "database" 131 + show_logs_for_service "redis" 132 + exit 1 133 + fi 134 + 135 + echo "" 136 + echo -e "${YELLOW}─── Running endpoint checks ───${NC}" 137 + echo "" 138 + 139 + # ── Step 3: API health check ─────────────────────────── 140 + check_status \ 141 + "GET /api/health → 200 OK" \ 142 + "$BASE_URL/api/health" \ 143 + "200" 144 + 145 + check_json_field \ 146 + "GET /api/health → has 'status' field" \ 147 + "$BASE_URL/api/health" \ 148 + "status" 149 + 150 + # ── Step 4: Auth endpoints (unauthenticated) ─────────── 151 + 152 + # 401 proves: API is running AND auth middleware is working correctly. 153 + # A missing session returning 401 is the expected, correct behavior. 154 + check_status \ 155 + "GET /api/auth/session → 401 (no session cookie, auth middleware active)" \ 156 + "$BASE_URL/api/auth/session" \ 157 + "401" 158 + 159 + check_status \ 160 + "GET /api/auth/client-metadata.json → 200" \ 161 + "$BASE_URL/api/auth/client-metadata.json" \ 162 + "200" 163 + 164 + check_json_field \ 165 + "GET /api/auth/client-metadata.json → has 'client_id' field" \ 166 + "$BASE_URL/api/auth/client-metadata.json" \ 167 + "client_id" 168 + 169 + check_status \ 170 + "GET /api/auth/jwks → 200" \ 171 + "$BASE_URL/api/auth/jwks" \ 172 + "200" 173 + 174 + check_json_field \ 175 + "GET /api/auth/jwks → has 'keys' field" \ 176 + "$BASE_URL/api/auth/jwks" \ 177 + "keys" 178 + 179 + # ── Step 5: Frontend is served ───────────────────────── 180 + check_status \ 181 + "GET / → 200 (frontend HTML served by nginx)" \ 182 + "$BASE_URL/" \ 183 + "200" 184 + 185 + # ── Step 6: Docker service health summary ────────────── 186 + echo "" 187 + echo -e "${YELLOW}─── Docker service status ───${NC}" 188 + docker compose -f "$COMPOSE_FILE" ps 189 + echo "" 190 + 191 + # ── Step 7: Show results ─────────────────────────────── 192 + echo -e "${YELLOW}─── Results ────────────────────────────────────${NC}" 193 + echo -e " ${GREEN}Passed: $PASS${NC}" 194 + echo -e " ${RED}Failed: $FAIL${NC}" 195 + echo "" 196 + 197 + if [ $FAIL -gt 0 ]; then 198 + echo -e "${RED}Smoke test FAILED ($FAIL checks failed).${NC}" 199 + echo "" 200 + echo "Showing logs for potentially failing services:" 201 + 202 + for service in api worker database redis frontend; do 203 + STATUS=$(docker compose -f "$COMPOSE_FILE" ps --format "{{.Service}} {{.Status}}" 2>/dev/null \ 204 + | grep "^$service " | awk '{print $2}' || echo "unknown") 205 + if [[ "$STATUS" != *"Up"* ]] || [[ "$STATUS" == *"unhealthy"* ]]; then 206 + show_logs_for_service "$service" 207 + fi 208 + done 209 + 210 + exit 1 211 + else 212 + echo -e "${GREEN}All smoke tests passed!${NC}" 213 + echo "" 214 + echo "The stack is running. Access the app at: $BASE_URL" 215 + exit 0 216 + fi
+32
scripts/generate-secrets.sh
··· 1 + #!/bin/bash 2 + # ═══════════════════════════════════════════════════════ 3 + # ATlast Secret Generator 4 + # Generates all required environment variable values. 5 + # Usage: bash scripts/generate-secrets.sh 6 + # ═══════════════════════════════════════════════════════ 7 + 8 + set -euo pipefail 9 + 10 + echo "# ═══════════════════════════════════════════════════" 11 + echo "# ATlast generated secrets — $(date -u +%Y-%m-%dT%H:%M:%SZ)" 12 + echo "# Copy these values into docker/.env" 13 + echo "# ═══════════════════════════════════════════════════" 14 + echo "" 15 + 16 + echo "# Database password (32 bytes of randomness, hex-encoded)" 17 + echo "DB_PASSWORD=$(node -e "process.stdout.write(require('crypto').randomBytes(32).toString('hex'))")" 18 + echo "" 19 + 20 + echo "# Token encryption key (32 bytes of randomness, hex-encoded)" 21 + echo "TOKEN_ENCRYPTION_KEY=$(node -e "process.stdout.write(require('crypto').randomBytes(32).toString('hex'))")" 22 + echo "" 23 + 24 + echo "# Generate OAUTH_PRIVATE_KEY separately (requires openssl):" 25 + echo "# Run the following command, then paste the output as OAUTH_PRIVATE_KEY" 26 + echo "# Replace actual newlines with \\n for the .env file." 27 + echo "#" 28 + echo "# openssl ecparam -genkey -name prime256v1 -noout | openssl pkcs8 -topk8 -nocrypt" 29 + echo "" 30 + 31 + echo "# CLOUDFLARE_TUNNEL_TOKEN: obtain from https://one.dash.cloudflare.com" 32 + echo "# Zero Trust → Access → Tunnels → Create a tunnel → copy the token"