210 lines
8.9 KiB
Bash
Executable File
210 lines
8.9 KiB
Bash
Executable File
#!/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 ""
|