install.sh
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env bash
#
# GlucoPilot — one-command local installer.
#
# Gets GlucoPilot running on your own machine at http://localhost:<port>.
# Works on macOS, Linux, and Windows (via WSL2 / Git Bash). All it needs is
# Docker. Run it from the folder you cloned this repo into:
#
# ./install.sh
#
# It's safe to re-run: it won't overwrite an existing .env unless you say so.
set -euo pipefail
# ── pretty output ───────────────────────────────────────────────────────────
if [ -t 1 ]; then
B=$'\033[1m'; DIM=$'\033[2m'; G=$'\033[32m'; Y=$'\033[33m'; R=$'\033[31m'; C=$'\033[36m'; N=$'\033[0m'
else
B=""; DIM=""; G=""; Y=""; R=""; C=""; N=""
fi
say() { printf '%s\n' "$*"; }
ok() { printf '%s✓%s %s\n' "$G" "$N" "$*"; }
warn() { printf '%s!%s %s\n' "$Y" "$N" "$*"; }
die() { printf '%s✗ %s%s\n' "$R" "$*" "$N" >&2; exit 1; }
ask() { # ask "Prompt" "default" -> echoes answer (default if non-interactive/empty)
local prompt="$1" default="${2:-}" reply=""
if [ -t 0 ]; then
printf '%s%s%s ' "$C" "$prompt" "$N" >&2
[ -n "$default" ] && printf '%s[%s]%s ' "$DIM" "$default" "$N" >&2
read -r reply || true
fi
printf '%s' "${reply:-$default}"
}
say ""
say "${B}GlucoPilot installer${N}"
say "${DIM}A private, self-hosted health companion. This sets it up on this machine.${N}"
say ""
# ── 1. preflight: docker ────────────────────────────────────────────────────
command -v docker >/dev/null 2>&1 || die "Docker isn't installed. Get it at https://docs.docker.com/get-docker/ then re-run this."
if docker compose version >/dev/null 2>&1; then DC="docker compose"
elif command -v docker-compose >/dev/null 2>&1; then DC="docker-compose"
else die "Docker Compose isn't available. Install Docker Desktop (macOS/Windows) or the compose plugin (Linux)."
fi
docker info >/dev/null 2>&1 || die "Docker is installed but not running. Start Docker Desktop (or the docker service) and re-run."
ok "Docker is ready"
# ── 2. in the repo? ─────────────────────────────────────────────────────────
cd "$(dirname "$0")"
[ -f docker-compose.local.yml ] || die "Run this from the folder you cloned GlucoPilot into (docker-compose.local.yml not found)."
# ── 3. wizard ───────────────────────────────────────────────────────────────
detect_tz() {
if command -v timedatectl >/dev/null 2>&1; then timedatectl show -p Timezone --value 2>/dev/null && return; fi
[ -f /etc/timezone ] && { cat /etc/timezone; return; }
if [ -L /etc/localtime ]; then readlink /etc/localtime | sed 's#.*/zoneinfo/##'; return; fi
printf 'America/New_York'
}
if [ -f .env ]; then
warn "An .env already exists."
if [ "$(ask 'Keep it and just (re)start? y/n' 'y')" = "y" ]; then
KEEP_ENV=1
else
KEEP_ENV=0; cp .env ".env.bak.$(date +%s 2>/dev/null || echo old)" 2>/dev/null || true
ok "Backed up your old .env"
fi
else
KEEP_ENV=0
fi
PORT="8000"
if [ "${KEEP_ENV:-0}" = "0" ]; then
say ""
PORT="$(ask 'Which port should GlucoPilot use?' '8000')"
[[ "$PORT" =~ ^[0-9]+$ ]] || die "Port must be a number."
TZ_DEFAULT="$(detect_tz)"; [ -n "$TZ_DEFAULT" ] || TZ_DEFAULT="America/New_York"
APP_TZ="$(ask 'Your timezone (IANA name)?' "$TZ_DEFAULT")"
say ""
say "${DIM}Optional: the AI features (Companion, insights, Visit Report) can use the"
say "Anthropic API, a local model, or be set up later in Settings. Leave blank to skip.${N}"
ANTHROPIC_KEY="$(ask 'Anthropic API key (optional, starts with sk-ant-):' '')"
# random secret for signing session cookies
if command -v openssl >/dev/null 2>&1; then SECRET="$(openssl rand -hex 32)"
else SECRET="$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')"; fi
{
echo "# GlucoPilot — generated by install.sh. Advanced/server options: see .env.example"
echo "APP_PUBLIC_URL=http://localhost:${PORT}"
echo "APP_SECRET_KEY=${SECRET}"
echo "APP_TIMEZONE=${APP_TZ}"
echo "SYNC_ENABLED=true"
echo "PORT=${PORT}"
[ -n "$ANTHROPIC_KEY" ] && echo "ANTHROPIC_API_KEY=${ANTHROPIC_KEY}"
} > .env
ok "Wrote .env (secret key generated for you)"
else
# reuse the port already in .env if present
PORT="$(grep -E '^PORT=' .env 2>/dev/null | head -1 | cut -d= -f2)"; [ -n "$PORT" ] || PORT="8000"
fi
# ── 4. launch ───────────────────────────────────────────────────────────────
say ""
say "${B}Starting GlucoPilot…${N} ${DIM}(first run downloads the image — a few minutes)${N}"
if ! PORT="$PORT" $DC -f docker-compose.local.yml up -d 2>/dev/null; then
warn "Prebuilt image unavailable — building from source instead (slower, one-time)."
PORT="$PORT" $DC -f docker-compose.local.yml up -d --build
fi
# ── 5. wait for it to answer ────────────────────────────────────────────────
URL="http://localhost:${PORT}"
printf '%sWaiting for GlucoPilot to come up' "$DIM"
up=0
for _ in $(seq 1 60); do
code="$(curl -s -o /dev/null -w '%{http_code}' "$URL" 2>/dev/null || echo 000)"
if [ "$code" != "000" ]; then up=1; break; fi
printf '.'; sleep 2
done
printf '%s\n' "$N"
say ""
if [ "$up" = "1" ]; then
ok "GlucoPilot is running!"
else
warn "It didn't answer yet — it may still be starting. Check logs with: $DC -f docker-compose.local.yml logs -f"
fi
say ""
say "${B}Open:${N} ${C}${URL}${N}"
say " ${DIM}The first visit lets you create your login. Then open Settings to connect"
say " data sources (Dexcom, Oura, …) and choose your AI. That's it.${N}"
say ""
say "${B}Handy commands${N}"
say " ${DIM}Stop:${N} $DC -f docker-compose.local.yml down"
say " ${DIM}Update:${N} git pull && $DC -f docker-compose.local.yml pull && $DC -f docker-compose.local.yml up -d"
say " ${DIM}Logs:${N} $DC -f docker-compose.local.yml logs -f"
say " ${DIM}Backup:${N} see docs/DEPLOY.md"
say ""