#!/usr/bin/env sh # boxx installer — bootstraps Docker if needed, then installs the boxx binary. # # Usage: # curl -fsSL https://raw.githubusercontent.com/plainwork/boxx/main/install.sh | sh # # Options (env vars): # PREFIX= install boxx here (default: /usr/local/bin) # VERSION= pin a specific release (default: latest) # SKIP_DOCKER=1 skip Docker install/setup set -eu REPO="plainwork/boxx" PREFIX="${PREFIX:-/usr/local/bin}" VERSION="${VERSION:-latest}" SKIP_DOCKER="${SKIP_DOCKER:-0}" # ── helpers ────────────────────────────────────────────────────────────────── say() { printf '\033[1mboxx:\033[0m %s\n' "$*"; } ok() { printf ' \033[32m✓\033[0m %s\n' "$*"; } info() { printf ' \033[2m→\033[0m %s\n' "$*"; } die() { printf '\033[31merror:\033[0m %s\n' "$*" >&2; exit 1; } need() { command -v "$1" >/dev/null 2>&1 || die "required tool not found: $1" } uname_os() { os="$(uname -s | tr '[:upper:]' '[:lower:]')" case "$os" in darwin|linux) printf '%s' "$os" ;; *) die "unsupported OS: $os" ;; esac } uname_arch() { arch="$(uname -m)" case "$arch" in x86_64|amd64) printf 'amd64' ;; aarch64|arm64) printf 'arm64' ;; armv7l|armv7) printf 'armv7' ;; *) die "unsupported architecture: $arch" ;; esac } need curl need tar OS="$(uname_os)" ARCH="$(uname_arch)" # ── Docker bootstrap (Linux only) ───────────────────────────────────────────── if [ "$OS" = "linux" ] && [ "$SKIP_DOCKER" = "0" ]; then if command -v docker >/dev/null 2>&1; then ok "Docker already installed ($(docker --version 2>/dev/null | head -1))" else say "Docker not found — installing via get.docker.com …" curl -fsSL https://get.docker.com | sh ok "Docker installed" fi # Enable and start Docker via systemd (idempotent) if command -v systemctl >/dev/null 2>&1; then systemctl is-enabled docker >/dev/null 2>&1 || { info "enabling Docker service …" sudo systemctl enable --now docker } systemctl is-active docker >/dev/null 2>&1 || { info "starting Docker service …" sudo systemctl start docker } ok "Docker service enabled and running" fi # Add current user to the docker group so sudo isn't needed for docker commands CURRENT_USER="${SUDO_USER:-$(id -un)}" if [ -n "$CURRENT_USER" ] && [ "$CURRENT_USER" != "root" ]; then if ! groups "$CURRENT_USER" 2>/dev/null | grep -q '\bdocker\b'; then info "adding $CURRENT_USER to docker group …" sudo usermod -aG docker "$CURRENT_USER" ok "added $CURRENT_USER to docker group (re-login to apply)" else ok "$CURRENT_USER is already in the docker group" fi fi fi # ── Resolve version ─────────────────────────────────────────────────────────── if [ "$VERSION" = "latest" ]; then say "Resolving latest release …" TAG="$(curl -fsSL "https://api.github.com/repos/$REPO/releases/latest" \ | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p' | head -n1)" [ -n "$TAG" ] || die "could not determine latest release — check https://github.com/$REPO/releases" else TAG="$VERSION" fi # ── Download and install boxx binary ───────────────────────────────────────── # GoReleaser names: boxx_1.2.3_linux_amd64.tar.gz (arm → armv7) ARCH_SUFFIX="$ARCH" [ "$ARCH_SUFFIX" = "armv7" ] && ARCH_SUFFIX="armv7" ARCHIVE="boxx_${TAG#v}_${OS}_${ARCH_SUFFIX}.tar.gz" URL="https://github.com/$REPO/releases/download/$TAG/$ARCHIVE" say "Downloading boxx $TAG ($OS/$ARCH) …" TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT curl -fsSL "$URL" -o "$TMP/boxx.tgz" || die "download failed: $URL" tar -xzf "$TMP/boxx.tgz" -C "$TMP" if [ -w "$PREFIX" ]; then install -m 0755 "$TMP/boxx" "$PREFIX/boxx" else sudo install -m 0755 "$TMP/boxx" "$PREFIX/boxx" fi ok "boxx $TAG installed to $PREFIX/boxx" # ── Done ────────────────────────────────────────────────────────────────────── echo "$PREFIX/boxx" doctor || true echo say "Ready. Run \`boxx\` for the TUI or \`boxx install --host \` to deploy your first app." if [ "$OS" = "linux" ]; then echo info "Tip: if this is a fresh login, run \`newgrp docker\` (or re-login) to use Docker without sudo." echo PUBLIC_IP="$(curl -fsSL --max-time 5 https://api.ipify.org 2>/dev/null || true)" if [ -n "$PUBLIC_IP" ]; then say "This server's public IP is: \033[1m$PUBLIC_IP\033[0m" info "Point your domain's A record to $PUBLIC_IP, then run:" info " boxx install --host " fi fi