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:
1 parent
b29314fd80
commit
5153587baf
5 files changed
+170
-17
No files matched your search
+6
-6
@@ -1,18 +1,18 @@
|
|||||||
# PostgreSQL
|
# PostgreSQL
|
||||||
POSTGRES_USER=elydora
|
POSTGRES_USER=elydora
|
||||||
POSTGRES_PASSWORD=elydora-secret-change-me
|
POSTGRES_PASSWORD=GENERATE_ME
|
||||||
POSTGRES_DB=elydora
|
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
|
||||||
REDIS_URL=redis://redis:6379
|
REDIS_URL=redis://redis:6379
|
||||||
|
|
||||||
# MinIO
|
# MinIO
|
||||||
MINIO_ROOT_USER=elydora
|
MINIO_ROOT_USER=elydora
|
||||||
MINIO_ROOT_PASSWORD=elydora-minio-secret-change-me
|
MINIO_ROOT_PASSWORD=GENERATE_ME
|
||||||
MINIO_ENDPOINT=http://minio:9000
|
MINIO_ENDPOINT=http://minio:9000
|
||||||
MINIO_ACCESS_KEY=elydora
|
MINIO_ACCESS_KEY=elydora
|
||||||
MINIO_SECRET_KEY=elydora-minio-secret-change-me
|
MINIO_SECRET_KEY=GENERATE_ME
|
||||||
MINIO_BUCKET=elydora-evidence
|
MINIO_BUCKET=elydora-evidence
|
||||||
|
|
||||||
# Elydora API
|
# Elydora API
|
||||||
@@ -20,8 +20,8 @@ PORT=8787
|
|||||||
ENVIRONMENT=production
|
ENVIRONMENT=production
|
||||||
API_VERSION=v1
|
API_VERSION=v1
|
||||||
PROTOCOL_VERSION=1.0
|
PROTOCOL_VERSION=1.0
|
||||||
JWT_SECRET=change-me-to-a-random-string-at-least-32-chars
|
JWT_SECRET=GENERATE_ME
|
||||||
ELYDORA_SIGNING_KEY=change-me-base64url-ed25519-private-key
|
ELYDORA_SIGNING_KEY=GENERATE_ME
|
||||||
ALLOWED_ORIGINS=http://localhost:3000
|
ALLOWED_ORIGINS=http://localhost:3000
|
||||||
|
|
||||||
# TSA (optional)
|
# TSA (optional)
|
||||||
|
|||||||
@@ -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
|
git clone https://github.com/Elydora-Infrastructure/Elydora-Open-Source.git
|
||||||
cd Elydora-Open-Source
|
cd Elydora-Open-Source
|
||||||
|
|
||||||
# Copy and edit environment configuration
|
# One-click install — generates all secrets and starts every service
|
||||||
cp .env.example .env
|
./scripts/install.sh
|
||||||
|
|
||||||
# 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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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
|
### Kubernetes Deploy
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -64,6 +64,14 @@ async function request<T>(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
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;
|
let errorBody: ErrorResponse | null = null;
|
||||||
try {
|
try {
|
||||||
errorBody = await response.json() as ErrorResponse;
|
errorBody = await response.json() as ErrorResponse;
|
||||||
|
|||||||
@@ -43,6 +43,21 @@ const queue = new BullMQAdapter({
|
|||||||
port: Number(redisUrl.port) || 6379,
|
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
|
// Build the env bindings object
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Executable
+122
@@ -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'
|
||||||
Reference in new issue
Block a user