#!/usr/bin/env bash # ============================================================================== # OpenClaw Local (Bare-Metal) Deployment Script # ============================================================================== # Installs OpenClaw directly on the host via npm. # Source: https://docs.openclaw.ai/install # # Usage: deploy-local-openclaw.sh # deploy-local-openclaw.sh --domain oc.an2.io # deploy-local-openclaw.sh --remove [--purge] [--yes] # ============================================================================== set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${SCRIPT_DIR}/common.sh" # --- Parse arguments --- parse_args "$@" # --- Service config --- SERVICE_NAME="openclaw" BASE_DIR="/opt/${SERVICE_NAME}" UNIT_NAME="${SERVICE_NAME}" # --- Handle --remove --- if [[ "$ARG_REMOVE" == "1" ]]; then require_root echo "" echo -e "${YELLOW}══════════════════════════════════════════════${NC}" echo -e "${YELLOW} Removing ${SERVICE_NAME} (local)${NC}" echo -e "${YELLOW}══════════════════════════════════════════════${NC}" if [[ "$ARG_PURGE" == "1" ]]; then echo -e " Mode: ${RED}Purge (delete all data)${NC}" else echo -e " Mode: Safe (keep data)" fi echo -e " Data: ${BASE_DIR}" echo -e " Systemd: ${UNIT_NAME}.service" echo -e "${YELLOW}══════════════════════════════════════════════${NC}" echo "" if [[ "$ARG_PURGE" == "1" ]] && [[ -d "$BASE_DIR" ]]; then backup_script="${SCRIPT_DIR}/backup-${SERVICE_NAME}.sh" [[ -f "$backup_script" ]] && warn "Back up first? ${backup_script}" echo "" fi unit_file="/etc/systemd/system/${UNIT_NAME}.service" if [[ -f "$unit_file" ]]; then systemctl stop "${UNIT_NAME}.service" > /dev/null 2>&1 || true systemctl disable "${UNIT_NAME}.service" > /dev/null 2>&1 || true rm -f "$unit_file" systemctl daemon-reload ok "Removed systemd unit: ${UNIT_NAME}.service" fi remove_traefik_dynamic_config "$SERVICE_NAME" if [[ "$ARG_PURGE" == "1" ]]; then echo "" warn "This will permanently delete: $BASE_DIR" if [[ "$ARG_YES" != "1" ]]; then read -rp "Are you sure? [y/N] " confirm if [[ "${confirm,,}" != "y" ]]; then info "Purge cancelled." exit 0 fi fi rm -rf "$BASE_DIR" rm -rf "$HOME/.openclaw" 2>/dev/null ok "Purged: $BASE_DIR" fi echo "" echo -e "${GREEN}══════════════════════════════════════════════${NC}" echo -e "${GREEN} ${SERVICE_NAME} removed${NC}" echo -e "${GREEN}══════════════════════════════════════════════${NC}" if [[ "$ARG_PURGE" != "1" ]]; then echo -e " Data preserved at ${BASE_DIR}" echo -e " To purge: $0 --remove --purge" fi echo -e " To redeploy: $0" echo -e "${GREEN}══════════════════════════════════════════════${NC}" echo "" exit 0 fi # --- Domain resolution --- if [[ -n "$ARG_DOMAIN" ]]; then DOMAIN="$ARG_DOMAIN" elif [[ -f "${BASE_DIR}/.env" ]] && grep -q '^DOMAIN=' "${BASE_DIR}/.env"; then DOMAIN="$(grep '^DOMAIN=' "${BASE_DIR}/.env" | cut -d= -f2)" else DOMAIN="oc.an2.io" fi # --- Detect current state for banner --- install_mode="Fresh install" if [[ -f "${BASE_DIR}/.env" ]]; then install_mode="Re-run (preserving data)" fi service_status="Not running" if systemctl is-active --quiet "${UNIT_NAME}.service" 2>/dev/null; then service_status="Running" fi # --- Print deployment plan --- echo "" echo -e "${CYAN}══════════════════════════════════════════════${NC}" echo -e "${CYAN} Deploying OpenClaw (Local)${NC}" echo -e "${CYAN}══════════════════════════════════════════════${NC}" echo -e " Domain: ${DOMAIN}" echo -e " Data: ${BASE_DIR}/data/" echo -e " Service: ${service_status}" echo -e " Mode: ${install_mode}" echo -e "${CYAN}══════════════════════════════════════════════${NC}" echo "" # --- Check if already deployed and running --- if [[ -f "${BASE_DIR}/.env" ]] && \ systemctl is-active --quiet "${UNIT_NAME}.service" 2>/dev/null; then echo -e "${GREEN} OpenClaw is already running — nothing to do.${NC}" echo "" echo -e " Health: curl -fsS http://127.0.0.1:18789/healthz" echo -e " Logs: journalctl -u ${UNIT_NAME}.service -f" echo -e " Backup: ${SCRIPT_DIR}/backup-openclaw.sh" echo -e " Remove: $0 --remove [--purge]" echo "" exit 0 fi # --- Infrastructure --- require_root detect_os # --- Install Node.js 22+ --- install_nodejs_22() { if command -v node &>/dev/null; then local node_major node_major="$(node --version | sed 's/^v//' | cut -d. -f1)" if (( node_major >= 22 )); then ok "Node.js $(node --version) already installed." return 0 fi warn "Node.js $(node --version) is too old (need 22+). Upgrading..." fi info "Installing Node.js 22..." case "$DISTRO_FAMILY" in debian) curl -fsSL https://deb.nodesource.com/setup_22.x | bash - > /dev/null 2>&1 apt-get install -y -qq nodejs > /dev/null 2>&1 ;; fedora) curl -fsSL https://rpm.nodesource.com/setup_22.x | bash - > /dev/null 2>&1 dnf install -y -q nodejs > /dev/null 2>&1 ;; esac ok "Node.js $(node --version) installed." } # Install base prerequisites case "$DISTRO_FAMILY" in debian) export DEBIAN_FRONTEND=noninteractive apt-get update -qq apt-get install -y -qq curl openssl jq git ca-certificates > /dev/null 2>&1 ;; fedora) dnf install -y -q curl openssl jq git ca-certificates > /dev/null 2>&1 ;; esac install_nodejs_22 # --- Backup restore check --- if check_and_restore_backup "$SERVICE_NAME"; then saved_domain="$DOMAIN" # shellcheck source=/dev/null source "${BASE_DIR}/.env" DOMAIN="$saved_domain" ok "Backup restored." else # --- Fresh install --- if [[ -f "${BASE_DIR}/.env" ]]; then saved_domain="$DOMAIN" # shellcheck source=/dev/null source "${BASE_DIR}/.env" DOMAIN="$saved_domain" info "Existing installation found. Updating..." npm update -g openclaw 2>/dev/null || true else info "Installing OpenClaw via npm..." npm install -g openclaw@latest mkdir -p "${BASE_DIR}/data" # Redirect ~/.openclaw to our data dir rm -rf "$HOME/.openclaw" 2>/dev/null ln -sfn "${BASE_DIR}/data" "$HOME/.openclaw" # --- Generate .env --- cat > "${BASE_DIR}/.env" < "/etc/systemd/system/${UNIT_NAME}.service" < /dev/null 2>&1 systemctl restart "${UNIT_NAME}.service" ok "Systemd unit installed and started: ${UNIT_NAME}.service" # --- Wait for health --- info "Waiting for OpenClaw to become healthy (up to 60s)..." elapsed=0 while (( elapsed < 60 )); do if curl -fsS http://127.0.0.1:18789/ > /dev/null 2>&1; then ok "OpenClaw is responding." break fi sleep 5 (( elapsed += 5 )) done if (( elapsed >= 60 )); then warn "OpenClaw did not respond within 60s. Check: journalctl -u ${UNIT_NAME}.service" fi # --- Configure gateway for Traefik/browser access --- OPENCLAW_CONFIG_JSON="${BASE_DIR}/data/openclaw.json" if [[ -f "$OPENCLAW_CONFIG_JSON" ]]; then python3 - <