Files
dezky/scripts/bootstrap.sh
T
Ronni Baslund c9911cc262 feat(website): add Nuxt 4 marketing landing page
New standalone apps/website (Nuxt 4) serving the public marketing site at
dezky.local / www.dezky.local. The customer portal moves off the root domain
to app.dezky.local only.

Landing page ported from the Dezky design handoff: light theme, Danish
default, hero variant A, with a working da/en toggle. Self-contained colour
system threaded through components (utils/landingTokens.ts), full bilingual
copy (utils/landingCopy.ts), and shared state (composables/useLanding.ts).
Sections live under components/landing/* with the Node logo under
components/brand/*.

Wired into docker-compose (website service, volume, Traefik labels, network
aliases) and bootstrap.sh (hosts list + service URLs).
2026-06-05 10:58:25 +02:00

269 lines
9.7 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# Dezky local development bootstrap
# Run this once when setting up the project for the first time.
#
# Usage: ./scripts/bootstrap.sh
#
set -euo pipefail
# ────────────────────────────────────────
# Colors for output
# ────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
# ────────────────────────────────────────
# Determine project root
# ────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
COMPOSE_DIR="$PROJECT_ROOT/infrastructure/docker-compose"
CERTS_DIR="$COMPOSE_DIR/certs"
cd "$PROJECT_ROOT"
echo ""
echo "╔══════════════════════════════════════════════════════════════╗"
echo "║ Dezky Local Development Bootstrap ║"
echo "╚══════════════════════════════════════════════════════════════╝"
echo ""
# ────────────────────────────────────────
# Step 1: Check prerequisites
# ────────────────────────────────────────
info "Step 1: Checking prerequisites..."
check_command() {
if ! command -v "$1" &> /dev/null; then
error "$1 is not installed."
echo " Install with: $2"
exit 1
fi
ok "$1 found: $(command -v "$1")"
}
check_command docker "Install Docker Desktop or OrbStack from https://orbstack.dev"
check_command mkcert "brew install mkcert"
check_command openssl "Should be preinstalled on macOS"
if ! docker compose version &> /dev/null; then
error "Docker Compose v2 not available."
echo " Update Docker Desktop or install OrbStack."
exit 1
fi
ok "Docker Compose v2 available"
# Check Docker daemon is running
if ! docker info &> /dev/null; then
error "Docker daemon not running. Start Docker Desktop / OrbStack first."
exit 1
fi
ok "Docker daemon running"
echo ""
# ────────────────────────────────────────
# Step 2: Generate TLS certificates
# ────────────────────────────────────────
info "Step 2: Setting up TLS certificates..."
mkdir -p "$CERTS_DIR"
cd "$CERTS_DIR"
if [[ -f "dezky.local.pem" && -f "dezky.local-key.pem" ]]; then
ok "TLS certificates already exist in $CERTS_DIR"
else
info "Generating wildcard certificate for *.dezky.local..."
if ! mkcert -CAROOT &> /dev/null; then
warn "mkcert root CA not found. Running mkcert -install..."
mkcert -install
fi
mkcert "*.dezky.local" "dezky.local" "localhost" "127.0.0.1" "::1"
# Normalize filenames (mkcert adds counts)
mv ./_wildcard.dezky.local+*.pem dezky.local.pem 2>/dev/null || true
mv ./_wildcard.dezky.local+*-key.pem dezky.local-key.pem 2>/dev/null || true
ok "TLS certificates generated"
fi
cd "$PROJECT_ROOT"
echo ""
# ────────────────────────────────────────
# Step 3: Update /etc/hosts
# ────────────────────────────────────────
info "Step 3: Setting up /etc/hosts entries..."
HOSTS_ENTRIES=(
"dezky.local"
"www.dezky.local"
"app.dezky.local"
"operator.dezky.local"
"auth.dezky.local"
"mail.dezky.local"
"files.dezky.local"
"office.dezky.local"
"meet.dezky.local"
"chat.dezky.local"
"traefik.dezky.local"
)
MISSING_ENTRIES=()
for entry in "${HOSTS_ENTRIES[@]}"; do
if ! grep -q "127.0.0.1[[:space:]]\+.*\b${entry}\b" /etc/hosts; then
MISSING_ENTRIES+=("$entry")
fi
done
if [[ ${#MISSING_ENTRIES[@]} -eq 0 ]]; then
ok "All /etc/hosts entries already present"
else
warn "Missing /etc/hosts entries: ${MISSING_ENTRIES[*]}"
echo ""
echo "Add the following line to /etc/hosts (requires sudo):"
echo ""
echo "127.0.0.1 ${HOSTS_ENTRIES[*]}"
echo ""
read -p "Add these entries automatically? (requires sudo) [y/N] " -n 1 -r
echo ""
if [[ $REPLY =~ ^[Yy]$ ]]; then
HOSTS_LINE="127.0.0.1 ${HOSTS_ENTRIES[*]}"
echo "$HOSTS_LINE" | sudo tee -a /etc/hosts > /dev/null
ok "Added /etc/hosts entries"
else
warn "Skipping /etc/hosts setup — you must add entries manually before continuing"
fi
fi
echo ""
# ────────────────────────────────────────
# Step 4: Generate .env file
# ────────────────────────────────────────
info "Step 4: Setting up .env file..."
if [[ -f "$PROJECT_ROOT/.env" ]]; then
ok ".env file already exists"
else
info "Generating .env with secure random values..."
cp "$PROJECT_ROOT/.env.example" "$PROJECT_ROOT/.env"
# Replace all 'changeme_*' placeholders with actual random values
if [[ "$OSTYPE" == "darwin"* ]]; then
SED_INPLACE=(-i '')
else
SED_INPLACE=(-i)
fi
while IFS= read -r line; do
if [[ "$line" =~ ^([A-Z_]+)=changeme ]]; then
VAR_NAME="${BASH_REMATCH[1]}"
if [[ "$VAR_NAME" == "AUTHENTIK_SECRET_KEY" ]]; then
NEW_VALUE=$(openssl rand -hex 50)
else
NEW_VALUE=$(openssl rand -hex 32)
fi
sed "${SED_INPLACE[@]}" "s|^${VAR_NAME}=changeme.*|${VAR_NAME}=${NEW_VALUE}|" "$PROJECT_ROOT/.env"
fi
done < "$PROJECT_ROOT/.env.example"
ok ".env generated with secure random values"
warn "Default admin password generated. Check .env for AUTHENTIK_BOOTSTRAP_PASSWORD"
fi
echo ""
# ────────────────────────────────────────
# Step 5: Pull Docker images
# ────────────────────────────────────────
info "Step 5: Pulling Docker images (this may take a few minutes)..."
cd "$COMPOSE_DIR"
docker compose pull
ok "All images pulled"
echo ""
# ────────────────────────────────────────
# Step 6: Start the stack in stages
# ────────────────────────────────────────
info "Step 6: Starting services..."
info "Starting database layer (postgres, mongo, redis)..."
docker compose up -d postgres mongo redis
info "Waiting for databases to be healthy..."
sleep 10
for service in postgres mongo redis; do
until [[ "$(docker inspect --format='{{.State.Health.Status}}' dezky-$service 2>/dev/null)" == "healthy" ]]; do
echo -n "."
sleep 2
done
echo ""
ok "$service is healthy"
done
info "Starting Traefik reverse proxy..."
docker compose up -d traefik
info "Starting Authentik..."
docker compose up -d authentik-server authentik-worker
info "Waiting 30 seconds for Authentik to bootstrap..."
sleep 30
info "Starting application services..."
docker compose up -d stalwart ocis collabora
echo ""
ok "Stack started"
echo ""
# ────────────────────────────────────────
# Final instructions
# ────────────────────────────────────────
echo "╔══════════════════════════════════════════════════════════════╗"
echo "║ Setup Complete ║"
echo "╚══════════════════════════════════════════════════════════════╝"
echo ""
echo "Service URLs:"
echo " Website: https://dezky.local"
echo " Portal: https://app.dezky.local"
echo " Authentik (auth): https://auth.dezky.local"
echo " Mail (admin): https://mail.dezky.local"
echo " Files (OCIS): https://files.dezky.local"
echo " Office: https://office.dezky.local"
echo " Traefik: https://traefik.dezky.local"
echo ""
echo "First-time Authentik admin login:"
echo " URL: https://auth.dezky.local/if/flow/initial-setup/"
echo " Email: admin@dezky.local"
echo " Password: (see AUTHENTIK_BOOTSTRAP_PASSWORD in .env)"
echo ""
echo "Next steps:"
echo " 1. Configure Authentik (see docs/AUTHENTIK-SETUP.md)"
echo " 2. Configure OCIS OIDC provider in Authentik"
echo " 3. Start building the portal in apps/portal/"
echo ""
echo "Useful commands:"
echo " Logs: docker compose -f $COMPOSE_DIR/docker-compose.yml logs -f [service]"
echo " Stop: docker compose -f $COMPOSE_DIR/docker-compose.yml down"
echo " Reset: $PROJECT_ROOT/scripts/reset.sh"
echo ""