From 5153587baf5f2409ad4ad4ad777274b3b1e8bc07 Mon Sep 17 00:00:00 2001 From: Zachary Date: Mon, 2 Mar 2026 14:56:45 -0500 Subject: [PATCH] Add one-click install script with automatic secret generation - Create scripts/install.sh that auto-generates Ed25519 signing key, JWT secret, Postgres and MinIO passwords from .env.example placeholders - Add startup validation in server to reject placeholder secrets - Add 401 interceptor in console API client to clear stale JWT tokens - Update README Quick Start to use ./scripts/install.sh - Update .env.example with GENERATE_ME placeholders for all secrets Co-Authored-By: Claude Opus 4.6 --- .env.example | 12 ++-- README.md | 30 +++++--- packages/console/src/lib/api.ts | 8 +++ packages/server/src/index.ts | 15 ++++ scripts/install.sh | 122 ++++++++++++++++++++++++++++++++ 5 files changed, 170 insertions(+), 17 deletions(-) create mode 100755 scripts/install.sh diff --git a/.env.example b/.env.example index c6aaff2..c8e3577 100644 --- a/.env.example +++ b/.env.example @@ -1,18 +1,18 @@ # PostgreSQL POSTGRES_USER=elydora -POSTGRES_PASSWORD=elydora-secret-change-me +POSTGRES_PASSWORD=GENERATE_ME POSTGRES_DB=elydora -DATABASE_URL=postgresql://elydora:elydora-secret-change-me@postgres:5432/elydora +DATABASE_URL=postgresql://elydora:GENERATE_ME@postgres:5432/elydora # Redis REDIS_URL=redis://redis:6379 # MinIO MINIO_ROOT_USER=elydora -MINIO_ROOT_PASSWORD=elydora-minio-secret-change-me +MINIO_ROOT_PASSWORD=GENERATE_ME MINIO_ENDPOINT=http://minio:9000 MINIO_ACCESS_KEY=elydora -MINIO_SECRET_KEY=elydora-minio-secret-change-me +MINIO_SECRET_KEY=GENERATE_ME MINIO_BUCKET=elydora-evidence # Elydora API @@ -20,8 +20,8 @@ PORT=8787 ENVIRONMENT=production API_VERSION=v1 PROTOCOL_VERSION=1.0 -JWT_SECRET=change-me-to-a-random-string-at-least-32-chars -ELYDORA_SIGNING_KEY=change-me-base64url-ed25519-private-key +JWT_SECRET=GENERATE_ME +ELYDORA_SIGNING_KEY=GENERATE_ME ALLOWED_ORIGINS=http://localhost:3000 # TSA (optional) diff --git a/README.md b/README.md index c481b0c..966e97a 100644 --- a/README.md +++ b/README.md @@ -85,19 +85,27 @@ The result is a complete, verifiable history of everything your AI agents have e git clone https://github.com/Elydora-Infrastructure/Elydora-Open-Source.git cd Elydora-Open-Source -# Copy and edit environment configuration -cp .env.example .env - -# Generate Ed25519 signing keys for the server and initial admin -./scripts/generate-keys.sh - -# Start all services (API, PostgreSQL, MinIO, Redis) -docker compose up -d - -# Verify the server is healthy -curl http://localhost:8787/v1/health +# One-click install — generates all secrets and starts every service +./scripts/install.sh ``` +The install script automatically: + +1. Checks prerequisites (Docker, Docker Compose, OpenSSL, curl) +2. Generates a `.env` with fresh cryptographic secrets (Ed25519 signing key, JWT secret, database and object-store passwords) +3. Starts all services via `docker compose up -d` +4. Waits for the API to become healthy + +Once complete, open: + +| Service | URL | +|---------|-----| +| Console | http://localhost:3000 | +| API | http://localhost:8787 | +| MinIO Console | http://localhost:9001 | + +> **Re-running is safe.** If `.env` already exists, the script skips secret generation and only restarts services. + ### Kubernetes Deploy ```bash diff --git a/packages/console/src/lib/api.ts b/packages/console/src/lib/api.ts index 35e00d0..b581997 100644 --- a/packages/console/src/lib/api.ts +++ b/packages/console/src/lib/api.ts @@ -64,6 +64,14 @@ async function request( }); if (!response.ok) { + // On 401, clear stored tokens and redirect to login + if (response.status === 401 && typeof window !== 'undefined') { + localStorage.removeItem('elydora_token'); + localStorage.removeItem('elydora_display_name'); + localStorage.removeItem('elydora_email'); + window.location.href = '/login'; + } + let errorBody: ErrorResponse | null = null; try { errorBody = await response.json() as ErrorResponse; diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 28f553b..1e11964 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -43,6 +43,21 @@ const queue = new BullMQAdapter({ port: Number(redisUrl.port) || 6379, }); +// --------------------------------------------------------------------------- +// Validate required secrets at startup +// --------------------------------------------------------------------------- + +const requiredSecrets = [ + { name: 'JWT_SECRET', value: process.env.JWT_SECRET }, + { name: 'ELYDORA_SIGNING_KEY', value: process.env.ELYDORA_SIGNING_KEY }, +]; +for (const { name, value } of requiredSecrets) { + if (!value || value.startsWith('change-me')) { + console.error(`FATAL: ${name} is not configured. Run ./scripts/install.sh to generate secrets.`); + process.exit(1); + } +} + // --------------------------------------------------------------------------- // Build the env bindings object // --------------------------------------------------------------------------- diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..0363e96 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env sh +set -e + +# ─────────────────────────────────────────────────────────── +# Elydora Open Source — One-click deployment +# ─────────────────────────────────────────────────────────── + +printf '\n' +printf ' ╔══════════════════════════════════════════╗\n' +printf ' ║ Elydora Open Source Installer ║\n' +printf ' ║ Self-hosted AI audit platform ║\n' +printf ' ╚══════════════════════════════════════════╝\n' +printf '\n' + +# ── helpers ──────────────────────────────────────────────── + +fail() { printf '\n[ERROR] %s\n' "$1" >&2; exit 1; } +info() { printf '[INFO] %s\n' "$1"; } +ok() { printf '[OK] %s\n' "$1"; } + +# Platform-aware sed in-place (macOS needs '') +sedi() { + if [ "$(uname)" = "Darwin" ]; then + sed -i '' "$@" + else + sed -i "$@" + fi +} + +# ── 1. check prerequisites ──────────────────────────────── + +info "Checking prerequisites..." + +command -v docker >/dev/null 2>&1 \ + || fail "docker is not installed. See https://docs.docker.com/get-docker/" + +docker compose version >/dev/null 2>&1 \ + || fail "docker compose (v2) is not available. See https://docs.docker.com/compose/install/" + +command -v openssl >/dev/null 2>&1 \ + || fail "openssl is not installed." + +command -v curl >/dev/null 2>&1 \ + || fail "curl is not installed." + +ok "All prerequisites satisfied." + +# ── 2. generate .env ─────────────────────────────────────── + +cd "$(dirname "$0")/.." + +if [ -f .env ]; then + info "Existing .env found — skipping secret generation." +else + info "Generating .env from .env.example..." + + [ -f .env.example ] || fail ".env.example not found in project root." + + cp .env.example .env + + # Generate secrets + PG_PASS=$(openssl rand -hex 24) + MINIO_PASS=$(openssl rand -hex 24) + JWT=$(openssl rand -hex 32) + SIGNING_KEY=$(openssl genpkey -algorithm ed25519 2>/dev/null \ + | openssl pkcs8 -topk8 -nocrypt -outform DER 2>/dev/null \ + | tail -c 32 \ + | base64 \ + | tr '+/' '-_' \ + | tr -d '=\n') + + [ -n "$PG_PASS" ] || fail "Failed to generate Postgres password." + [ -n "$MINIO_PASS" ] || fail "Failed to generate MinIO password." + [ -n "$JWT" ] || fail "Failed to generate JWT secret." + [ -n "$SIGNING_KEY" ] || fail "Failed to generate Ed25519 signing key." + + # Replace placeholders + sedi "s|POSTGRES_PASSWORD=GENERATE_ME|POSTGRES_PASSWORD=${PG_PASS}|" .env + sedi "s|elydora:GENERATE_ME@postgres|elydora:${PG_PASS}@postgres|" .env + sedi "s|MINIO_ROOT_PASSWORD=GENERATE_ME|MINIO_ROOT_PASSWORD=${MINIO_PASS}|" .env + sedi "s|MINIO_SECRET_KEY=GENERATE_ME|MINIO_SECRET_KEY=${MINIO_PASS}|" .env + sedi "s|JWT_SECRET=GENERATE_ME|JWT_SECRET=${JWT}|" .env + sedi "s|ELYDORA_SIGNING_KEY=GENERATE_ME|ELYDORA_SIGNING_KEY=${SIGNING_KEY}|" .env + + chmod 600 .env + + ok "Generated .env with fresh secrets (permissions 0600)." +fi + +# ── 3. docker compose up ────────────────────────────────── + +info "Starting services with docker compose..." +docker compose up -d + +# ── 4. wait for API health ──────────────────────────────── + +info "Waiting for API to become healthy (up to 60 s)..." + +elapsed=0 +while [ "$elapsed" -lt 60 ]; do + if curl -sf http://localhost:8787/v1/health >/dev/null 2>&1; then + break + fi + sleep 2 + elapsed=$((elapsed + 2)) +done + +if [ "$elapsed" -ge 60 ]; then + fail "API did not become healthy within 60 seconds. Run 'docker compose logs api' to diagnose." +fi + +# ── 5. done ─────────────────────────────────────────────── + +printf '\n' +printf ' ╔══════════════════════════════════════════╗\n' +printf ' ║ Elydora is running! ║\n' +printf ' ╠══════════════════════════════════════════╣\n' +printf ' ║ Console: http://localhost:3000 ║\n' +printf ' ║ API: http://localhost:8787 ║\n' +printf ' ║ MinIO Console: http://localhost:9001 ║\n' +printf ' ╚══════════════════════════════════════════╝\n' +printf '\n'