changemaker.lite/config.sh
bunker-admin 99a6abab06 Add video card insert feature + MkDocs video hydration + fixes
- New video card block for GrapesJS landing pages, email templates,
  MkDocs export, and documentation editor Insert dropdown
- Shared HTML generators in admin/src/utils/videoCardHtml.ts
- MkDocs video-player.js hydrates .video-card-block elements:
  thumbnail fix via MEDIA_API_URL, click-to-play inline, Gallery link
- Media API CORS: auto-add MkDocs + docs subdomain origins
- env_config_hook.py: smart Docker hostname detection, ADMIN_PORT
  resolution, pass env vars to MkDocs container
- Gallery URL uses /gallery?expanded=ID format
- VideoPickerModal: fix double /api prefix and Docker hostname thumbs
- Seed: default-video-card PageBlock
- Remove V1 legacy code (influence/, map/)

Bunker Admin
2026-02-17 15:42:32 -07:00

871 lines
27 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# =============================================================================
# Changemaker Lite V2 — Configuration Wizard
# Produces a working .env file and stages the full application stack.
# =============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_FILE="$SCRIPT_DIR/.env"
ENV_EXAMPLE="$SCRIPT_DIR/.env.example"
MKDOCS_YML="$SCRIPT_DIR/mkdocs/mkdocs.yml"
SERVICES_YAML="$SCRIPT_DIR/configs/homepage/services.yaml"
# --- Colors (respects NO_COLOR convention) ---
if [[ -t 1 ]] && [[ -z "${NO_COLOR:-}" ]]; then
RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m'
BLUE='\033[0;34m' CYAN='\033[0;36m' BOLD='\033[1m'
DIM='\033[2m' NC='\033[0m'
else
RED='' GREEN='' YELLOW='' BLUE='' CYAN='' BOLD='' DIM='' NC=''
fi
# =============================================================================
# Utility Functions
# =============================================================================
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
success() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERR]${NC} $*" >&2; }
header() {
echo ""
echo -e "${BOLD}${BLUE}── $* ──${NC}"
echo ""
}
generate_secret() {
openssl rand -hex 32
}
generate_password() {
local length=${1:-24}
openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c "$length"
}
# Line-by-line .env replacement — robust with special characters
update_env_var() {
local key=$1
local value=$2
if grep -q "^${key}=" "$ENV_FILE"; then
local tmpfile
tmpfile=$(mktemp)
while IFS= read -r line; do
if [[ "$line" =~ ^${key}= ]]; then
echo "${key}=${value}" >> "$tmpfile"
else
echo "$line" >> "$tmpfile"
fi
done < "$ENV_FILE"
mv "$tmpfile" "$ENV_FILE"
else
echo "${key}=${value}" >> "$ENV_FILE"
fi
}
backup_env_file() {
if [[ -f "$ENV_FILE" ]]; then
local ts
ts=$(date +"%Y%m%d_%H%M%S")
local backup="$ENV_FILE.backup_$ts"
cp "$ENV_FILE" "$backup"
success "Backed up .env to ${backup##*/}"
fi
}
validate_password() {
local pw=$1
[[ ${#pw} -ge 12 ]] || return 1
[[ "$pw" =~ [A-Z] ]] || return 1
[[ "$pw" =~ [a-z] ]] || return 1
[[ "$pw" =~ [0-9] ]] || return 1
return 0
}
prompt_yes_no() {
local prompt=$1 default=${2:-n}
local yn
if [[ "$default" == "y" ]]; then
read -rp "$prompt [Y/n]: " yn
[[ "$yn" =~ ^[Nn]$ ]] && return 1 || return 0
else
read -rp "$prompt [y/N]: " yn
[[ "$yn" =~ ^[Yy]$ ]] && return 0 || return 1
fi
}
# =============================================================================
# Prerequisites
# =============================================================================
check_prerequisites() {
header "Checking Prerequisites"
local ok=true
if command -v docker &>/dev/null; then
success "Docker found: $(docker --version | head -1)"
else
error "Docker is not installed. See https://docs.docker.com/get-docker/"
ok=false
fi
if docker compose version &>/dev/null; then
success "Docker Compose found: $(docker compose version --short)"
else
error "Docker Compose v2 plugin not found. See https://docs.docker.com/compose/install/"
ok=false
fi
if command -v openssl &>/dev/null; then
success "OpenSSL found"
else
error "OpenSSL is not installed (needed for secret generation)"
ok=false
fi
$ok || { echo ""; error "Missing prerequisites. Install them and re-run."; exit 1; }
}
# =============================================================================
# Banner
# =============================================================================
print_banner() {
cat << 'EOF'
██████╗██╗ ██╗ █████╗ ███╗ ██╗ ██████╗ ███████╗
██╔════╝██║ ██║██╔══██╗████╗ ██║██╔════╝ ██╔════╝
██║ ███████║███████║██╔██╗ ██║██║ ███╗█████╗
██║ ██╔══██║██╔══██║██║╚██╗██║██║ ██║██╔══╝
╚██████╗██║ ██║██║ ██║██║ ╚████║╚██████╔╝███████╗
╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚══════╝
███╗ ███╗ █████╗ ██╗ ██╗███████╗██████╗
████╗ ████║██╔══██╗██║ ██╔╝██╔════╝██╔══██╗
██╔████╔██║███████║█████╔╝ █████╗ ██████╔╝
██║╚██╔╝██║██╔══██║██╔═██╗ ██╔══╝ ██╔══██╗
██║ ╚═╝ ██║██║ ██║██║ ██╗███████╗██║ ██║
╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
V2 Configuration Wizard
EOF
echo ""
info "This wizard will create your .env file, generate secure secrets,"
info "and prepare your system to run the full Changemaker Lite stack."
echo ""
}
# =============================================================================
# .env Initialization
# =============================================================================
initialize_env() {
header "Environment File Setup"
if [[ ! -f "$ENV_EXAMPLE" ]]; then
error ".env.example not found at $ENV_EXAMPLE"
error "Make sure you're running this from the project root."
exit 1
fi
if [[ -f "$ENV_FILE" ]]; then
warn "Existing .env file found at $ENV_FILE"
if prompt_yes_no "Back up existing .env and create a fresh one?"; then
backup_env_file
cp "$ENV_EXAMPLE" "$ENV_FILE"
success "Created fresh .env from .env.example"
else
info "Keeping existing .env. Will update values in place."
fi
else
cp "$ENV_EXAMPLE" "$ENV_FILE"
success "Created .env from .env.example"
fi
}
# =============================================================================
# Configuration Sections
# =============================================================================
configure_domain() {
header "Domain Configuration"
info "Root domain serves MkDocs documentation."
info "All application routes are at app.<domain>."
echo ""
read -rp "Enter your domain (e.g., example.org) [default: cmlite.org]: " domain
domain=${domain:-cmlite.org}
update_env_var "DOMAIN" "$domain"
update_env_var "BASE_DOMAIN" "https://$domain"
update_env_var "GITEA_ROOT_URL" "https://git.$domain"
update_env_var "GITEA_DOMAIN" "git.$domain"
update_env_var "N8N_HOST" "n8n.$domain"
update_env_var "SMTP_FROM" "noreply@$domain"
update_env_var "SMTP_FROM_NAME" "Changemaker Lite"
update_env_var "INITIAL_ADMIN_EMAIL" "admin@$domain"
update_env_var "NC_ADMIN_EMAIL" "admin@$domain"
update_env_var "TEST_EMAIL_RECIPIENT" "admin@$domain"
update_env_var "EXCALIDRAW_WS_URL" "wss://draw.$domain"
update_env_var "LISTMONK_SMTP_FROM" "Changemaker Lite <noreply@$domain>"
update_env_var "HOMEPAGE_VAR_BASE_URL" "https://$domain"
# Update mkdocs.yml
if [[ -f "$MKDOCS_YML" ]]; then
sed -i "s|^site_url:.*|site_url: https://$domain|" "$MKDOCS_YML"
sed -i "s|^repo_url:.*|repo_url: https://git.$domain/admin/changemaker.lite|" "$MKDOCS_YML"
success "Updated mkdocs.yml (site_url, repo_url)"
else
warn "mkdocs.yml not found — skipping"
fi
success "Domain set to: $domain"
# Store for later use
CONFIGURED_DOMAIN="$domain"
}
configure_admin() {
header "Admin Credentials"
local default_email="admin@${CONFIGURED_DOMAIN:-cmlite.org}"
read -rp "Admin email [default: $default_email]: " admin_email
admin_email=${admin_email:-$default_email}
local admin_password=""
while true; do
echo ""
read -rsp "Admin password (min 12 chars, uppercase + lowercase + digit): " admin_password
echo ""
if validate_password "$admin_password"; then
read -rsp "Confirm password: " confirm_password
echo ""
if [[ "$admin_password" == "$confirm_password" ]]; then
break
else
warn "Passwords do not match. Try again."
fi
else
warn "Password must be 12+ characters with uppercase, lowercase, and a digit."
fi
done
update_env_var "INITIAL_ADMIN_EMAIL" "$admin_email"
update_env_var "INITIAL_ADMIN_PASSWORD" "$admin_password"
update_env_var "N8N_USER_EMAIL" "$admin_email"
success "Admin credentials configured"
}
generate_all_secrets() {
header "Generating Secrets"
info "Auto-generating 16 unique secrets and passwords..."
echo ""
# JWT & Encryption (64-char hex)
local jwt_access jwt_refresh enc_key
jwt_access=$(generate_secret)
jwt_refresh=$(generate_secret)
enc_key=$(generate_secret)
update_env_var "JWT_ACCESS_SECRET" "$jwt_access"
update_env_var "JWT_REFRESH_SECRET" "$jwt_refresh"
update_env_var "ENCRYPTION_KEY" "$enc_key"
success "JWT secrets + encryption key"
# Database passwords (24-char alphanum)
local pg_pass redis_pass
pg_pass=$(generate_password 24)
redis_pass=$(generate_password 24)
update_env_var "V2_POSTGRES_PASSWORD" "$pg_pass"
update_env_var "REDIS_PASSWORD" "$redis_pass"
update_env_var "REDIS_URL" "redis://:${redis_pass}@redis-changemaker:6379"
success "PostgreSQL + Redis passwords"
# Listmonk
local lm_db_pass lm_web_pass lm_api_token
lm_db_pass=$(generate_password 24)
lm_web_pass=$(generate_password 20)
lm_api_token=$(openssl rand -hex 16)
update_env_var "LISTMONK_DB_PASSWORD" "$lm_db_pass"
update_env_var "LISTMONK_WEB_ADMIN_PASSWORD" "$lm_web_pass"
update_env_var "LISTMONK_API_TOKEN" "$lm_api_token"
update_env_var "LISTMONK_ADMIN_PASSWORD" "$lm_api_token"
success "Listmonk passwords + API token"
# NocoDB
local nc_pass
nc_pass=$(generate_password 20)
update_env_var "NC_ADMIN_PASSWORD" "$nc_pass"
success "NocoDB admin password"
# Gitea
local gitea_db gitea_root
gitea_db=$(generate_password 20)
gitea_root=$(generate_password 20)
update_env_var "GITEA_DB_PASSWD" "$gitea_db"
update_env_var "GITEA_DB_ROOT_PASSWORD" "$gitea_root"
success "Gitea database passwords"
# n8n
local n8n_enc n8n_pass
n8n_enc=$(generate_password 32)
n8n_pass=$(generate_password 20)
update_env_var "N8N_ENCRYPTION_KEY" "$n8n_enc"
update_env_var "N8N_USER_PASSWORD" "$n8n_pass"
success "n8n encryption key + admin password"
# Monitoring
local grafana_pass gotify_pass
grafana_pass=$(generate_password 20)
gotify_pass=$(generate_password 20)
update_env_var "GRAFANA_ADMIN_PASSWORD" "$grafana_pass"
update_env_var "GOTIFY_ADMIN_PASSWORD" "$gotify_pass"
success "Grafana + Gotify admin passwords"
echo ""
success "All 16 secrets generated. No placeholder passwords remain."
}
configure_smtp() {
header "Email Configuration"
info "By default, emails are captured by MailHog (test mode)."
info "You can configure a production SMTP server now or later."
echo ""
if prompt_yes_no "Configure production SMTP now?"; then
read -rp " SMTP host (e.g., smtp.protonmail.ch): " smtp_host
read -rp " SMTP port (e.g., 587): " smtp_port
read -rp " SMTP user: " smtp_user
read -rsp " SMTP password: " smtp_pass
echo ""
update_env_var "SMTP_HOST" "$smtp_host"
update_env_var "SMTP_PORT" "$smtp_port"
update_env_var "SMTP_USER" "$smtp_user"
update_env_var "SMTP_PASS" "$smtp_pass"
update_env_var "EMAIL_TEST_MODE" "false"
if prompt_yes_no " Also use this SMTP for Listmonk newsletters?"; then
update_env_var "LISTMONK_SMTP_HOST" "$smtp_host"
update_env_var "LISTMONK_SMTP_PORT" "$smtp_port"
update_env_var "LISTMONK_SMTP_USER" "$smtp_user"
update_env_var "LISTMONK_SMTP_PASSWORD" "$smtp_pass"
update_env_var "LISTMONK_SMTP_TLS_TYPE" "STARTTLS"
success "Listmonk SMTP configured"
fi
success "Production SMTP configured"
SMTP_MODE="production"
else
info "Using MailHog for email testing (port 8025)"
SMTP_MODE="mailhog"
fi
}
configure_features() {
header "Feature Flags"
if prompt_yes_no "Enable Media Manager (video library)?"; then
update_env_var "ENABLE_MEDIA_FEATURES" "true"
success "Media Manager enabled"
MEDIA_ENABLED="yes"
else
MEDIA_ENABLED="no"
fi
if prompt_yes_no "Enable Listmonk newsletter sync?"; then
update_env_var "LISTMONK_SYNC_ENABLED" "true"
success "Listmonk sync enabled"
LISTMONK_SYNC="yes"
else
LISTMONK_SYNC="no"
fi
}
configure_pangolin() {
header "Tunnel Configuration (Pangolin)"
info "Pangolin provides secure public access to your services."
info "Skip this if you'll configure tunneling later."
echo ""
if prompt_yes_no "Configure Pangolin tunnel now?"; then
read -rp " Pangolin API URL [default: https://api.bnkserve.org/v1]: " pang_url
pang_url=${pang_url:-https://api.bnkserve.org/v1}
read -rp " Pangolin API key: " pang_key
read -rp " Pangolin Organization ID: " pang_org
update_env_var "PANGOLIN_API_URL" "$pang_url"
update_env_var "PANGOLIN_API_KEY" "$pang_key"
update_env_var "PANGOLIN_ORG_ID" "$pang_org"
success "Pangolin configured"
info "Complete tunnel setup in the admin GUI at /app/pangolin after starting services."
PANGOLIN_CONFIGURED="yes"
else
PANGOLIN_CONFIGURED="no"
fi
}
configure_cors() {
local domain="${CONFIGURED_DOMAIN:-cmlite.org}"
if [[ "$domain" != "cmlite.org" ]]; then
local origins="http://app.$domain,https://app.$domain,http://localhost:3000,http://localhost"
update_env_var "CORS_ORIGINS" "$origins"
success "CORS origins set for $domain"
fi
}
# =============================================================================
# Homepage services.yaml
# =============================================================================
generate_services_yaml() {
local domain="${CONFIGURED_DOMAIN:-cmlite.org}"
mkdir -p "$(dirname "$SERVICES_YAML")"
cat > "$SERVICES_YAML" << YAML
---
# Homepage Services Configuration — Generated by config.sh
# Production tab: public URLs via $domain
# Local tab: localhost URLs with ports
- Production - Core:
- Admin GUI:
icon: mdi-view-dashboard
href: "https://app.$domain"
description: Application dashboard and public pages
container: changemaker-v2-admin
- API:
icon: mdi-api
href: "https://api.$domain"
description: V2 REST API
container: changemaker-v2-api
- Media API:
icon: mdi-video
href: "https://media.$domain"
description: Video library and streaming
container: changemaker-media-api
- Main Site:
icon: mdi-web
href: "https://$domain"
description: Documentation and marketing site
container: mkdocs-site-server-changemaker
- Production - Tools:
- Code Server:
icon: mdi-code-braces
href: "https://code.$domain"
description: VS Code in the browser
container: code-server-changemaker
- NocoDB:
icon: mdi-database
href: "https://db.$domain"
description: Database browser (read-only)
container: changemaker-v2-nocodb
- MkDocs (Live):
icon: mdi-book-open-page-variant
href: "https://docs.$domain"
description: Live documentation with hot reload
container: mkdocs-changemaker
- Mini QR:
icon: mdi-qrcode
href: "https://qr.$domain"
description: QR code generator
container: mini-qr
- Excalidraw:
icon: mdi-draw
href: "https://draw.$domain"
description: Collaborative whiteboard
container: excalidraw-changemaker
- Production - Integrations:
- Listmonk:
icon: mdi-email-newsletter
href: "https://listmonk.$domain"
description: Newsletter and mailing list manager
container: listmonk-app
- MailHog:
icon: mdi-email-check
href: "https://mail.$domain"
description: Email capture for testing
container: mailhog-changemaker
- n8n:
icon: mdi-robot-industrial
href: "https://n8n.$domain"
description: Workflow automation platform
container: n8n-changemaker
- Gitea:
icon: mdi-git
href: "https://git.$domain"
description: Git repository hosting
container: gitea-changemaker
- Production - Infrastructure:
- Nginx:
icon: mdi-web-box
href: "#"
description: Reverse proxy (subdomain routing)
container: changemaker-v2-nginx
- PostgreSQL:
icon: mdi-database-outline
href: "#"
description: Primary database (V2)
container: changemaker-v2-postgres
- Redis:
icon: mdi-database-sync
href: "#"
description: Cache, rate limiting, job queues
container: redis-changemaker
- Production - Monitoring:
- Grafana:
icon: mdi-chart-box
href: "https://grafana.$domain"
description: Monitoring dashboards
container: grafana-changemaker
- Prometheus:
icon: mdi-chart-line
href: "https://prometheus.$domain"
description: Metrics collection
container: prometheus-changemaker
- Alertmanager:
icon: mdi-bell-alert
href: "https://alertmanager.$domain"
description: Alert routing
container: alertmanager-changemaker
- Gotify:
icon: mdi-cellphone-message
href: "https://gotify.$domain"
description: Push notifications
container: gotify-changemaker
- cAdvisor:
icon: mdi-docker
href: "https://cadvisor.$domain"
description: Container metrics
container: cadvisor-changemaker
- Node Exporter:
icon: mdi-server
href: "#"
description: Host system metrics
container: node-exporter-changemaker
- Redis Exporter:
icon: mdi-database-export
href: "#"
description: Redis metrics exporter
container: redis-exporter-changemaker
# ─────────────────────────────────────────────────
# LOCAL DEVELOPMENT
# ─────────────────────────────────────────────────
- Local - Core:
- Admin GUI:
icon: mdi-view-dashboard
href: "http://localhost:3000"
description: Application dashboard (port 3000)
container: changemaker-v2-admin
- API:
icon: mdi-api
href: "http://localhost:4000"
description: V2 REST API (port 4000)
container: changemaker-v2-api
- Media API:
icon: mdi-video
href: "http://localhost:4100"
description: Video library API (port 4100)
container: changemaker-media-api
- Main Site:
icon: mdi-web
href: "http://localhost:4001"
description: Documentation site (port 4001)
container: mkdocs-site-server-changemaker
- Homepage:
icon: mdi-home
href: "http://localhost:3010"
description: This dashboard (port 3010)
container: homepage-changemaker
- Local - Tools:
- Code Server:
icon: mdi-code-braces
href: "http://localhost:8888"
description: VS Code in the browser (port 8888)
container: code-server-changemaker
- NocoDB:
icon: mdi-database
href: "http://localhost:8091"
description: Database browser (port 8091)
container: changemaker-v2-nocodb
- MkDocs (Live):
icon: mdi-book-open-page-variant
href: "http://localhost:4003"
description: Live documentation (port 4003)
container: mkdocs-changemaker
- Mini QR:
icon: mdi-qrcode
href: "http://localhost:8089"
description: QR code generator (port 8089)
container: mini-qr
- Excalidraw:
icon: mdi-draw
href: "http://localhost:8090"
description: Collaborative whiteboard (port 8090)
container: excalidraw-changemaker
- Local - Integrations:
- Listmonk:
icon: mdi-email-newsletter
href: "http://localhost:9001"
description: Newsletter manager (port 9001)
container: listmonk-app
- MailHog:
icon: mdi-email-check
href: "http://localhost:8025"
description: Email capture UI (port 8025)
container: mailhog-changemaker
- n8n:
icon: mdi-robot-industrial
href: "http://localhost:5678"
description: Workflow automation (port 5678)
container: n8n-changemaker
- Gitea:
icon: mdi-git
href: "http://localhost:3030"
description: Git repository hosting (port 3030)
container: gitea-changemaker
- Local - Infrastructure:
- Nginx:
icon: mdi-web-box
href: "#"
description: Reverse proxy (port 80)
container: changemaker-v2-nginx
- PostgreSQL:
icon: mdi-database-outline
href: "#"
description: Primary database (port 5433)
container: changemaker-v2-postgres
- Redis:
icon: mdi-database-sync
href: "#"
description: Cache and job queues (port 6379)
container: redis-changemaker
- Local - Monitoring:
- Grafana:
icon: mdi-chart-box
href: "http://localhost:3001"
description: Monitoring dashboards (port 3001)
container: grafana-changemaker
- Prometheus:
icon: mdi-chart-line
href: "http://localhost:9090"
description: Metrics collection (port 9090)
container: prometheus-changemaker
- Alertmanager:
icon: mdi-bell-alert
href: "http://localhost:9093"
description: Alert routing (port 9093)
container: alertmanager-changemaker
- Gotify:
icon: mdi-cellphone-message
href: "http://localhost:8889"
description: Push notifications (port 8889)
container: gotify-changemaker
- cAdvisor:
icon: mdi-docker
href: "http://localhost:8080"
description: Container metrics (port 8080)
container: cadvisor-changemaker
- Node Exporter:
icon: mdi-server
href: "http://localhost:9100/metrics"
description: Host system metrics (port 9100)
container: node-exporter-changemaker
- Redis Exporter:
icon: mdi-database-export
href: "http://localhost:9121/metrics"
description: Redis metrics (port 9121)
container: redis-exporter-changemaker
YAML
success "Generated services.yaml for Homepage dashboard"
}
# =============================================================================
# Directory Permissions
# =============================================================================
fix_container_permissions() {
header "Container Directory Permissions"
local -a dirs=(
"configs/code-server/.config:Code Server config"
"configs/code-server/.local:Code Server local data"
"mkdocs/.cache:MkDocs cache"
"mkdocs/site:MkDocs built site"
"assets/uploads:Listmonk uploads"
"assets/images:Shared images"
"assets/icons:Homepage icons"
"media/local/inbox:Media upload inbox"
"media/local/thumbnails:Video thumbnails"
"media/public:Public media files"
"local-files:n8n local files"
"data:NAR import data"
)
local errors=0
for entry in "${dirs[@]}"; do
local dir_path="$SCRIPT_DIR/${entry%%:*}"
local dir_desc="${entry#*:}"
mkdir -p "$dir_path"
if chmod 775 "$dir_path" 2>/dev/null; then
success "$dir_desc"
else
warn "$dir_desc — could not set permissions (may need sudo)"
((errors++))
fi
done
echo ""
if [[ $errors -eq 0 ]]; then
success "All directories ready"
else
warn "Some directories may need: sudo chown -R $(id -u):$(id -g) $SCRIPT_DIR"
fi
}
# =============================================================================
# Summary & Next Steps
# =============================================================================
print_summary() {
header "Configuration Complete"
echo -e " ${BOLD}Domain:${NC} ${CONFIGURED_DOMAIN:-cmlite.org}"
echo -e " ${BOLD}Admin email:${NC} (see .env: INITIAL_ADMIN_EMAIL)"
echo -e " ${BOLD}Admin password:${NC} [set]"
echo -e " ${BOLD}SMTP:${NC} ${SMTP_MODE:-mailhog}"
echo -e " ${BOLD}Media Manager:${NC} ${MEDIA_ENABLED:-no}"
echo -e " ${BOLD}Listmonk sync:${NC} ${LISTMONK_SYNC:-no}"
echo -e " ${BOLD}Pangolin:${NC} ${PANGOLIN_CONFIGURED:-no}"
echo -e " ${BOLD}Secrets:${NC} 16 auto-generated"
echo ""
echo -e " ${DIM}Config file: $ENV_FILE${NC}"
}
print_next_steps() {
echo ""
echo -e "${BOLD}${BLUE}══════════════════════════════════════${NC}"
echo -e "${BOLD}${BLUE} Next Steps${NC}"
echo -e "${BOLD}${BLUE}══════════════════════════════════════${NC}"
echo ""
echo -e " ${BOLD}1.${NC} Start core services:"
echo -e " ${CYAN}docker compose up -d v2-postgres redis api admin${NC}"
echo ""
echo -e " ${BOLD}2.${NC} Run database setup:"
echo -e " ${CYAN}docker compose exec api npx prisma migrate deploy${NC}"
echo -e " ${CYAN}docker compose exec api npx prisma db seed${NC}"
echo ""
echo -e " ${BOLD}3.${NC} Access the application:"
echo -e " Admin GUI: ${CYAN}http://localhost:3000${NC}"
echo -e " API: ${CYAN}http://localhost:4000${NC}"
echo ""
echo -e " ${BOLD}4.${NC} Optional — start additional services:"
echo -e " ${CYAN}docker compose up -d nginx${NC} # Reverse proxy"
echo -e " ${CYAN}docker compose up -d media-api${NC} # Video library"
echo -e " ${CYAN}docker compose up -d listmonk-app${NC} # Newsletters"
echo -e " ${CYAN}docker compose up -d homepage${NC} # Service dashboard"
echo -e " ${CYAN}docker compose --profile monitoring up -d${NC} # Monitoring"
echo ""
echo -e " ${BOLD}5.${NC} Or start everything at once:"
echo -e " ${CYAN}docker compose up -d${NC}"
echo ""
echo -e " ${YELLOW}IMPORTANT: Change your admin password after first login!${NC}"
echo ""
}
# =============================================================================
# Main
# =============================================================================
main() {
print_banner
check_prerequisites
initialize_env
configure_domain
configure_admin
generate_all_secrets
configure_smtp
configure_features
configure_pangolin
configure_cors
generate_services_yaml
fix_container_permissions
print_summary
print_next_steps
}
main "$@"