feat: production deployment setup and hardcoded URL fixes
- Fix hardcoded localhost:8000 URLs in frontend to use relative /api/v1 path (works with Nginx proxy in prod and VITE_API_URL in dev) - Create production entrypoint (entrypoint.prod.sh) that runs migrations, seeds, and starts uvicorn with 4 workers (no --reload) - Create comprehensive install.sh script for production deployment that generates secure .env, builds containers, waits for health, and optionally triggers initial MITRE sync - Update docker-compose.prod.yml to use production entrypoint - Update Dockerfile to make both entrypoints executable - Remove init.ps1 (production will always be Linux) - Update README with production deployment instructions
This commit is contained in:
63
README.md
63
README.md
@@ -95,38 +95,40 @@ Both Red Lead and Blue Lead must independently vote:
|
|||||||
|
|
||||||
- Docker and Docker Compose
|
- Docker and Docker Compose
|
||||||
- Git
|
- Git
|
||||||
|
- Linux / macOS (or WSL on Windows)
|
||||||
|
|
||||||
### Installation
|
### Production Deployment
|
||||||
|
|
||||||
|
The recommended way to deploy Aegis in production:
|
||||||
|
|
||||||
1. Clone the repository:
|
|
||||||
```bash
|
```bash
|
||||||
git clone <repository-url>
|
git clone <repository-url>
|
||||||
cd Aegis
|
cd Aegis
|
||||||
|
chmod +x scripts/install.sh
|
||||||
|
./scripts/install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Start all services:
|
The install script will automatically:
|
||||||
|
- Generate a `.env` file with secure random secrets
|
||||||
|
- Build and start all containers (PostgreSQL, MinIO, Backend, Frontend)
|
||||||
|
- Run database migrations
|
||||||
|
- Seed the admin user and data sources
|
||||||
|
- Optionally run the initial MITRE ATT&CK sync
|
||||||
|
|
||||||
|
Access the application at **http://your-server:80**.
|
||||||
|
|
||||||
|
### Development Setup
|
||||||
|
|
||||||
|
For local development with hot-reload:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
git clone <repository-url>
|
||||||
|
cd Aegis
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
|
./scripts/init.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Run database migrations:
|
Access at **http://localhost:5173** (frontend dev server) and **http://localhost:8000/docs** (API docs).
|
||||||
```bash
|
|
||||||
docker exec aegis-backend alembic upgrade head
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Seed the admin user:
|
|
||||||
```bash
|
|
||||||
docker exec aegis-backend python -m app.seed
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Access the application:
|
|
||||||
```bash
|
|
||||||
# API health check
|
|
||||||
curl http://localhost:8000/health
|
|
||||||
# Expected: {"status":"ok"}
|
|
||||||
|
|
||||||
# Open http://localhost:5173 — Aegis login page
|
|
||||||
```
|
|
||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
|
|
||||||
@@ -137,27 +139,32 @@ Username: admin
|
|||||||
Password: admin123
|
Password: admin123
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Important:** Change the default `admin123` password and `SECRET_KEY` in production.
|
> **Important:** Change the default `admin123` password immediately after first login.
|
||||||
|
|
||||||
### Importing Data Sources
|
### Importing Data Sources
|
||||||
|
|
||||||
After initial setup, the entrypoint script automatically seeds the initial data sources (Atomic Red Team, SigmaHQ, CALDERA, LOLBAS, GTFOBins, D3FEND). You can then sync each source from the UI:
|
On startup, the backend automatically seeds the initial data sources (Atomic Red Team, SigmaHQ, CALDERA, LOLBAS, GTFOBins, D3FEND). You can then sync each source from the UI:
|
||||||
|
|
||||||
1. Navigate to **System > Data Sources** in the admin panel
|
1. Navigate to **Data Sources** in the sidebar
|
||||||
2. Click **Sync** on each data source to import its content
|
2. Click **Sync** on each data source to import its content
|
||||||
3. Trigger a **MITRE ATT&CK Sync** from the **System > MITRE Sync** page
|
3. Trigger a **MITRE ATT&CK Sync** from the **System** page
|
||||||
|
|
||||||
Alternatively, use the API:
|
Alternatively, use the API:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Sync MITRE ATT&CK techniques
|
# Sync MITRE ATT&CK techniques
|
||||||
curl -X POST http://localhost:8000/api/v1/system/sync-mitre -H "Authorization: Bearer $TOKEN"
|
curl -X POST http://your-server/api/v1/system/sync-mitre -H "Authorization: Bearer $TOKEN"
|
||||||
|
|
||||||
# Sync all data sources at once
|
# Sync all data sources at once
|
||||||
curl -X POST http://localhost:8000/api/v1/data-sources/sync-all -H "Authorization: Bearer $TOKEN"
|
curl -X POST http://your-server/api/v1/data-sources/sync-all -H "Authorization: Bearer $TOKEN"
|
||||||
```
|
```
|
||||||
|
|
||||||
See [docs/DATA_SOURCES.md](docs/DATA_SOURCES.md) for detailed instructions on all data sources.
|
### Production Considerations
|
||||||
|
|
||||||
|
- **HTTPS/TLS:** For internet-facing deployments, place a reverse proxy with TLS in front (e.g., Traefik, Caddy, or Nginx with Let's Encrypt).
|
||||||
|
- **Backups:** Set up regular PostgreSQL backups: `docker exec aegis-postgres pg_dump -U postgres attackdb > backup.sql`
|
||||||
|
- **Updates:** To update, pull the latest code and run: `docker compose -f docker-compose.prod.yml up -d --build`
|
||||||
|
- **Firewall:** Only expose port 80/443. All other services (DB, MinIO, backend) are internal only.
|
||||||
|
|
||||||
### Configuring Scoring Weights
|
### Configuring Scoring Weights
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ RUN pip install --no-cache-dir -r requirements.txt
|
|||||||
# Copy application code
|
# Copy application code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Make entrypoint executable
|
# Make entrypoints executable
|
||||||
RUN chmod +x /app/entrypoint.sh
|
RUN chmod +x /app/entrypoint.sh /app/entrypoint.prod.sh
|
||||||
|
|
||||||
# Expose port
|
# Expose port
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|||||||
14
backend/entrypoint.prod.sh
Normal file
14
backend/entrypoint.prod.sh
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=== [PROD] Running Alembic migrations ==="
|
||||||
|
alembic upgrade head
|
||||||
|
|
||||||
|
echo "=== [PROD] Seeding admin user ==="
|
||||||
|
python -m app.seed
|
||||||
|
|
||||||
|
echo "=== [PROD] Seeding data sources ==="
|
||||||
|
python -m app.seed_data_sources
|
||||||
|
|
||||||
|
echo "=== [PROD] Starting uvicorn (production) ==="
|
||||||
|
exec uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4
|
||||||
@@ -68,7 +68,7 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
minio:
|
minio:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4
|
command: sh /app/entrypoint.prod.sh
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import axios, { type AxiosError } from "axios";
|
import axios, { type AxiosError } from "axios";
|
||||||
|
|
||||||
|
const API_BASE_URL = import.meta.env.VITE_API_URL || "/api/v1";
|
||||||
|
|
||||||
const client = axios.create({
|
const client = axios.create({
|
||||||
baseURL: "http://localhost:8000/api/v1",
|
baseURL: API_BASE_URL,
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ export default function TestCreatePage() {
|
|||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
// We need to find the mitre_id from the technique list
|
// We need to find the mitre_id from the technique list
|
||||||
// This is a workaround since we get UUID but need mitre_id
|
// This is a workaround since we get UUID but need mitre_id
|
||||||
const response = await fetch(`http://localhost:8000/api/v1/techniques`, {
|
const apiBase = import.meta.env.VITE_API_URL || "/api/v1";
|
||||||
|
const response = await fetch(`${apiBase}/techniques`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
||||||
},
|
},
|
||||||
|
|||||||
132
scripts/init.ps1
132
scripts/init.ps1
@@ -1,132 +0,0 @@
|
|||||||
# =============================================================================
|
|
||||||
# Aegis Initialization Script (PowerShell)
|
|
||||||
# =============================================================================
|
|
||||||
# This script initializes the Aegis platform after starting Docker containers.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# .\scripts\init.ps1
|
|
||||||
# =============================================================================
|
|
||||||
|
|
||||||
$ErrorActionPreference = "Stop"
|
|
||||||
|
|
||||||
Write-Host "╔═══════════════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
|
|
||||||
Write-Host "║ Aegis - Platform Initialization ║" -ForegroundColor Cyan
|
|
||||||
Write-Host "╚═══════════════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
|
|
||||||
Write-Host ""
|
|
||||||
|
|
||||||
function Write-Status {
|
|
||||||
param([string]$Message)
|
|
||||||
Write-Host "[✓] $Message" -ForegroundColor Green
|
|
||||||
}
|
|
||||||
|
|
||||||
function Write-Warning {
|
|
||||||
param([string]$Message)
|
|
||||||
Write-Host "[!] $Message" -ForegroundColor Yellow
|
|
||||||
}
|
|
||||||
|
|
||||||
function Write-Error {
|
|
||||||
param([string]$Message)
|
|
||||||
Write-Host "[✗] $Message" -ForegroundColor Red
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if Docker is running
|
|
||||||
try {
|
|
||||||
docker info | Out-Null
|
|
||||||
} catch {
|
|
||||||
Write-Error "Docker is not running. Please start Docker first."
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if containers are running
|
|
||||||
$backendRunning = docker-compose ps | Select-String "aegis-backend"
|
|
||||||
if (-not $backendRunning) {
|
|
||||||
Write-Warning "Containers not running. Starting them now..."
|
|
||||||
docker-compose up -d
|
|
||||||
Write-Host ""
|
|
||||||
Write-Host "Waiting for services to be healthy..."
|
|
||||||
Start-Sleep -Seconds 10
|
|
||||||
}
|
|
||||||
|
|
||||||
# Wait for backend to be ready
|
|
||||||
Write-Host "Waiting for backend to be ready..."
|
|
||||||
$maxRetries = 30
|
|
||||||
$retryCount = 0
|
|
||||||
$ready = $false
|
|
||||||
|
|
||||||
while (-not $ready -and $retryCount -lt $maxRetries) {
|
|
||||||
try {
|
|
||||||
$response = Invoke-WebRequest -Uri "http://localhost:8000/health" -UseBasicParsing -TimeoutSec 2
|
|
||||||
if ($response.StatusCode -eq 200) {
|
|
||||||
$ready = $true
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
$retryCount++
|
|
||||||
Write-Host " Waiting... ($retryCount/$maxRetries)"
|
|
||||||
Start-Sleep -Seconds 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-not $ready) {
|
|
||||||
Write-Error "Backend failed to start after $maxRetries attempts"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
Write-Status "Backend is healthy"
|
|
||||||
|
|
||||||
# Run database migrations
|
|
||||||
Write-Host ""
|
|
||||||
Write-Host "Running database migrations..."
|
|
||||||
docker-compose exec -T backend alembic upgrade head
|
|
||||||
Write-Status "Migrations completed"
|
|
||||||
|
|
||||||
# Seed admin user
|
|
||||||
Write-Host ""
|
|
||||||
Write-Host "Seeding admin user..."
|
|
||||||
try {
|
|
||||||
docker-compose exec -T backend python -m app.seed 2>$null
|
|
||||||
Write-Status "Admin user ready"
|
|
||||||
} catch {
|
|
||||||
Write-Warning "Admin user may already exist"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Ask about MITRE sync
|
|
||||||
Write-Host ""
|
|
||||||
$runSync = Read-Host "Do you want to run initial MITRE ATT&CK sync? (y/N)"
|
|
||||||
if ($runSync -eq "y" -or $runSync -eq "Y") {
|
|
||||||
Write-Host "Triggering MITRE sync (this may take a minute)..."
|
|
||||||
|
|
||||||
try {
|
|
||||||
# Get admin token
|
|
||||||
$loginBody = "username=admin&password=admin123"
|
|
||||||
$loginResponse = Invoke-RestMethod -Uri "http://localhost:8000/api/v1/auth/login" `
|
|
||||||
-Method Post -Body $loginBody -ContentType "application/x-www-form-urlencoded"
|
|
||||||
|
|
||||||
$token = $loginResponse.access_token
|
|
||||||
|
|
||||||
if ($token) {
|
|
||||||
$headers = @{ "Authorization" = "Bearer $token" }
|
|
||||||
Invoke-RestMethod -Uri "http://localhost:8000/api/v1/system/sync-mitre" `
|
|
||||||
-Method Post -Headers $headers | Out-Null
|
|
||||||
Write-Status "MITRE sync triggered"
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
Write-Warning "Could not authenticate. Run sync manually from the System page."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Print summary
|
|
||||||
Write-Host ""
|
|
||||||
Write-Host "╔═══════════════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
|
|
||||||
Write-Host "║ Aegis is ready! ║" -ForegroundColor Cyan
|
|
||||||
Write-Host "╠═══════════════════════════════════════════════════════════════════════╣" -ForegroundColor Cyan
|
|
||||||
Write-Host "║ ║" -ForegroundColor Cyan
|
|
||||||
Write-Host "║ Frontend: http://localhost:5173 ║" -ForegroundColor White
|
|
||||||
Write-Host "║ Backend API: http://localhost:8000 ║" -ForegroundColor White
|
|
||||||
Write-Host "║ Swagger UI: http://localhost:8000/docs ║" -ForegroundColor White
|
|
||||||
Write-Host "║ MinIO Console: http://localhost:9001 ║" -ForegroundColor White
|
|
||||||
Write-Host "║ ║" -ForegroundColor Cyan
|
|
||||||
Write-Host "║ Default login: admin / admin123 ║" -ForegroundColor Yellow
|
|
||||||
Write-Host "║ ║" -ForegroundColor Cyan
|
|
||||||
Write-Host "║ ⚠️ Change the default password in production! ║" -ForegroundColor Yellow
|
|
||||||
Write-Host "║ ║" -ForegroundColor Cyan
|
|
||||||
Write-Host "╚═══════════════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
|
|
||||||
Write-Host ""
|
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Aegis Initialization Script
|
# Aegis Development Initialization Script
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# This script initializes the Aegis platform after starting Docker containers.
|
# This script initializes the Aegis platform for local development.
|
||||||
|
# For production, use: ./scripts/install.sh
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./scripts/init.sh
|
# ./scripts/init.sh
|
||||||
|
|||||||
249
scripts/install.sh
Normal file
249
scripts/install.sh
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# Aegis - Production Installation Script
|
||||||
|
# =============================================================================
|
||||||
|
# This script sets up the Aegis platform for production.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# chmod +x scripts/install.sh
|
||||||
|
# ./scripts/install.sh
|
||||||
|
#
|
||||||
|
# Prerequisites:
|
||||||
|
# - Docker and Docker Compose installed
|
||||||
|
# - Port 80 (or FRONTEND_PORT) available
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
BOLD='\033[1m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
print_ok() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||||
|
print_warn() { echo -e "${YELLOW}[!]${NC} $1"; }
|
||||||
|
print_error() { echo -e "${RED}[✗]${NC} $1"; }
|
||||||
|
print_info() { echo -e "${CYAN}[i]${NC} $1"; }
|
||||||
|
print_header() { echo -e "\n${BOLD}── $1 ──${NC}"; }
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "╔═══════════════════════════════════════════════════════════════╗"
|
||||||
|
echo "║ Aegis - Production Installation ║"
|
||||||
|
echo "╚═══════════════════════════════════════════════════════════════╝"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ── 1. Check prerequisites ──────────────────────────────────────────
|
||||||
|
|
||||||
|
print_header "Checking prerequisites"
|
||||||
|
|
||||||
|
if ! command -v docker &> /dev/null; then
|
||||||
|
print_error "Docker is not installed. Please install Docker first."
|
||||||
|
echo " -> https://docs.docker.com/engine/install/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
print_ok "Docker found: $(docker --version | head -1)"
|
||||||
|
|
||||||
|
if ! docker info > /dev/null 2>&1; then
|
||||||
|
print_error "Docker daemon is not running. Please start Docker."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
print_ok "Docker daemon is running"
|
||||||
|
|
||||||
|
# Check for docker compose (v2 plugin or standalone)
|
||||||
|
if docker compose version > /dev/null 2>&1; then
|
||||||
|
COMPOSE_CMD="docker compose"
|
||||||
|
elif command -v docker-compose &> /dev/null; then
|
||||||
|
COMPOSE_CMD="docker-compose"
|
||||||
|
else
|
||||||
|
print_error "Docker Compose is not installed."
|
||||||
|
echo " -> https://docs.docker.com/compose/install/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
print_ok "Docker Compose found ($COMPOSE_CMD)"
|
||||||
|
|
||||||
|
# ── 2. Setup .env file ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
print_header "Environment configuration"
|
||||||
|
|
||||||
|
ENV_FILE=".env"
|
||||||
|
|
||||||
|
if [ -f "$ENV_FILE" ]; then
|
||||||
|
print_warn ".env file already exists"
|
||||||
|
read -p " Overwrite with new values? (y/N) " -n 1 -r
|
||||||
|
echo ""
|
||||||
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
print_info "Keeping existing .env file"
|
||||||
|
SKIP_ENV=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${SKIP_ENV}" != "true" ]; then
|
||||||
|
# Generate secure secrets
|
||||||
|
SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))" 2>/dev/null || openssl rand -hex 32 2>/dev/null || head -c 64 /dev/urandom | od -An -tx1 | tr -d ' \n')
|
||||||
|
DB_PASSWORD=$(python3 -c "import secrets; print(secrets.token_urlsafe(24))" 2>/dev/null || openssl rand -base64 24 2>/dev/null || head -c 24 /dev/urandom | base64)
|
||||||
|
MINIO_SECRET=$(python3 -c "import secrets; print(secrets.token_urlsafe(24))" 2>/dev/null || openssl rand -base64 24 2>/dev/null || head -c 24 /dev/urandom | base64)
|
||||||
|
|
||||||
|
cat > "$ENV_FILE" <<EOF
|
||||||
|
# ── Aegis Production Environment ─────────────────────────────────
|
||||||
|
# Generated by install.sh on $(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
||||||
|
|
||||||
|
# Database
|
||||||
|
DB_USER=postgres
|
||||||
|
DB_PASSWORD=${DB_PASSWORD}
|
||||||
|
DB_NAME=attackdb
|
||||||
|
|
||||||
|
# Security
|
||||||
|
SECRET_KEY=${SECRET_KEY}
|
||||||
|
TOKEN_EXPIRE_MINUTES=60
|
||||||
|
|
||||||
|
# MinIO Object Storage
|
||||||
|
MINIO_ACCESS_KEY=minioadmin
|
||||||
|
MINIO_SECRET_KEY=${MINIO_SECRET}
|
||||||
|
MINIO_BUCKET=evidence
|
||||||
|
|
||||||
|
# Frontend
|
||||||
|
FRONTEND_PORT=80
|
||||||
|
EOF
|
||||||
|
|
||||||
|
print_ok ".env file created with secure random secrets"
|
||||||
|
print_info "Review and edit .env if needed before proceeding"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 3. Build and start containers ────────────────────────────────────
|
||||||
|
|
||||||
|
print_header "Building and starting containers"
|
||||||
|
|
||||||
|
print_info "This may take a few minutes on first run..."
|
||||||
|
$COMPOSE_CMD -f docker-compose.prod.yml up -d --build 2>&1 | while IFS= read -r line; do
|
||||||
|
echo " $line"
|
||||||
|
done
|
||||||
|
|
||||||
|
print_ok "Containers started"
|
||||||
|
|
||||||
|
# ── 4. Wait for services to be healthy ───────────────────────────────
|
||||||
|
|
||||||
|
print_header "Waiting for services"
|
||||||
|
|
||||||
|
# Wait for postgres
|
||||||
|
print_info "Waiting for PostgreSQL..."
|
||||||
|
MAX_RETRIES=30
|
||||||
|
RETRY=0
|
||||||
|
until docker exec aegis-postgres pg_isready -U postgres > /dev/null 2>&1; do
|
||||||
|
RETRY=$((RETRY + 1))
|
||||||
|
if [ $RETRY -ge $MAX_RETRIES ]; then
|
||||||
|
print_error "PostgreSQL failed to start after $MAX_RETRIES attempts"
|
||||||
|
echo " Check logs: docker logs aegis-postgres"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
print_ok "PostgreSQL is ready"
|
||||||
|
|
||||||
|
# Wait for backend (which runs migrations + seed on startup)
|
||||||
|
print_info "Waiting for backend (running migrations and seeds)..."
|
||||||
|
RETRY=0
|
||||||
|
until curl -sf http://localhost:8000/health > /dev/null 2>&1 || \
|
||||||
|
docker exec aegis-backend curl -sf http://localhost:8000/health > /dev/null 2>&1; do
|
||||||
|
RETRY=$((RETRY + 1))
|
||||||
|
if [ $RETRY -ge 60 ]; then
|
||||||
|
print_error "Backend failed to start after 120 seconds"
|
||||||
|
echo " Check logs: docker logs aegis-backend"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
print_ok "Backend is ready (migrations and seeds completed)"
|
||||||
|
|
||||||
|
# Wait for frontend
|
||||||
|
print_info "Waiting for frontend..."
|
||||||
|
RETRY=0
|
||||||
|
FRONTEND_PORT=$(grep FRONTEND_PORT "$ENV_FILE" 2>/dev/null | cut -d= -f2 || echo "80")
|
||||||
|
FRONTEND_PORT=${FRONTEND_PORT:-80}
|
||||||
|
until curl -sf "http://localhost:${FRONTEND_PORT}" > /dev/null 2>&1; do
|
||||||
|
RETRY=$((RETRY + 1))
|
||||||
|
if [ $RETRY -ge 30 ]; then
|
||||||
|
print_error "Frontend failed to start after 60 seconds"
|
||||||
|
echo " Check logs: docker logs aegis-frontend"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
print_ok "Frontend is ready"
|
||||||
|
|
||||||
|
# ── 5. Trigger MITRE ATT&CK sync ────────────────────────────────────
|
||||||
|
|
||||||
|
print_header "Initial data sync"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
read -p "Run initial MITRE ATT&CK sync? This imports ~700 techniques. (Y/n) " -n 1 -r
|
||||||
|
echo ""
|
||||||
|
if [[ ! $REPLY =~ ^[Nn]$ ]]; then
|
||||||
|
print_info "Authenticating..."
|
||||||
|
|
||||||
|
# Get admin token
|
||||||
|
TOKEN=$(curl -sf -X POST "http://localhost:${FRONTEND_PORT}/api/v1/auth/login" \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "username=admin&password=admin123" | python3 -c "import sys,json; print(json.load(sys.stdin).get('access_token',''))" 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if [ -n "$TOKEN" ] && [ "$TOKEN" != "" ]; then
|
||||||
|
print_info "Syncing MITRE ATT&CK data (this takes 1-2 minutes)..."
|
||||||
|
SYNC_RESULT=$(curl -sf -X POST "http://localhost:${FRONTEND_PORT}/api/v1/system/sync-mitre" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" 2>/dev/null || echo "error")
|
||||||
|
|
||||||
|
if [ "$SYNC_RESULT" != "error" ]; then
|
||||||
|
print_ok "MITRE ATT&CK sync completed"
|
||||||
|
else
|
||||||
|
print_warn "MITRE sync may have timed out. Check the System page in the UI."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Sync data sources
|
||||||
|
print_info "Syncing data sources (Atomic Red Team, SigmaHQ, etc.)..."
|
||||||
|
for source_id in $(curl -sf "http://localhost:${FRONTEND_PORT}/api/v1/data-sources" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" 2>/dev/null | \
|
||||||
|
python3 -c "import sys,json; [print(s['id']) for s in json.load(sys.stdin)]" 2>/dev/null); do
|
||||||
|
|
||||||
|
curl -sf -X POST "http://localhost:${FRONTEND_PORT}/api/v1/data-sources/${source_id}/sync" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" > /dev/null 2>&1 || true
|
||||||
|
done
|
||||||
|
print_ok "Data source sync triggered"
|
||||||
|
else
|
||||||
|
print_warn "Could not authenticate. Run MITRE sync manually from the System page."
|
||||||
|
print_info "Default credentials: admin / admin123"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
print_info "Skipping MITRE sync. You can do this later from the System page."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── 6. Summary ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Get the server's IP
|
||||||
|
SERVER_IP=$(hostname -I 2>/dev/null | awk '{print $1}' || echo "localhost")
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "╔═══════════════════════════════════════════════════════════════╗"
|
||||||
|
echo "║ Aegis is ready! ║"
|
||||||
|
echo "╠═══════════════════════════════════════════════════════════════╣"
|
||||||
|
echo "║ ║"
|
||||||
|
echo "║ Application: http://${SERVER_IP}:${FRONTEND_PORT} "
|
||||||
|
echo "║ API Docs: http://${SERVER_IP}:${FRONTEND_PORT}/api/v1/docs "
|
||||||
|
echo "║ ║"
|
||||||
|
echo "║ Default login: admin / admin123 ║"
|
||||||
|
echo "║ ║"
|
||||||
|
echo "║ ⚠ IMPORTANT: ║"
|
||||||
|
echo "║ • Change the default password immediately ║"
|
||||||
|
echo "║ • Set up HTTPS/TLS for internet-facing deployments ║"
|
||||||
|
echo "║ • Configure firewall rules as needed ║"
|
||||||
|
echo "║ • Set up regular database backups ║"
|
||||||
|
echo "║ ║"
|
||||||
|
echo "╚═══════════════════════════════════════════════════════════════╝"
|
||||||
|
echo ""
|
||||||
|
echo "Useful commands:"
|
||||||
|
echo " View logs: docker logs -f aegis-backend"
|
||||||
|
echo " Stop: $COMPOSE_CMD -f docker-compose.prod.yml down"
|
||||||
|
echo " Restart: $COMPOSE_CMD -f docker-compose.prod.yml restart"
|
||||||
|
echo " Update: $COMPOSE_CMD -f docker-compose.prod.yml up -d --build"
|
||||||
|
echo " DB backup: docker exec aegis-postgres pg_dump -U postgres attackdb > backup.sql"
|
||||||
|
echo ""
|
||||||
Reference in New Issue
Block a user