#!/usr/bin/env bash # MileVault installer # Usage: curl -fsSL https://raw.githubusercontent.com/you/milevault/main/install.sh | bash # Or: bash install.sh set -euo pipefail RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m' BOLD='\033[1m' info() { echo -e "${GREEN}✓${NC} $*"; } warn() { echo -e "${YELLOW}!${NC} $*"; } error() { echo -e "${RED}✗ $*${NC}"; exit 1; } step() { echo -e "\n${CYAN}${BOLD}── $* ──${NC}"; } echo -e "${BOLD}" echo " ███████╗██╗████████╗████████╗██████╗ █████╗ ██████╗██╗ ██╗███████╗██████╗ " echo " ██╔════╝██║╚══██╔══╝╚══██╔══╝██╔══██╗██╔══██╗██╔════╝██║ ██╔╝██╔════╝██╔══██╗" echo " █████╗ ██║ ██║ ██║ ██████╔╝███████║██║ █████╔╝ █████╗ ██████╔╝" echo " ██╔══╝ ██║ ██║ ██║ ██╔══██╗██╔══██║██║ ██╔═██╗ ██╔══╝ ██╔══██╗" echo " ██║ ██║ ██║ ██║ ██║ ██║██║ ██║╚██████╗██║ ██╗███████╗██║ ██║" echo " ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝" echo -e "${NC}" echo " Self-hosted fitness tracking — Garmin & Strava" echo "" # ── Preflight checks ────────────────────────────────────────────────────────── step "Checking requirements" command -v docker >/dev/null 2>&1 || error "Docker is not installed. Install from https://docs.docker.com/get-docker/" info "Docker found: $(docker --version | head -1)" # Check docker compose (v2 plugin or v1 standalone) if docker compose version >/dev/null 2>&1; then COMPOSE_CMD="docker compose" elif command -v docker-compose >/dev/null 2>&1; then COMPOSE_CMD="docker-compose" else error "Docker Compose not found. Install from https://docs.docker.com/compose/install/" fi info "Docker Compose found: $($COMPOSE_CMD version | head -1)" # Check Docker daemon is running docker info >/dev/null 2>&1 || error "Docker daemon is not running. Start Docker and retry." info "Docker daemon is running" # ── Install directory ───────────────────────────────────────────────────────── step "Setting up install directory" INSTALL_DIR="${FITTRACKER_DIR:-$HOME/milevault}" if [ -d "$INSTALL_DIR" ] && [ "$(ls -A "$INSTALL_DIR" 2>/dev/null)" ]; then warn "Directory $INSTALL_DIR already exists." read -rp " Continue and update existing install? [y/N] " confirm [[ "$confirm" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 0; } fi mkdir -p "$INSTALL_DIR" cd "$INSTALL_DIR" info "Install directory: $INSTALL_DIR" # ── Download project files ──────────────────────────────────────────────────── step "Downloading MileVault" # If we're already inside the repo (files exist), skip download if [ -f "docker-compose.yml" ]; then info "Project files already present — skipping download" else # Try git first, fall back to curl if command -v git >/dev/null 2>&1; then git clone --depth 1 https://github.com/yourusername/milevault.git . 2>/dev/null || { warn "Git clone failed — copying bundled files instead" } fi # Fallback: if running this script from inside a downloaded zip, the files are next to it SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" if [ "$SCRIPT_DIR" != "$INSTALL_DIR" ] && [ -f "$SCRIPT_DIR/docker-compose.yml" ]; then cp -r "$SCRIPT_DIR"/. "$INSTALL_DIR/" info "Copied project files from $SCRIPT_DIR" fi fi [ -f "docker-compose.yml" ] || error "docker-compose.yml not found. Place install.sh inside the project directory." info "Project files ready" # ── Generate .env ───────────────────────────────────────────────────────────── step "Configuring environment" if [ -f ".env" ]; then warn ".env already exists — skipping generation (delete it to regenerate)" else # Generate secure random values if command -v openssl >/dev/null 2>&1; then SECRET_KEY=$(openssl rand -hex 32) DB_PASSWORD=$(openssl rand -base64 18 | tr -d '/+=') REDIS_PASSWORD=$(openssl rand -base64 12 | tr -d '/+=') ADMIN_PASSWORD=$(openssl rand -base64 12 | tr -d '/+=') else # Fallback if openssl not available SECRET_KEY=$(cat /dev/urandom | tr -dc 'a-f0-9' | head -c 64) DB_PASSWORD=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 18) REDIS_PASSWORD=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 12) ADMIN_PASSWORD=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 12) fi ADMIN_USERNAME="${FITTRACKER_ADMIN:-admin}" PORT="${FITTRACKER_PORT:-80}" cat > .env << ENV # MileVault configuration — generated $(date) # Edit this file to change settings, then run: docker compose up -d # Admin login ADMIN_USERNAME=${ADMIN_USERNAME} ADMIN_PASSWORD=${ADMIN_PASSWORD} # Secrets (auto-generated — do not share) SECRET_KEY=${SECRET_KEY} DB_PASSWORD=${DB_PASSWORD} DB_USER=milevault REDIS_PASSWORD=${REDIS_PASSWORD} # Server HTTP_PORT=${PORT} ENVIRONMENT=production # Optional: Mapbox token for satellite map tiles (free at mapbox.com) VITE_MAPBOX_TOKEN= # Optional: PocketID passkey authentication # POCKETID_ISSUER=https://your-pocketid.example.com # POCKETID_CLIENT_ID=milevault # POCKETID_CLIENT_SECRET= ENV info ".env created with secure random secrets" # Save credentials for display at end SHOW_CREDS=true fi source .env # ── Build & start ───────────────────────────────────────────────────────────── step "Building and starting containers" echo " This takes 3–5 minutes on first run (building images)..." echo "" $COMPOSE_CMD up -d --build # ── Wait for healthy ────────────────────────────────────────────────────────── step "Waiting for services to be ready" TIMEOUT=120 ELAPSED=0 printf " Waiting" while ! docker inspect milevault_backend 2>/dev/null | grep -q '"healthy"' ; do if [ $ELAPSED -ge $TIMEOUT ]; then echo "" warn "Backend taking longer than expected. Check logs: docker compose logs backend" break fi printf "." sleep 3 ELAPSED=$((ELAPSED + 3)) done echo "" info "All services are up" # ── Done ────────────────────────────────────────────────────────────────────── PORT="${HTTP_PORT:-80}" URL="http://localhost${PORT:+:${PORT}}" [[ "$PORT" == "80" ]] && URL="http://localhost" echo "" echo -e "${GREEN}${BOLD}╔══════════════════════════════════════════╗${NC}" echo -e "${GREEN}${BOLD}║ MileVault is ready! ║${NC}" echo -e "${GREEN}${BOLD}╚══════════════════════════════════════════╝${NC}" echo "" echo -e " 🌐 Open: ${CYAN}${URL}${NC}" echo -e " 👤 Username: ${BOLD}${ADMIN_USERNAME:-admin}${NC}" if [ "${SHOW_CREDS:-false}" = "true" ]; then echo -e " 🔑 Password: ${BOLD}${ADMIN_PASSWORD}${NC}" echo "" warn "Save this password — it won't be shown again." warn "It's also stored in: ${INSTALL_DIR}/.env" else echo -e " 🔑 Password: (see ${INSTALL_DIR}/.env — ADMIN_PASSWORD)" fi echo "" echo " Useful commands:" echo " docker compose logs -f # View live logs" echo " docker compose logs backend # Backend logs only" echo " docker compose down # Stop everything" echo " docker compose up -d # Start again" echo " docker compose pull && docker compose up -d --build # Update" echo "" echo " Import your data:" echo " Go to ${URL} → Import → upload your Garmin export ZIP or Strava ZIP" echo ""