a0f79ab852
Add a "Configure git remote" step that points origin at the Gitea host (git@git.lastcloud.io) and pins the host to port 22222 in ~/.ssh/config so git doesn't default to port 22 and get rejected by the agent offering too many keys. Idempotent: reuses existing config on re-run. Also adds git to the prerequisite checks and renumbers the steps to 1-7.
336 lines
13 KiB
Bash
Executable File
336 lines
13 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"
|
|
check_command git "brew install git"
|
|
|
|
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: Configure git remote
|
|
# ────────────────────────────────────────
|
|
info "Step 2: Configuring git remote..."
|
|
|
|
GIT_REMOTE_URL="git@git.lastcloud.io:ronnibaslund/dezky.git"
|
|
GIT_SSH_HOST="git.lastcloud.io"
|
|
GIT_SSH_PORT="22222"
|
|
|
|
if [[ -d "$PROJECT_ROOT/.git" ]]; then
|
|
CURRENT_URL="$(git -C "$PROJECT_ROOT" remote get-url origin 2>/dev/null || true)"
|
|
if [[ "$CURRENT_URL" == "$GIT_REMOTE_URL" ]]; then
|
|
ok "Git remote 'origin' already set to $GIT_REMOTE_URL"
|
|
elif [[ -n "$CURRENT_URL" ]]; then
|
|
git -C "$PROJECT_ROOT" remote set-url origin "$GIT_REMOTE_URL"
|
|
ok "Updated git remote 'origin' → $GIT_REMOTE_URL (was $CURRENT_URL)"
|
|
else
|
|
git -C "$PROJECT_ROOT" remote add origin "$GIT_REMOTE_URL"
|
|
ok "Added git remote 'origin' → $GIT_REMOTE_URL"
|
|
fi
|
|
|
|
# Gitea's git SSH listens on a non-standard port. Without an ssh config
|
|
# entry, git defaults to port 22 and the global "Host *" 1Password agent
|
|
# offers too many keys — the server rejects the connection before the right
|
|
# key is tried. Pin the host to port 22222 and the registered key only.
|
|
if [[ "$(ssh -G "$GIT_SSH_HOST" 2>/dev/null | awk '/^port /{print $2}')" == "$GIT_SSH_PORT" ]]; then
|
|
ok "SSH config already routes $GIT_SSH_HOST to port $GIT_SSH_PORT"
|
|
else
|
|
warn "$GIT_SSH_HOST is not pinned to port $GIT_SSH_PORT in your SSH config"
|
|
echo ""
|
|
echo "The following block is needed in ~/.ssh/config so git can reach Gitea:"
|
|
echo ""
|
|
echo " Host $GIT_SSH_HOST"
|
|
echo " HostName $GIT_SSH_HOST"
|
|
echo " Port $GIT_SSH_PORT"
|
|
echo " User git"
|
|
echo " IdentityFile ~/.ssh/id_ed25519"
|
|
echo " IdentitiesOnly yes"
|
|
echo ""
|
|
read -p "Append this block to ~/.ssh/config automatically? [y/N] " -n 1 -r
|
|
echo ""
|
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
mkdir -p "$HOME/.ssh"
|
|
{
|
|
echo ""
|
|
echo "# Gitea (lastcloud) — Git SSH on port $GIT_SSH_PORT. Force the registered"
|
|
echo "# key only; the global \"Host *\" agent otherwise offers too many keys."
|
|
echo "Host $GIT_SSH_HOST"
|
|
echo " HostName $GIT_SSH_HOST"
|
|
echo " Port $GIT_SSH_PORT"
|
|
echo " User git"
|
|
echo " IdentityFile ~/.ssh/id_ed25519"
|
|
echo " IdentitiesOnly yes"
|
|
} >> "$HOME/.ssh/config"
|
|
chmod 600 "$HOME/.ssh/config"
|
|
ok "Appended SSH config block for $GIT_SSH_HOST"
|
|
else
|
|
warn "Skipping SSH config — pushes to $GIT_SSH_HOST may fail until you add it"
|
|
fi
|
|
fi
|
|
else
|
|
warn "No .git directory in $PROJECT_ROOT — skipping git remote setup"
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# ────────────────────────────────────────
|
|
# Step 3: Generate TLS certificates
|
|
# ────────────────────────────────────────
|
|
info "Step 3: 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 4: Update /etc/hosts
|
|
# ────────────────────────────────────────
|
|
info "Step 4: 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 5: Generate .env file
|
|
# ────────────────────────────────────────
|
|
info "Step 5: 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 6: Pull Docker images
|
|
# ────────────────────────────────────────
|
|
info "Step 6: Pulling Docker images (this may take a few minutes)..."
|
|
|
|
cd "$COMPOSE_DIR"
|
|
docker compose pull
|
|
ok "All images pulled"
|
|
|
|
echo ""
|
|
|
|
# ────────────────────────────────────────
|
|
# Step 7: Start the stack in stages
|
|
# ────────────────────────────────────────
|
|
info "Step 7: 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 ""
|