mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
* feat(deploy): add one-click Docker/Podman Compose installer for Linux and macOS - Add install.sh with interactive wizard, Podman/Docker runtime detection, port conflict check, health verification, and systemd user unit creation - Add update.sh for image pull and restart with health check - Add uninstall.sh with interactive user data backup before removal - Unify CLI output styling with step/ok/warn/error/info helpers - Add install-guide.md documentation - Add install.test.ts integration test suite * feat(deploy): add one-click Docker/Podman Compose installer - interactive setup wizard with port, image, CORS, memory prompts - automatic Docker/Podman detection with install guidance - systemd user unit for Linux, health check polling - update.sh (pull + restart + prune) and uninstall.sh (backup + cleanup) - node:test integration suite and install-guide.md * style(deploy): improve POSIX sh compatibility and systemd unit handling - unify shell shebangs to #!/usr/bin/env bash - add pipefail option for better error handling - fix systemd unit for Podman: remove After/Requires when no service - correct documentation to match actual uninstall behavior * fix(deploy): address review feedback for installer scripts - remove curl | sh path, document clone-first only - isolate tests via docker-compose.override.yml with unique names - support both --image <ref> and --image=<ref> in update.sh - add running container detection before install * docs(install): remove demo scripts and add MCP note
141 lines
4.9 KiB
Bash
Executable file
141 lines
4.9 KiB
Bash
Executable file
#!/usr/bin/env bash
|
||
# Open Design — Updater
|
||
# Pulls the latest image and restarts the service
|
||
#
|
||
# Usage: ./update.sh [--image <ref>] [--non-interactive]
|
||
set -euo pipefail
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||
DEPLOY_DIR="$(dirname "$SCRIPT_DIR")"
|
||
COMPOSE_FILE="${DEPLOY_DIR}/docker-compose.yml"
|
||
OVERRIDE_FILE="${DEPLOY_DIR}/docker-compose.override.yml"
|
||
HEALTH_TIMEOUT=60
|
||
|
||
COMPOSE_FILES=(-f "$COMPOSE_FILE")
|
||
if [ -f "$OVERRIDE_FILE" ]; then
|
||
COMPOSE_FILES+=(-f "$OVERRIDE_FILE")
|
||
fi
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Colors & formatting
|
||
# ---------------------------------------------------------------------------
|
||
BOLD="" DIM="" RED="" GREEN="" YELLOW="" CYAN="" RESET=""
|
||
if [ -t 1 ]; then
|
||
BOLD="\033[1m" DIM="\033[2m" RED="\033[31m" GREEN="\033[32m"
|
||
YELLOW="\033[33m" CYAN="\033[36m" RESET="\033[0m"
|
||
fi
|
||
|
||
step() { printf " ${DIM}▸${RESET} %s\n" "$1"; }
|
||
ok() { printf " ${GREEN}✓${RESET} %s\n" "$1"; }
|
||
warn() { printf " ${YELLOW}!${RESET} %s\n" "$1" >&2; }
|
||
error() { printf " ${RED}✗${RESET} %s\n" "$1" >&2; }
|
||
info() { printf " ${CYAN}›${RESET} %s\n" "$1"; }
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Detect container runtime
|
||
# ---------------------------------------------------------------------------
|
||
COMPOSE_CMD=""
|
||
if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
|
||
COMPOSE_CMD="docker compose"
|
||
elif command -v podman >/dev/null 2>&1 && podman compose version >/dev/null 2>&1; then
|
||
COMPOSE_CMD="podman compose"
|
||
elif command -v podman >/dev/null 2>&1 && command -v podman-compose >/dev/null 2>&1; then
|
||
COMPOSE_CMD="podman-compose"
|
||
elif command -v docker >/dev/null 2>&1 && command -v docker-compose >/dev/null 2>&1; then
|
||
COMPOSE_CMD="docker-compose"
|
||
else
|
||
error "No container runtime found. Install Docker or Podman."
|
||
exit 1
|
||
fi
|
||
|
||
OPT_IMAGE=""
|
||
NON_INTERACTIVE=0
|
||
|
||
while [ $# -gt 0 ]; do
|
||
case "$1" in
|
||
--image) shift; OPT_IMAGE="$1" ;;
|
||
--image=*) OPT_IMAGE="${1#--image=}" ;;
|
||
--non-interactive) NON_INTERACTIVE=1 ;;
|
||
--help|-h)
|
||
echo "Usage: update.sh [options]"
|
||
echo " --image <ref> Pull a specific image instead of latest"
|
||
echo " --non-interactive Skip confirmation prompts"
|
||
exit 0
|
||
;;
|
||
esac
|
||
shift
|
||
done
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Banner
|
||
# ---------------------------------------------------------------------------
|
||
printf "\n"
|
||
printf "${BOLD} ┌──────────────────────────────────────┐${RESET}\n"
|
||
printf "${BOLD} │${RESET} ${BOLD}│${RESET}\n"
|
||
printf "${BOLD} │${RESET} ${CYAN}◈${RESET} ${BOLD}Open Design${RESET} ${BOLD}│${RESET}\n"
|
||
printf "${BOLD} │${RESET} ${DIM}Updater${RESET} ${BOLD}│${RESET}\n"
|
||
printf "${BOLD} │${RESET} ${BOLD}│${RESET}\n"
|
||
printf "${BOLD} └──────────────────────────────────────┘${RESET}\n"
|
||
printf "\n"
|
||
|
||
# Read current port from .env if it exists
|
||
PORT=7456
|
||
ENV_FILE="${DEPLOY_DIR}/.env"
|
||
if [ -f "$ENV_FILE" ]; then
|
||
_port="$(grep '^OPEN_DESIGN_PORT=' "$ENV_FILE" | cut -d= -f2)"
|
||
if [ -n "$_port" ]; then PORT="$_port"; fi
|
||
fi
|
||
|
||
# Override image if specified
|
||
if [ -n "$OPT_IMAGE" ]; then
|
||
export OPEN_DESIGN_IMAGE="$OPT_IMAGE"
|
||
fi
|
||
|
||
# Pull latest image
|
||
step "Pulling latest image..."
|
||
$COMPOSE_CMD "${COMPOSE_FILES[@]}" pull
|
||
|
||
# Restart with new image
|
||
step "Restarting service..."
|
||
$COMPOSE_CMD "${COMPOSE_FILES[@]}" up -d --no-build
|
||
|
||
# Health check
|
||
step "Waiting for health check (up to ${HEALTH_TIMEOUT}s)..."
|
||
HEALTH_URL="http://127.0.0.1:${PORT}/api/health"
|
||
HEALTH_OK=0
|
||
ELAPSED=0
|
||
|
||
while [ "$ELAPSED" -lt "$HEALTH_TIMEOUT" ]; do
|
||
if command -v curl >/dev/null 2>&1; then
|
||
HTTP_CODE="$(curl -s -o /dev/null -w '%{http_code}' "$HEALTH_URL" 2>/dev/null || echo '000')"
|
||
elif command -v wget >/dev/null 2>&1; then
|
||
HTTP_CODE="$(wget -q -O /dev/null --server-response "$HEALTH_URL" 2>&1 | grep 'HTTP/' | tail -1 | awk '{print $2}')"
|
||
else
|
||
HTTP_CODE="000"
|
||
fi
|
||
|
||
if [ "$HTTP_CODE" = "200" ]; then
|
||
HEALTH_OK=1
|
||
break
|
||
fi
|
||
sleep 2
|
||
ELAPSED=$((ELAPSED + 2))
|
||
done
|
||
|
||
if [ "$HEALTH_OK" = "1" ]; then
|
||
ok "Update complete. Daemon is healthy."
|
||
else
|
||
warn "Health check did not pass."
|
||
step "Check logs: ${COMPOSE_CMD} \"${COMPOSE_FILES[@]}\" logs"
|
||
fi
|
||
|
||
# Clean up dangling images
|
||
step "Cleaning up old images..."
|
||
# shellcheck disable=SC2086
|
||
${COMPOSE_CMD%% *}$ image prune -f >/dev/null 2>&1 || true
|
||
|
||
printf "\n"
|
||
printf "${BOLD}${GREEN} ── Update Complete ───────────────────────────────${RESET}\n"
|
||
printf "\n"
|
||
printf " URL: http://127.0.0.1:%s\n" "$PORT"
|
||
printf "\n"
|