#!/usr/bin/env bash # # LinkBreeze — One-line installer # # Usage: # curl -fsSL https://linkbreeze.omnirise.dev/install.sh | bash # # Or clone and run locally: # bash scripts/install.sh # # What it does: # 1. Detects Docker or Podman (prefers Docker) # 2. Pulls the latest LinkBreeze image from GHCR # 3. Runs the container with sensible defaults # 4. Optionally creates a systemd service for auto-start on boot # set -euo pipefail # ── Colors ────────────────────────────────────────────────────────────────── if [[ -t 1 ]]; then BOLD='\033[1m' GREEN='\033[0;32m' YELLOW='\033[0;33m' BLUE='\033[0;34m' RED='\033[0;31m' NC='\033[0m' else BOLD=''; GREEN=''; YELLOW=''; BLUE=''; RED=''; NC='' fi info() { echo -e "${BLUE}ℹ${NC} $*"; } success() { echo -e "${GREEN}✓${NC} $*"; } warn() { echo -e "${YELLOW}⚠${NC} $*"; } error() { echo -e "${RED}✗${NC} $*" >&2; } # ── Config ─────────────────────────────────────────────────────────────────── IMAGE="docker.io/omnirise/linkbreeze:latest" CONTAINER_NAME="linkbreeze" PORT="${LINKBREEZE_PORT:-3000}" DATA_VOLUME="linkbreeze-data" # ── Banner ─────────────────────────────────────────────────────────────────── cat << 'EOF' ▐▀▀▀▀█ ▀▀▀▀▀▀▀▀▀▄ █▀▀▀▀█▐▀▀▀▀█ █▀▀▀▀█▐▀▀▀▀▀▀▀▀▀█▄▄ ▐▀▀▀▀▀▀▀▀▀█▄▄ ▐▀▀▀▀▀▀▀▀▀▀▀▀█▐▀▀▀▀▀▀▀▀▀▀▀▀█▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▐▀▀▀▀▀▀▀▀▀▀▀▀█ ▐ ████ ▀▀▀▀▀▀▀▀▄ ▀▄ ██████▐ ████ █ ████▐█████████████▄ ▐ ████████████▄ ▐ ████████████▐ ████████████▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▐ ████████████ ▐ ████ ▀▀▀▀▀▀▀▀ ▀▄ ▀▄ ██████▐ ████ █ ████ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀█▀████▌▐ ████▀▀▀▀▀▀▀▀▐ ████▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▐ ████▀▀▀▀▀▀▀▀ ▐ ████ █▀▀▀▀█ ▀▄ ▀▄ ▀▄ ██████▐ ████▄▄▄▄█▀▄███▌▐▀▀▀▀█ ▀▀▀▀▀▀▀ ▐ █ █ ▄▄▄█ ████▌▐ ████▄▄▄▄▄▄▄▄▐ ████▄▄▄▄▄▄▄▄▀▀▀▀▀▀ ▄█▀▀▀███▀▐ ████▄▄▄▄▄▄▄▄ ▐ ████ █ ████ ▀▄ ▀▄ ▀▄ █▄████▐ █████▄▄▄▄████▀ ▐█████ ▀▀▀▀▀▀▀▀▀ ▐ █ █ ██▄▄████▀ ▐ ████▄▄▄▄▄▄▄█▐ ████▄▄▄▄▄▄▄█ ▄█▀▄████▀ ▄▐ ████▄▄▄▄▄▄▄█ ▐ ████ █ ████ ▐█▄ ▀▄ ▀▄ ▀▄ ▀████▐ ████████████ ▄▀▐█████ ▀▀▀▀▀▀▀▀▀ ▐ █ █ ██████▀ ▐ ████████████▐ ████████████ ▄█▀▄████▀ ▄▀ ▐ ████████████ ▐▄████ █ ████ ▐ ██▄ ▀▄ ▀▄ ▀▄ ▀██▐▄████ ▄ ▀▀████▄ ▐█████ ▀ ▄█▀▀▀▀█▌▐ █ █ ▀█▄▀███▄ ▐▄████ ▐▄████ ▄█▀▄████▀ ▀ ▐▄████ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄ █ ████ ▐ ████ ▀▄ ▀▄ ▀▄ ▀▄▄▄▄▄▄ ▀▄ █████▐█████▀▀▀▀▄█████ ▐ █ █ ▄ ▀█ ████▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▐▀▄█████████████▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄ █ ████ ▐ ████ ▀▄ ▀▄ ▀▄ ▀▄▄▄▄▄▄▄ █ ▄▄▄▄▄▄▐█████████████▀ ▐ █ █ ▀▄▐█ ████▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▐ ██████████████▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄ █▄████ ▐▄████ ▀▄ ▀▄ █ █▄▄▄▄▄▄ █ ▄▄▄▄▄▄▐▄▄████████▀▀ ▄▀ ▐ █ █ █ █▄████▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▐▄██████████████▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄ ▄▄▄▄▄▄ ▀▄▄ ▄ ▄▄▄▄▄▄▄ █▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▀▀ ▄ ▄ ▄ ▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ EOF # ── Preflight: detect container runtime ────────────────────────────────────── RUNTIME="" if command -v docker &>/dev/null; then RUNTIME="docker" success "Docker detected: $(docker --version)" elif command -v podman &>/dev/null; then RUNTIME="podman" success "Podman detected: $(podman --version)" warn "Using Podman (rootless mode). If you hit permission issues, try: podman run --userns=keep-id ..." else error "Neither Docker nor Podman was found on this system." echo "" echo " Install one of the following:" echo " Docker: https://docs.docker.com/engine/install/" echo " Podman: https://podman.io/docs/installation" echo "" exit 1 fi # ── Preflight: check if a container with the same name exists ──────────────── if "$RUNTIME" ps -a --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER_NAME"; then warn "A container named '$CONTAINER_NAME' already exists." echo "" read -rp $'\033[0;33m?\033[0m Stop and remove the existing container? [y/N] ' RECREATE RECREATE="${RECREATE:-N}" if [[ "${RECREATE,,}" == "y" ]]; then info "Stopping and removing existing container..." "$RUNTIME" rm -f "$CONTAINER_NAME" >/dev/null success "Old container removed." else info "Keeping existing container. Installation cancelled." exit 0 fi fi # ── Pull the image ─────────────────────────────────────────────────────────── info "Pulling $IMAGE..." "$RUNTIME" pull "$IMAGE" success "Image pulled." # ── Run the container ──────────────────────────────────────────────────────── info "Starting LinkBreeze on port $PORT..." if [[ "$RUNTIME" == "docker" ]]; then "$RUNTIME" run -d \ --name "$CONTAINER_NAME" \ --restart unless-stopped \ -p "${PORT}:3000" \ -v "${DATA_VOLUME}:/app/data" \ "$IMAGE" else # Podman: create a volume first (podman volume create is explicit) "$RUNTIME" volume create "$DATA_VOLUME" >/dev/null 2>&1 || true "$RUNTIME" run -d \ --name "$CONTAINER_NAME" \ --restart unless-stopped \ -p "${PORT}:3000" \ -v "${DATA_VOLUME}:/app/data" \ "$IMAGE" fi success "Container started." # ── Wait for health ────────────────────────────────────────────────────────── info "Waiting for LinkBreeze to start..." MAX_WAIT=30 for i in $(seq 1 "$MAX_WAIT"); do if curl -sf "http://localhost:${PORT}/api/health" >/dev/null 2>&1; then success "LinkBreeze is healthy!" break fi if [[ "$i" -eq "$MAX_WAIT" ]]; then warn "LinkBreeze didn't respond to health check within ${MAX_WAIT}s." warn "It may still be starting. Check: $RUNTIME logs -f $CONTAINER_NAME" fi sleep 1 done # ── Systemd service prompt ─────────────────────────────────────────────────── echo "" if [[ "$EUID" -ne 0 ]] && [[ -z "${SUDO_USER:-}" ]]; then # Non-root, no sudo — skip systemd prompt info "Run as root (or with sudo) to optionally generate a systemd service." else read -rp $'\033[0;33m?\033[0m Generate a systemd service so LinkBreeze starts on boot? [y/N] ' SYSTEMD_ANS SYSTEMD_ANS="${SYSTEMD_ANS:-N}" if [[ "${SYSTEMD_ANS,,}" == "y" ]]; then info "Creating systemd service..." SERVICE_FILE="/etc/systemd/system/linkbreeze.service" # Stop the container — systemd will manage it from here "$RUNTIME" stop "$CONTAINER_NAME" >/dev/null 2>&1 || true "$RUNTIME" rm "$CONTAINER_NAME" >/dev/null 2>&1 || true cat > "$SERVICE_FILE" << EOF [Unit] Description=LinkBreeze — Self-hosted link-in-bio Requires=${RUNTIME}.service After=${RUNTIME}.service [Service] Type=simple ExecStartPre=-/usr/bin/${RUNTIME} rm -f ${CONTAINER_NAME} ExecStart=/usr/bin/${RUNTIME} run --name ${CONTAINER_NAME} \\ --restart no \\ -p ${PORT}:3000 \\ -v ${DATA_VOLUME}:/app/data \\ ${IMAGE} ExecStop=/usr/bin/${RUNTIME} stop ${CONTAINER_NAME} Restart=always RestartSec=10 [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable linkbreeze.service >/dev/null 2>&1 systemctl start linkbreeze.service success "systemd service created and enabled!" info "Service file: $SERVICE_FILE" info "Commands:" echo " systemctl status linkbreeze" echo " systemctl stop linkbreeze" echo " systemctl restart linkbreeze" echo " journalctl -u linkbreeze -f" else info "Skipping systemd service. Container will restart via Docker/Podman restart policy." fi fi # ── Done ───────────────────────────────────────────────────────────────────── echo "" echo -e "${GREEN}${BOLD} ✓ LinkBreeze is live!${NC}" echo "" echo -e " Open: ${BOLD}http://localhost:${PORT}${NC}" echo "" echo -e " ${BLUE}First time?${NC} The setup wizard will create your admin account." echo -e " ${BLUE}Docs:${NC} https://linkbreeze.omnirise.dev/docs" echo ""