diff --git a/install.sh b/install.sh index a372a96..74003fe 100755 --- a/install.sh +++ b/install.sh @@ -23,36 +23,146 @@ warn() { printf "${YELLOW}⚠${NC} %s\n" "$1"; } err() { printf "${RED}✗${NC} %s\n" "$1"; } info() { printf "${CYAN}→${NC} %s\n" "$1"; } -# ── Prerequisites ──────────────────────────────── -prereqs=("git" "docker") -missing=() -for cmd in "${prereqs[@]}"; do - if ! command -v "$cmd" &>/dev/null; then - missing+=("$cmd") +# ── OS detection ────────────────────────────────── +detect_os() { + if [ -f /etc/os-release ]; then + . /etc/os-release + echo "$ID" + elif command -v lsb_release &>/dev/null; then + lsb_release -si | tr '[:upper:]' '[:lower:]' + else + echo "unknown" fi -done +} + +OS=$(detect_os) +ARCH=$(uname -m) + +# ── Git ────────────────────────────────────────── +if ! command -v git &>/dev/null; then + info "Installing git..." + case "$OS" in + ubuntu|debian) sudo apt update -qq && sudo apt install -y -qq git ;; + arch|endeavouros) sudo pacman -S --noconfirm git ;; + fedora|centos|rhel|almalinux|rocky) sudo dnf install -y git ;; + *) err "Unsupported OS: $OS. Install git manually." && exit 1 ;; + esac + log "git installed" +fi + +# ── Docker ─────────────────────────────────────── +NEED_DOCKER=false +NEED_COMPOSE=false + +if ! command -v docker &>/dev/null; then + NEED_DOCKER=true +fi if ! docker compose version &>/dev/null && ! docker-compose --version &>/dev/null; then - missing+=("docker compose plugin") + NEED_COMPOSE=true fi -if [ ${#missing[@]} -gt 0 ]; then - err "Missing prerequisites: ${missing[*]}" - info "Install them and re-run:" - info " Ubuntu/Debian: sudo apt update && sudo apt install -y git docker.io docker-compose-v2" - info " Arch: sudo pacman -S git docker docker-compose" - info " Fedora: sudo dnf install -y git docker docker-compose" - exit 1 +if $NEED_DOCKER; then + info "Installing Docker Engine..." + case "$OS" in + ubuntu|debian) + sudo apt update -qq + sudo apt install -y -qq ca-certificates curl + sudo install -m 0755 -d /etc/apt/keyrings + sudo curl -fsSL https://download.docker.com/linux/"$OS"/gpg -o /etc/apt/keyrings/docker.asc + sudo chmod a+r /etc/apt/keyrings/docker.asc + echo "deb [arch=$ARCH signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/$OS $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ + | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt update -qq + sudo apt install -y -qq docker-ce docker-ce-cli containerd.io + ;; + arch|endeavouros) + sudo pacman -S --noconfirm docker + ;; + fedora) + sudo dnf install -y dnf-plugins-core + sudo dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo + sudo dnf install -y docker-ce docker-ce-cli containerd.io + ;; + centos|rhel|almalinux|rocky) + sudo dnf install -y dnf-plugins-core + sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo + sudo dnf install -y docker-ce docker-ce-cli containerd.io + ;; + *) + err "Unsupported OS: $OS. Install Docker manually: https://docs.docker.com/engine/install/" + exit 1 + ;; + esac + log "Docker Engine installed" fi -log "Prerequisites satisfied" -# Check docker is running +if $NEED_COMPOSE && ! command -v docker &>/dev/null; then + # fresh install — compose plugin is already included in docker-ce package + NEED_COMPOSE=false +fi + +if $NEED_COMPOSE; then + info "Installing Docker Compose plugin..." + case "$OS" in + ubuntu|debian) + sudo apt install -y -qq docker-compose-v2 2>/dev/null || { + sudo apt install -y -qq docker-compose-plugin 2>/dev/null || { + warn "Compose plugin not in apt. Installing via CLI plugin..." + CLI_PLUGINS="/usr/lib/docker/cli-plugins" + sudo mkdir -p "$CLI_PLUGINS" + sudo curl -fsSL "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o "$CLI_PLUGINS/docker-compose" + sudo chmod +x "$CLI_PLUGINS/docker-compose" + } + } + ;; + arch|endeavouros) + sudo pacman -S --noconfirm docker-compose + ;; + fedora|centos|rhel|almalinux|rocky) + sudo dnf install -y docker-compose-v2 2>/dev/null || { + CLI_PLUGINS="/usr/lib/docker/cli-plugins" + sudo mkdir -p "$CLI_PLUGINS" + sudo curl -fsSL "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o "$CLI_PLUGINS/docker-compose" + sudo chmod +x "$CLI_PLUGINS/docker-compose" + } + ;; + esac + log "Docker Compose plugin installed" +fi + +log "Docker prerequisites satisfied" + +# ── Start Docker daemon ───────────────────────── if ! docker info &>/dev/null; then - err "Docker daemon is not running. Start it with: sudo systemctl start docker" - exit 1 + info "Starting Docker daemon..." + if command -v systemctl &>/dev/null; then + sudo systemctl enable --now docker + elif command -v service &>/dev/null; then + sudo service docker start + else + sudo dockerd &>/dev/null & + sleep 3 + fi fi log "Docker daemon is running" +# ── Add user to docker group ──────────────────── +if ! groups "$USER" | grep -q docker; then + info "Adding $USER to the docker group..." + sudo usermod -aG docker "$USER" + # Spawn subshell with the new group so rest of script can use docker without sudo + sg docker -c "true" 2>/dev/null || true + warn "You may need to re-login for group changes to take effect in new shells" +fi + +# Use sudo for docker commands if the current shell can't run docker directly +if docker info &>/dev/null 2>&1; then + DOCKER_CMD="docker" +else + DOCKER_CMD="sg docker -c docker" +fi + # ── Clone / Pull ───────────────────────────────── if [ -d "$INSTALL_DIR" ]; then info "Directory $INSTALL_DIR already exists — pulling latest" @@ -86,22 +196,22 @@ log "Data directory ready" # ── Build & Start ──────────────────────────────── info "Building Docker image (this may take a few minutes)..." -docker compose build web +$DOCKER_CMD compose build web log "Build complete" info "Starting containers..." -docker compose up -d +$DOCKER_CMD compose up -d log "Containers are running" # ── Wait for health ────────────────────────────── info "Waiting for web service to be healthy..." for i in $(seq 1 30); do - if docker compose exec -T web wget --no-verbose --tries=1 --spider http://127.0.0.1:3000/api/health &>/dev/null; then + if $DOCKER_CMD compose exec -T web wget --no-verbose --tries=1 --spider http://127.0.0.1:3000/api/health &>/dev/null; then log "Web service is healthy" break fi if [ "$i" -eq 30 ]; then - warn "Timed out waiting for health check — check logs with: docker compose logs web" + warn "Timed out waiting for health check — check logs with: $DOCKER_CMD compose logs web" fi sleep 2 done @@ -110,17 +220,19 @@ done IP=$(curl -fsSL http://checkip.amazonaws.com 2>/dev/null || curl -fsSL https://api.ipify.org 2>/dev/null || echo "localhost") # ── Summary ────────────────────────────────────── +BOX_W=60 +hr() { printf "║ %-*s ║\n" "$((BOX_W-6))" "$1"; } printf "\n" -printf "╔══════════════════════════════════════════════╗\n" -printf "║ ${GREEN}worst-scan-web installed!${NC} ║\n" -printf "╠══════════════════════════════════════════════╣\n" -printf "║ Web: http://%s:3000 ║\n" "$IP" -printf "║ Config: %s/.env ║\n" "$INSTALL_DIR" -printf "║ Logs: docker compose logs -f ║\n" -printf "║ Restart: docker compose restart ║\n" -printf "║ Stop: docker compose down ║\n" -printf "║ Update: bash %s/install.sh ║\n" "$INSTALL_DIR" -printf "╚══════════════════════════════════════════════╝\n" +printf "╔══════════════════════════════════════════════════════════════╗\n" +printf "║ ${GREEN}worst-scan-web installed!${NC} ║\n" +printf "╠══════════════════════════════════════════════════════════════╣\n" +hr "Web: http://$IP:3000" +hr "Config: $INSTALL_DIR/.env" +hr "Logs: cd $INSTALL_DIR && docker compose logs -f" +hr "Restart: cd $INSTALL_DIR && docker compose restart" +hr "Stop: cd $INSTALL_DIR && docker compose down" +hr "Update: bash install.sh" +printf "╚══════════════════════════════════════════════════════════════╝\n" printf "\n" info "Next steps:" printf " 1. Open http://%s:3000/setup in your browser\n" "$IP"