504dfc52f5
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled
Login is now by email, not username. username still exists internally (JWT sub claim, audit logs, Jira actor attribution, SSO provisioning all still key off it) but is now always kept equal to email everywhere a user is created or their email changes — never a separately-chosen value. - User.email is now unique + NOT NULL (migration b067 backfills any missing/blank email from username first, so existing rows — notably the seeded admin, which historically had none — never violate it). - /auth/login and the (unused but updated for consistency) authenticate_user() now query by email. - create_user (legacy, unreferenced but kept) and create_user_without_password both derive username from email. - update_user keeps username in sync when email changes, and rejects duplicate emails. - seed.py reads ADMIN_EMAIL (new env var, wired through install.sh and docker-compose.prod.yml) for the initial admin; falls back to an email-shaped ADMIN_USERNAME or a placeholder that's flagged for the operator to fix. - admin_config.py's import bundle now matches/creates users by email, skipping (not crashing on) entries with no email. - sso_service.py always sets username = email for SSO-provisioned users. - LoginPage/auth.ts updated to email input/copy (wire field name stays 'username' — that's the OAuth2PasswordRequestForm spec, not the value).
573 lines
27 KiB
Bash
573 lines
27 KiB
Bash
#!/bin/bash
|
|
# =============================================================================
|
|
# Aegis - Interactive Production Installer
|
|
# =============================================================================
|
|
# Sets up the Aegis platform for production with an interactive wizard
|
|
# that configures all environment variables.
|
|
#
|
|
# Usage:
|
|
# chmod +x scripts/install.sh
|
|
# ./scripts/install.sh
|
|
#
|
|
# Prerequisites:
|
|
# - Docker and Docker Compose installed
|
|
# - Port 80 (or chosen port) available
|
|
# =============================================================================
|
|
|
|
set -e
|
|
|
|
# Always run from the project root (parent of scripts/)
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
cd "$PROJECT_ROOT"
|
|
|
|
# ── Colors & helpers ──────────────────────────────────────────────────────
|
|
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
RED='\033[0;31m'
|
|
CYAN='\033[0;36m'
|
|
BOLD='\033[1m'
|
|
DIM='\033[2m'
|
|
NC='\033[0m'
|
|
|
|
print_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
|
|
print_warn() { echo -e "${YELLOW}[!]${NC} $1"; }
|
|
print_error() { echo -e "${RED}[X]${NC} $1"; }
|
|
print_info() { echo -e "${CYAN}[i]${NC} $1"; }
|
|
print_header() { echo -e "\n${BOLD}── $1 ──${NC}\n"; }
|
|
print_prompt() { echo -en "${CYAN}>>>${NC} $1"; }
|
|
|
|
# Generate a cryptographically secure random string
|
|
gen_secret() {
|
|
python3 -c "import secrets; print(secrets.token_hex($1))" 2>/dev/null \
|
|
|| openssl rand -hex "$1" 2>/dev/null \
|
|
|| head -c "$1" /dev/urandom | od -An -tx1 | tr -d ' \n'
|
|
}
|
|
|
|
gen_password() {
|
|
python3 -c "import secrets; print(secrets.token_urlsafe($1))" 2>/dev/null \
|
|
|| openssl rand -base64 "$1" 2>/dev/null \
|
|
|| head -c "$1" /dev/urandom | base64 | tr -d '=/+' | head -c "$1"
|
|
}
|
|
|
|
# ── Banner ────────────────────────────────────────────────────────────────
|
|
|
|
clear 2>/dev/null || true
|
|
echo ""
|
|
echo -e "${BOLD}"
|
|
echo " ╔══════════════════════════════════════════════════════════╗"
|
|
echo " ║ ║"
|
|
echo " ║ Aegis - Installation Wizard ║"
|
|
echo " ║ MITRE ATT&CK Coverage Platform ║"
|
|
echo " ║ ║"
|
|
echo " ╚══════════════════════════════════════════════════════════╝"
|
|
echo -e "${NC}"
|
|
|
|
# ═════════════════════════════════════════════════════════════════════════
|
|
# STEP 1: Check prerequisites
|
|
# ═════════════════════════════════════════════════════════════════════════
|
|
|
|
print_header "Step 1/5 - 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"
|
|
|
|
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)"
|
|
|
|
# Auto-detect Docker API version
|
|
DOCKER_SERVER_API=$(docker version --format '{{.Server.APIVersion}}' 2>/dev/null || echo "")
|
|
if [ -n "$DOCKER_SERVER_API" ]; then
|
|
export DOCKER_API_VERSION="$DOCKER_SERVER_API"
|
|
fi
|
|
|
|
# ═════════════════════════════════════════════════════════════════════════
|
|
# STEP 2: Interactive configuration
|
|
# ═════════════════════════════════════════════════════════════════════════
|
|
|
|
print_header "Step 2/5 - Configuration"
|
|
|
|
ENV_FILE=".env"
|
|
SKIP_CONFIG=false
|
|
|
|
if [ -f "$ENV_FILE" ]; then
|
|
print_warn "An existing .env file was found."
|
|
echo ""
|
|
print_prompt "Do you want to reconfigure? (y/N): "
|
|
read -r REPLY
|
|
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
print_info "Keeping existing configuration."
|
|
SKIP_CONFIG=true
|
|
|
|
# Older installs predate PLATFORM_URL (added when email links moved
|
|
# off the dev default) — backfill it from CORS_ORIGINS so upgrading
|
|
# in place doesn't break the backend's required-env-var check.
|
|
if ! grep -q '^PLATFORM_URL=' "$ENV_FILE" 2>/dev/null; then
|
|
EXISTING_ORIGIN=$(grep '^CORS_ORIGINS=' "$ENV_FILE" 2>/dev/null | cut -d= -f2-)
|
|
if [ -n "$EXISTING_ORIGIN" ]; then
|
|
echo "PLATFORM_URL=${EXISTING_ORIGIN}" >> "$ENV_FILE"
|
|
print_info "Added missing PLATFORM_URL=${EXISTING_ORIGIN} to existing .env"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [ "$SKIP_CONFIG" = false ]; then
|
|
|
|
echo -e " ${DIM}Answer the following questions to configure Aegis."
|
|
echo -e " Press Enter to accept the default value shown in [brackets].${NC}"
|
|
echo ""
|
|
|
|
# ── Domain / URL ──────────────────────────────────────────────────
|
|
|
|
echo -e " ${BOLD}1. Domain Configuration${NC}"
|
|
echo -e " ${DIM}The domain where Aegis will be accessible."
|
|
echo -e " Examples: aegis.example.com, 192.168.1.100, localhost${NC}"
|
|
echo ""
|
|
print_prompt "Domain or IP [localhost]: "
|
|
read -r INPUT_DOMAIN
|
|
DOMAIN="${INPUT_DOMAIN:-localhost}"
|
|
|
|
# ── Protocol ──────────────────────────────────────────────────────
|
|
|
|
if [ "$DOMAIN" = "localhost" ] || [ "$DOMAIN" = "127.0.0.1" ]; then
|
|
PROTOCOL="http"
|
|
print_info "Using HTTP for local deployment"
|
|
else
|
|
echo ""
|
|
print_prompt "Are you using HTTPS/SSL? (Y/n): "
|
|
read -r REPLY
|
|
if [[ $REPLY =~ ^[Nn]$ ]]; then
|
|
PROTOCOL="http"
|
|
else
|
|
PROTOCOL="https"
|
|
fi
|
|
fi
|
|
|
|
# ── Port ──────────────────────────────────────────────────────────
|
|
|
|
if [ "$PROTOCOL" = "https" ]; then
|
|
DEFAULT_PORT=443
|
|
else
|
|
DEFAULT_PORT=80
|
|
fi
|
|
|
|
echo ""
|
|
echo -e " ${BOLD}2. Port${NC}"
|
|
print_prompt "Frontend port [$DEFAULT_PORT]: "
|
|
read -r INPUT_PORT
|
|
FRONTEND_PORT="${INPUT_PORT:-$DEFAULT_PORT}"
|
|
|
|
# Build the full origin URL for CORS
|
|
if { [ "$PROTOCOL" = "https" ] && [ "$FRONTEND_PORT" = "443" ]; } || \
|
|
{ [ "$PROTOCOL" = "http" ] && [ "$FRONTEND_PORT" = "80" ]; }; then
|
|
ORIGIN_URL="${PROTOCOL}://${DOMAIN}"
|
|
else
|
|
ORIGIN_URL="${PROTOCOL}://${DOMAIN}:${FRONTEND_PORT}"
|
|
fi
|
|
|
|
# ── Admin account ─────────────────────────────────────────────────
|
|
|
|
echo ""
|
|
echo -e " ${BOLD}3. Admin Account${NC}"
|
|
echo -e " ${DIM}The initial administrator account for Aegis.${NC}"
|
|
echo ""
|
|
print_prompt "Admin username [admin]: "
|
|
read -r INPUT_ADMIN_USER
|
|
ADMIN_USERNAME="${INPUT_ADMIN_USER:-admin}"
|
|
|
|
echo ""
|
|
echo -e " ${DIM}Email is what you'll actually log in with (the unique identifier).${NC}"
|
|
print_prompt "Admin email: "
|
|
read -r INPUT_ADMIN_EMAIL
|
|
while [ -z "$INPUT_ADMIN_EMAIL" ]; do
|
|
print_warn "An admin email is required."
|
|
print_prompt "Admin email: "
|
|
read -r INPUT_ADMIN_EMAIL
|
|
done
|
|
ADMIN_EMAIL="$INPUT_ADMIN_EMAIL"
|
|
|
|
echo ""
|
|
echo -e " ${DIM}Leave empty to auto-generate a secure password."
|
|
echo -e " The password will be shown in the installation summary.${NC}"
|
|
print_prompt "Admin password [auto-generate]: "
|
|
read -rs INPUT_ADMIN_PASS
|
|
echo ""
|
|
ADMIN_PASSWORD="${INPUT_ADMIN_PASS}"
|
|
|
|
if [ -z "$ADMIN_PASSWORD" ]; then
|
|
ADMIN_PASSWORD=$(gen_password 18)
|
|
ADMIN_PW_GENERATED=true
|
|
print_info "Password will be auto-generated"
|
|
else
|
|
ADMIN_PW_GENERATED=false
|
|
print_ok "Password set"
|
|
fi
|
|
|
|
# ── Database ──────────────────────────────────────────────────────
|
|
|
|
echo ""
|
|
echo -e " ${BOLD}4. Database${NC}"
|
|
print_prompt "Database name [attackdb]: "
|
|
read -r INPUT_DB_NAME
|
|
DB_NAME="${INPUT_DB_NAME:-attackdb}"
|
|
|
|
print_prompt "Database user [postgres]: "
|
|
read -r INPUT_DB_USER
|
|
DB_USER="${INPUT_DB_USER:-postgres}"
|
|
|
|
echo -e " ${DIM}Leave empty to auto-generate a secure password.${NC}"
|
|
print_prompt "Database password [auto-generate]: "
|
|
read -rs INPUT_DB_PASS
|
|
echo ""
|
|
if [ -z "$INPUT_DB_PASS" ]; then
|
|
DB_PASSWORD=$(gen_password 24)
|
|
print_info "Database password auto-generated"
|
|
else
|
|
DB_PASSWORD="$INPUT_DB_PASS"
|
|
print_ok "Database password set"
|
|
fi
|
|
|
|
# ── Token expiry ──────────────────────────────────────────────────
|
|
|
|
echo ""
|
|
echo -e " ${BOLD}5. Session Duration${NC}"
|
|
print_prompt "Token expiry in minutes [60]: "
|
|
read -r INPUT_TOKEN_EXP
|
|
TOKEN_EXPIRE_MINUTES="${INPUT_TOKEN_EXP:-60}"
|
|
|
|
# ── MITRE sync ────────────────────────────────────────────────────
|
|
|
|
echo ""
|
|
echo -e " ${BOLD}6. Initial Data${NC}"
|
|
print_prompt "Run MITRE ATT&CK sync after install? (Y/n): "
|
|
read -r INPUT_SYNC
|
|
if [[ $INPUT_SYNC =~ ^[Nn]$ ]]; then
|
|
RUN_MITRE_SYNC=false
|
|
else
|
|
RUN_MITRE_SYNC=true
|
|
fi
|
|
|
|
# ── Generate secrets ──────────────────────────────────────────────
|
|
|
|
SECRET_KEY=$(gen_secret 32)
|
|
MINIO_ACCESS=$(gen_secret 8)
|
|
MINIO_SECRET=$(gen_password 24)
|
|
|
|
# ── Show summary before writing ──────────────────────────────────
|
|
|
|
echo ""
|
|
echo -e "${BOLD} ┌──────────────────────────────────────────────────────┐${NC}"
|
|
echo -e "${BOLD} │ Configuration Summary │${NC}"
|
|
echo -e "${BOLD} ├──────────────────────────────────────────────────────┤${NC}"
|
|
echo -e " │ URL: ${CYAN}${ORIGIN_URL}${NC}"
|
|
echo -e " │ Admin user: ${CYAN}${ADMIN_USERNAME}${NC}"
|
|
echo -e " │ Admin email: ${CYAN}${ADMIN_EMAIL}${NC}"
|
|
if [ "$ADMIN_PW_GENERATED" = true ]; then
|
|
echo -e " │ Admin pass: ${CYAN}(auto-generated)${NC}"
|
|
else
|
|
echo -e " │ Admin pass: ${CYAN}(custom)${NC}"
|
|
fi
|
|
echo -e " │ Database: ${CYAN}${DB_USER}@${DB_NAME}${NC}"
|
|
echo -e " │ Port: ${CYAN}${FRONTEND_PORT}${NC}"
|
|
echo -e " │ Session TTL: ${CYAN}${TOKEN_EXPIRE_MINUTES} min${NC}"
|
|
echo -e " │ MITRE sync: ${CYAN}$([ "$RUN_MITRE_SYNC" = true ] && echo "yes" || echo "no")${NC}"
|
|
echo -e "${BOLD} └──────────────────────────────────────────────────────┘${NC}"
|
|
echo ""
|
|
# Warn about data loss if containers/volumes already exist
|
|
if $COMPOSE_CMD -f docker-compose.prod.yml ps -q 2>/dev/null | grep -q . || \
|
|
docker volume ls --format '{{.Name}}' 2>/dev/null | grep -qi 'postgres'; then
|
|
echo ""
|
|
print_warn "Existing database volumes detected."
|
|
print_warn "Proceeding will RESET the database — all existing data will be lost."
|
|
fi
|
|
|
|
print_prompt "Proceed with these settings? (Y/n): "
|
|
read -r CONFIRM
|
|
if [[ $CONFIRM =~ ^[Nn]$ ]]; then
|
|
print_warn "Installation cancelled. Run the script again to reconfigure."
|
|
exit 0
|
|
fi
|
|
|
|
# ── Write .env ────────────────────────────────────────────────────
|
|
|
|
cat > "$ENV_FILE" <<ENVEOF
|
|
# =============================================================================
|
|
# Aegis Production Environment
|
|
# Generated by install.sh on $(date -u +"%Y-%m-%d %H:%M:%S UTC")
|
|
# =============================================================================
|
|
|
|
# ── Database ─────────────────────────────────────────────────────────────────
|
|
DB_USER=${DB_USER}
|
|
DB_PASSWORD=${DB_PASSWORD}
|
|
DB_NAME=${DB_NAME}
|
|
|
|
# ── Security ─────────────────────────────────────────────────────────────────
|
|
SECRET_KEY=${SECRET_KEY}
|
|
TOKEN_EXPIRE_MINUTES=${TOKEN_EXPIRE_MINUTES}
|
|
|
|
# ── Initial Admin Account ────────────────────────────────────────────────────
|
|
ADMIN_USERNAME=${ADMIN_USERNAME}
|
|
ADMIN_EMAIL=${ADMIN_EMAIL}
|
|
ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
|
|
|
# ── MinIO Object Storage ─────────────────────────────────────────────────────
|
|
MINIO_ACCESS_KEY=${MINIO_ACCESS}
|
|
MINIO_SECRET_KEY=${MINIO_SECRET}
|
|
MINIO_BUCKET=evidence
|
|
|
|
# ── CORS (allowed frontend origins) ─────────────────────────────────────────
|
|
CORS_ORIGINS=${ORIGIN_URL}
|
|
|
|
# ── Emails ────────────────────────────────────────────────────────────────────
|
|
# Base URL used to build links in outbound emails (set-password, etc).
|
|
PLATFORM_URL=${ORIGIN_URL}
|
|
|
|
# ── Frontend ─────────────────────────────────────────────────────────────────
|
|
FRONTEND_PORT=${FRONTEND_PORT}
|
|
|
|
# ── Environment ──────────────────────────────────────────────────────────────
|
|
AEGIS_ENV=production
|
|
ENVEOF
|
|
|
|
print_ok ".env file created with secure configuration"
|
|
|
|
fi # end SKIP_CONFIG
|
|
|
|
# ═════════════════════════════════════════════════════════════════════════
|
|
# STEP 3: Build and start
|
|
# ═════════════════════════════════════════════════════════════════════════
|
|
|
|
print_header "Step 3/5 - Building and starting containers"
|
|
|
|
print_info "This may take several minutes on first run..."
|
|
|
|
# When reconfiguring, remove old volumes so Postgres re-initializes with the new password.
|
|
# Without this, Postgres ignores the new DB_PASSWORD because it only sets credentials
|
|
# on first initialization (empty volume) — leaving the backend unable to authenticate.
|
|
if [ "$SKIP_CONFIG" = false ]; then
|
|
print_info "Removing existing volumes to apply new credentials..."
|
|
$COMPOSE_CMD -f docker-compose.prod.yml down -v > /dev/null 2>&1 || true
|
|
fi
|
|
|
|
if ! $COMPOSE_CMD -f docker-compose.prod.yml up -d --build 2>&1; then
|
|
print_error "Failed to build/start containers. Check the output above."
|
|
exit 1
|
|
fi
|
|
|
|
print_ok "Containers started"
|
|
|
|
# ═════════════════════════════════════════════════════════════════════════
|
|
# STEP 4: Wait for services
|
|
# ═════════════════════════════════════════════════════════════════════════
|
|
|
|
print_header "Step 4/5 - Waiting for services to be ready"
|
|
|
|
# Wait for PostgreSQL
|
|
echo -en " 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
|
|
echo ""
|
|
print_error "PostgreSQL failed to start. Check: docker logs aegis-postgres"
|
|
exit 1
|
|
fi
|
|
echo -n "."
|
|
sleep 2
|
|
done
|
|
echo -e " ${GREEN}ready${NC}"
|
|
|
|
# Wait for backend (runs migrations + seed)
|
|
echo -en " Backend (migrations + seed) ..."
|
|
RETRY=0
|
|
until docker exec aegis-backend curl -sf http://localhost:8000/health > /dev/null 2>&1; do
|
|
RETRY=$((RETRY + 1))
|
|
if [ $RETRY -ge 90 ]; then
|
|
echo ""
|
|
print_error "Backend failed to start after 3 minutes."
|
|
echo " Check: docker logs aegis-backend"
|
|
exit 1
|
|
fi
|
|
echo -n "."
|
|
sleep 2
|
|
done
|
|
echo -e " ${GREEN}ready${NC}"
|
|
|
|
# Wait for frontend
|
|
FRONTEND_PORT=$(grep FRONTEND_PORT "$ENV_FILE" 2>/dev/null | cut -d= -f2 || echo "80")
|
|
FRONTEND_PORT=${FRONTEND_PORT:-80}
|
|
|
|
echo -en " Frontend ..."
|
|
RETRY=0
|
|
until curl -sf "http://localhost:${FRONTEND_PORT}" > /dev/null 2>&1; do
|
|
RETRY=$((RETRY + 1))
|
|
if [ $RETRY -ge 30 ]; then
|
|
echo ""
|
|
print_error "Frontend failed to start. Check: docker logs aegis-frontend"
|
|
exit 1
|
|
fi
|
|
echo -n "."
|
|
sleep 2
|
|
done
|
|
echo -e " ${GREEN}ready${NC}"
|
|
|
|
print_ok "All services are running"
|
|
|
|
# ── Extract admin credentials from backend logs ──────────────────────
|
|
# Login is by email (the unique identifier) — ADMIN_CREDS_USER holds that
|
|
# email despite the variable name, kept for minimal diff below.
|
|
|
|
ADMIN_CREDS_USER=""
|
|
ADMIN_CREDS_PASS=""
|
|
|
|
# Try to extract the credentials from the backend startup logs
|
|
LOG_OUTPUT=$(docker logs aegis-backend 2>&1 | tail -20)
|
|
|
|
if echo "$LOG_OUTPUT" | grep -q "Initial Admin User Created"; then
|
|
ADMIN_CREDS_USER=$(echo "$LOG_OUTPUT" | grep "Login (email) :" | sed 's/.*Login (email) : //')
|
|
ADMIN_CREDS_PASS=$(echo "$LOG_OUTPUT" | grep "Password :" | sed 's/.*Password : //')
|
|
fi
|
|
|
|
# Fallback: if we set it via env, use those values
|
|
if [ -z "$ADMIN_CREDS_USER" ]; then
|
|
ADMIN_CREDS_USER=$(grep '^ADMIN_EMAIL=' "$ENV_FILE" 2>/dev/null | cut -d= -f2-)
|
|
fi
|
|
|
|
if [ -z "$ADMIN_CREDS_PASS" ] || [ "$ADMIN_CREDS_PASS" = "(set via ADMIN_PASSWORD env var)" ]; then
|
|
ADMIN_CREDS_PASS=$(grep ADMIN_PASSWORD "$ENV_FILE" 2>/dev/null | cut -d= -f2 || echo "")
|
|
fi
|
|
|
|
# ═════════════════════════════════════════════════════════════════════════
|
|
# STEP 5: Initial data sync (optional)
|
|
# ═════════════════════════════════════════════════════════════════════════
|
|
|
|
# Re-read RUN_MITRE_SYNC if we skipped config
|
|
if [ "$SKIP_CONFIG" = true ]; then
|
|
echo ""
|
|
print_prompt "Run initial MITRE ATT&CK sync? (~700 techniques, 1-2 min) (Y/n): "
|
|
read -r INPUT_SYNC
|
|
if [[ $INPUT_SYNC =~ ^[Nn]$ ]]; then
|
|
RUN_MITRE_SYNC=false
|
|
else
|
|
RUN_MITRE_SYNC=true
|
|
fi
|
|
fi
|
|
|
|
if [ "$RUN_MITRE_SYNC" = true ]; then
|
|
print_header "Step 5/5 - Initial data sync"
|
|
|
|
print_info "Authenticating with backend..."
|
|
|
|
# Authenticate via backend container (most reliable)
|
|
TOKEN=$(docker exec aegis-backend curl -sf -X POST "http://localhost:8000/api/v1/auth/login" \
|
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
-d "username=${ADMIN_CREDS_USER}&password=${ADMIN_CREDS_PASS}" 2>/dev/null | \
|
|
python3 -c "import sys,json; print(json.load(sys.stdin).get('access_token',''))" 2>/dev/null || echo "")
|
|
|
|
if [ -n "$TOKEN" ] && [ "$TOKEN" != "" ]; then
|
|
|
|
# MITRE ATT&CK sync
|
|
print_info "Syncing MITRE ATT&CK techniques (1-2 minutes)..."
|
|
SYNC_RESULT=$(docker exec aegis-backend curl -sf --max-time 300 \
|
|
-X POST "http://localhost:8000/api/v1/system/sync-mitre" \
|
|
-H "Authorization: Bearer $TOKEN" 2>/dev/null || echo "error")
|
|
|
|
if [ "$SYNC_RESULT" != "error" ]; then
|
|
NEW_TECHNIQUES=$(echo "$SYNC_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('new',0))" 2>/dev/null || echo "?")
|
|
print_ok "MITRE sync completed ($NEW_TECHNIQUES techniques imported)"
|
|
else
|
|
print_warn "MITRE sync timed out. You can retry from the System page."
|
|
fi
|
|
|
|
# Data sources sync
|
|
print_info "Syncing data sources (Atomic Red Team, SigmaHQ, etc.)..."
|
|
SOURCES=$(docker exec aegis-backend curl -sf "http://localhost:8000/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 || echo "")
|
|
|
|
SYNC_COUNT=0
|
|
for source_id in $SOURCES; do
|
|
docker exec aegis-backend curl -sf --max-time 120 \
|
|
-X POST "http://localhost:8000/api/v1/data-sources/${source_id}/sync" \
|
|
-H "Authorization: Bearer $TOKEN" > /dev/null 2>&1 && SYNC_COUNT=$((SYNC_COUNT + 1)) || true
|
|
done
|
|
if [ "$SYNC_COUNT" -gt 0 ]; then
|
|
print_ok "Data sources synced ($SYNC_COUNT sources)"
|
|
fi
|
|
else
|
|
print_warn "Could not authenticate. Run MITRE sync from the System page."
|
|
fi
|
|
else
|
|
print_header "Step 5/5 - Skipping data sync"
|
|
print_info "You can import data later from the System page."
|
|
fi
|
|
|
|
# ═════════════════════════════════════════════════════════════════════════
|
|
# FINAL SUMMARY
|
|
# ═════════════════════════════════════════════════════════════════════════
|
|
|
|
# Build the access URL
|
|
ORIGIN_URL=$(grep CORS_ORIGINS "$ENV_FILE" 2>/dev/null | cut -d= -f2 || echo "http://localhost")
|
|
SERVER_IP=$(hostname -I 2>/dev/null | awk '{print $1}' || echo "localhost")
|
|
|
|
echo ""
|
|
echo ""
|
|
echo -e "${BOLD}╔══════════════════════════════════════════════════════════════╗${NC}"
|
|
echo -e "${BOLD}║ ║${NC}"
|
|
echo -e "${BOLD}║ ${GREEN}Aegis is ready!${NC}${BOLD} ║${NC}"
|
|
echo -e "${BOLD}║ ║${NC}"
|
|
echo -e "${BOLD}╠══════════════════════════════════════════════════════════════╣${NC}"
|
|
echo -e "${BOLD}║${NC} ${BOLD}║${NC}"
|
|
echo -e "${BOLD}║${NC} ${CYAN}Application${NC} ${ORIGIN_URL}"
|
|
echo -e "${BOLD}║${NC} ${CYAN}Local IP${NC} http://${SERVER_IP}:${FRONTEND_PORT}"
|
|
echo -e "${BOLD}║${NC} ${BOLD}║${NC}"
|
|
echo -e "${BOLD}╠══════════════════════════════════════════════════════════════╣${NC}"
|
|
echo -e "${BOLD}║${NC} ${BOLD}║${NC}"
|
|
echo -e "${BOLD}║${NC} ${CYAN}Admin Login${NC} ${BOLD}║${NC}"
|
|
echo -e "${BOLD}║${NC} Email: ${GREEN}${ADMIN_CREDS_USER}${NC}"
|
|
if [ -n "$ADMIN_CREDS_PASS" ]; then
|
|
echo -e "${BOLD}║${NC} Password: ${GREEN}${ADMIN_CREDS_PASS}${NC}"
|
|
else
|
|
echo -e "${BOLD}║${NC} Password: ${YELLOW}(check: docker logs aegis-backend | grep Password)${NC}"
|
|
fi
|
|
echo -e "${BOLD}║${NC} ${BOLD}║${NC}"
|
|
echo -e "${BOLD}╠══════════════════════════════════════════════════════════════╣${NC}"
|
|
echo -e "${BOLD}║${NC} ${BOLD}║${NC}"
|
|
echo -e "${BOLD}║${NC} ${YELLOW}Important:${NC} ${BOLD}║${NC}"
|
|
echo -e "${BOLD}║${NC} - Save the admin password now if auto-generated ${BOLD}║${NC}"
|
|
echo -e "${BOLD}║${NC} - Set up HTTPS/TLS for internet-facing deployments ${BOLD}║${NC}"
|
|
echo -e "${BOLD}║${NC} - Configure firewall rules as needed ${BOLD}║${NC}"
|
|
echo -e "${BOLD}║${NC} - Set up regular database backups ${BOLD}║${NC}"
|
|
echo -e "${BOLD}║${NC} ${BOLD}║${NC}"
|
|
echo -e "${BOLD}╚══════════════════════════════════════════════════════════════╝${NC}"
|
|
echo ""
|
|
echo -e "${BOLD}Useful commands:${NC}"
|
|
echo -e " ${DIM}View logs${NC} docker logs -f aegis-backend"
|
|
echo -e " ${DIM}Stop${NC} $COMPOSE_CMD -f docker-compose.prod.yml down"
|
|
echo -e " ${DIM}Restart${NC} $COMPOSE_CMD -f docker-compose.prod.yml restart"
|
|
echo -e " ${DIM}Update${NC} $COMPOSE_CMD -f docker-compose.prod.yml up -d --build"
|
|
echo -e " ${DIM}DB backup${NC} docker exec aegis-postgres pg_dump -U postgres ${DB_NAME:-attackdb} > backup.sql"
|
|
echo -e " ${DIM}Reconfigure${NC} ./scripts/install.sh"
|
|
echo ""
|