diff --git a/scripts/init_macos.sh b/scripts/init_macos.sh new file mode 100644 index 000000000..5f63a263c --- /dev/null +++ b/scripts/init_macos.sh @@ -0,0 +1,216 @@ +#!/usr/bin/env bash +set -euo pipefail + +PROXY="${PROXY:-caddy}" +INSTALL_METHOD="${INSTALL_METHOD:-npm}" +GIT_REPO="${GIT_REPO:-https://github.com/cloud-neutral-toolkit/clawdbot.svc.plus.git}" +CLAWDBOT_VERSION="${CLAWDBOT_VERSION:-latest}" +PUBLIC_SCHEME="https" + +usage() { + cat <<'EOF' +Usage: + init_macos.sh [domain] + +Defaults: + - domain: current hostname (hostname -f, then hostname) + - clawdbot version: "latest" (override with CLAWDBOT_VERSION env var) + - install method: npm (set INSTALL_METHOD=git to install from the cloned repo in /opt) + - proxy: Caddy with automatic TLS (PROXY is always caddy on macOS) + +Examples: + ./init_macos.sh + ./init_macos.sh clawdbot.svc.plus +EOF +} + +if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then + usage + exit 0 +fi + +if [[ "$(uname -s 2>/dev/null || true)" != "Darwin" ]]; then + echo "This installer is macOS-only." + exit 1 +fi + +DOMAIN="${1:-}" +if [[ -z "$DOMAIN" ]]; then + DOMAIN="$(hostname -f 2>/dev/null || true)" + if [[ -z "$DOMAIN" ]]; then + DOMAIN="$(hostname 2>/dev/null || true)" + fi +fi + +if [[ -z "$DOMAIN" ]]; then + echo "Failed to determine domain (hostname). Pass one explicitly." + exit 1 +fi + +PROXY="$(tr '[:upper:]' '[:lower:]' <<< "$PROXY")" +if [[ "$PROXY" != "caddy" ]]; then + echo "Only PROXY=caddy is supported on macOS." + exit 1 +fi + +INSTALL_METHOD="$(tr '[:upper:]' '[:lower:]' <<< "$INSTALL_METHOD")" +if [[ "$INSTALL_METHOD" != "npm" && "$INSTALL_METHOD" != "git" ]]; then + echo "Unsupported install method '$INSTALL_METHOD'. Use 'npm' or 'git'." + exit 1 +fi + +as_root() { + if [[ "$(id -u)" -eq 0 ]]; then + if [[ "${1:-}" == "-E" ]]; then + shift + fi + "$@" + else + sudo "$@" + fi +} + +run_as_user() { + local user="${SUDO_USER:-$USER}" + if [[ "$user" == "root" ]]; then + echo "Run this installer as a non-root user (with sudo available)." + exit 1 + fi + sudo -u "$user" -H "$@" +} + +ensure_packages_darwin() { + if ! command -v brew >/dev/null 2>&1; then + echo "Homebrew is required on macOS. Install it from https://brew.sh and re-run." + exit 1 + fi + brew install git caddy curl +} + +ensure_node24_darwin() { + local need_install=1 + if command -v node >/dev/null 2>&1; then + local major + major="$(node -v | sed -E 's/^v([0-9]+).*/\1/')" + if [[ "${major:-0}" -ge 24 ]]; then + need_install=0 + fi + fi + if [[ "$need_install" -eq 1 ]]; then + if command -v brew >/dev/null 2>&1; then + brew install node@24 || brew install node + if brew list node@24 >/dev/null 2>&1; then + brew link --overwrite --force node@24 + fi + else + local arch pkg_name pkg_url pkg_path + arch="$(uname -m)" + case "$arch" in + arm64) arch="arm64" ;; + x86_64) arch="x64" ;; + *) + echo "Unsupported macOS architecture: ${arch}" + exit 1 + ;; + esac + pkg_name="$(curl -fsSL https://nodejs.org/dist/latest-v24.x/ \ + | awk -F\" -v arch="$arch" '/node-v24.*-darwin-/{if ($2 ~ ("-darwin-" arch "\\.pkg$")) {print $2; exit}}')" + if [[ -z "$pkg_name" ]]; then + echo "Failed to find a Node.js v24 macOS installer." + exit 1 + fi + pkg_url="https://nodejs.org/dist/latest-v24.x/${pkg_name}" + pkg_path="/tmp/${pkg_name}" + curl -fsSL "$pkg_url" -o "$pkg_path" + as_root installer -pkg "$pkg_path" -target / + fi + fi +} + +ensure_pnpm() { + run_as_user corepack enable + run_as_user corepack prepare pnpm@latest --activate +} + +install_clawdbot_npm() { + as_root npm install -g "clawdbot@${CLAWDBOT_VERSION}" +} + +install_clawdbot_git() { + local install_dir="/opt/clawdbot-svc-plus" + if [[ ! -d "$install_dir" ]]; then + run_as_user mkdir -p "$install_dir" + run_as_user git clone "$GIT_REPO" "$install_dir" + else + run_as_user git -C "$install_dir" fetch --all --prune + run_as_user git -C "$install_dir" checkout main + run_as_user git -C "$install_dir" reset --hard origin/main + fi + run_as_user bash -c "cd $install_dir && pnpm install && pnpm build" + run_as_user npm install -g "$install_dir" +} + +install_clawdbot() { + if [[ "$INSTALL_METHOD" == "git" ]]; then + install_clawdbot_git + else + install_clawdbot_npm + fi +} + +configure_clawdbot() { + run_as_user clawdbot onboard --install-daemon + run_as_user clawdbot config set gateway.trustedProxies.0 127.0.0.1 +} + +configure_caddy() { + local service + service="$(brew --prefix)/etc/Caddyfile" + cat </dev/null +${DOMAIN} { + reverse_proxy 127.0.0.1:18789 +} +EOF + brew services start caddy || brew services restart caddy +} + +health_check_url() { + local url="$1" + for i in $(seq 1 5); do + if curl -fsS --max-time 5 --retry 3 --retry-delay 2 "$url" >/dev/null 2>&1; then + return 0 + fi + sleep 2 + done + return 1 +} + +run_health_checks() { + if ! health_check_url http://127.0.0.1:18789; then + echo "Warning: local gateway health check failed." + fi + local target="${PUBLIC_SCHEME}://${DOMAIN}" + if ! health_check_url "${target}"; then + echo "Warning: public health check failed for ${target}. TLS might not be active yet." + fi +} + +echo "==> Domain: ${DOMAIN}" +ensure_packages_darwin +ensure_node24_darwin +ensure_pnpm +install_clawdbot +configure_clawdbot +configure_caddy +run_health_checks + +cat <