Spaces:
Runtime error
Runtime error
| # | |
| # FreeLLMAPI one-line installer (#250, idea by @StealthTensor). | |
| # | |
| # curl -fsSL https://tashfeenahmed.github.io/freellmapi/install.sh | bash | |
| # | |
| # What it does: | |
| # 1. Checks for Docker + Compose. | |
| # 2. Creates an install dir (default ~/freellmapi, override FREELLMAPI_DIR). | |
| # 3. Writes a docker-compose.yml pinned to ghcr.io/tashfeenahmed/freellmapi:latest | |
| # and a .env with a freshly generated ENCRYPTION_KEY (kept on re-runs). | |
| # 4. Pulls the image, starts the container, waits for the health endpoint. | |
| # | |
| # Options (env vars): | |
| # FREELLMAPI_DIR install directory (default: ~/freellmapi) | |
| # PORT host port for the dashboard (default: 3001) | |
| # HOST_BIND host interface to publish on (default: 127.0.0.1). | |
| # Set HOST_BIND=0.0.0.0 to reach it from your LAN β only on | |
| # a trusted network; the proxy is single-user. | |
| # | |
| # Re-running is safe: existing .env (and your encryption key) is preserved, | |
| # the compose file is refreshed, and the container is updated to :latest. | |
| set -euo pipefail | |
| INSTALL_DIR="${FREELLMAPI_DIR:-$HOME/freellmapi}" | |
| PORT="${PORT:-3001}" | |
| HOST_BIND="${HOST_BIND:-127.0.0.1}" | |
| IMAGE="ghcr.io/tashfeenahmed/freellmapi:latest" | |
| say() { printf '\033[1;32m==>\033[0m %s\n' "$*"; } | |
| warn() { printf '\033[1;33mWARN\033[0m %s\n' "$*" >&2; } | |
| die() { printf '\033[1;31mERROR\033[0m %s\n' "$*" >&2; exit 1; } | |
| # ββ 1. prerequisites βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| command -v docker >/dev/null 2>&1 || die "Docker is required. Install it from https://docs.docker.com/get-docker/ and re-run." | |
| if docker compose version >/dev/null 2>&1; then | |
| COMPOSE=(docker compose) | |
| elif command -v docker-compose >/dev/null 2>&1; then | |
| COMPOSE=(docker-compose) | |
| else | |
| die "Docker Compose is required (the 'docker compose' plugin or 'docker-compose')." | |
| fi | |
| docker info >/dev/null 2>&1 || die "Docker daemon isn't running (or you lack permission). Start Docker and re-run." | |
| # ββ 2. install dir βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| mkdir -p "$INSTALL_DIR" | |
| cd "$INSTALL_DIR" | |
| say "Installing into $INSTALL_DIR" | |
| # ββ 3. .env (generated once β your at-rest encryption key lives here) ββββββββ | |
| if [ -f .env ]; then | |
| say "Keeping existing .env (encryption key preserved)" | |
| else | |
| if command -v openssl >/dev/null 2>&1; then | |
| KEY="$(openssl rand -hex 32)" | |
| else | |
| # /dev/urandom fallback when openssl isn't installed | |
| KEY="$(od -vN 32 -An -tx1 /dev/urandom | tr -d ' \n')" | |
| fi | |
| umask 077 | |
| cat > .env <<EOF | |
| # Generated by install.sh on $(date -u +%Y-%m-%dT%H:%M:%SZ) | |
| # At-rest encryption key for your stored provider keys. Losing it means | |
| # re-entering your provider keys, so back this file up. | |
| ENCRYPTION_KEY=$KEY | |
| PORT=$PORT | |
| HOST_BIND=$HOST_BIND | |
| EOF | |
| umask 022 | |
| say "Wrote .env with a fresh ENCRYPTION_KEY" | |
| fi | |
| # ββ 4. compose file (image-only; refreshed on every run) βββββββββββββββββββββ | |
| cat > docker-compose.yml <<EOF | |
| # Written by install.sh β safe to edit; re-running the installer rewrites it. | |
| services: | |
| freellmapi: | |
| image: $IMAGE | |
| env_file: | |
| - .env | |
| environment: | |
| NODE_ENV: production | |
| PORT: 3001 | |
| # Bound to localhost by default β FreeLLMAPI is single-user and must not | |
| # be exposed to the internet. Set HOST_BIND=0.0.0.0 in .env for LAN access. | |
| ports: | |
| - "\${HOST_BIND:-127.0.0.1}:\${PORT:-3001}:3001" | |
| volumes: | |
| - freellmapi-data:/app/server/data | |
| restart: unless-stopped | |
| healthcheck: | |
| test: | |
| [ | |
| "CMD", | |
| "node", | |
| "-e", | |
| "fetch('http://127.0.0.1:3001/api/ping').then((res) => { if (!res.ok) process.exit(1); }).catch(() => process.exit(1));" | |
| ] | |
| interval: 30s | |
| timeout: 5s | |
| start_period: 15s | |
| retries: 3 | |
| volumes: | |
| freellmapi-data: | |
| EOF | |
| # ββ 5. pull + start ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| say "Pulling $IMAGE" | |
| "${COMPOSE[@]}" pull --quiet 2>/dev/null || "${COMPOSE[@]}" pull | |
| say "Starting FreeLLMAPI" | |
| "${COMPOSE[@]}" up -d | |
| # ββ 6. wait for health βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| say "Waiting for the server to come up" | |
| HEALTH_HOST="127.0.0.1" | |
| [ "$HOST_BIND" != "0.0.0.0" ] && [ "$HOST_BIND" != "127.0.0.1" ] && HEALTH_HOST="$HOST_BIND" | |
| for i in $(seq 1 30); do | |
| if curl -fsS "http://$HEALTH_HOST:$PORT/api/ping" >/dev/null 2>&1; then | |
| HEALTHY=1 | |
| break | |
| fi | |
| sleep 2 | |
| done | |
| if [ "${HEALTHY:-0}" = "1" ]; then | |
| cat <<EOF | |
| FreeLLMAPI is running. | |
| Dashboard http://localhost:$PORT | |
| Next steps add provider keys on the Keys page, then grab your | |
| unified API key from the page header β that's the | |
| Bearer token your OpenAI-compatible client uses. | |
| API endpoint http://localhost:$PORT/v1 | |
| Update cd $INSTALL_DIR && docker compose pull && docker compose up -d | |
| Logs cd $INSTALL_DIR && docker compose logs -f | |
| Uninstall cd $INSTALL_DIR && docker compose down -v && cd .. && rm -rf $INSTALL_DIR | |
| EOF | |
| else | |
| warn "Container started but the health endpoint didn't answer within 60s." | |
| warn "Check logs with: cd $INSTALL_DIR && ${COMPOSE[*]} logs -f" | |
| exit 1 | |
| fi | |