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 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 4.6 committed 2026-03-02 14:56:45 -05:00
1 parent b29314fd80
commit 5153587baf
5 files changed
+170 -17

No files matched your search

+6 -6
View File
@@ -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)
+19 -11
View File
@@ -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
+8
View File
@@ -64,6 +64,14 @@ async function request<T>(
});
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;
+15
View File
@@ -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
// ---------------------------------------------------------------------------
+122
View File
@@ -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'