diff --git a/install/install.sh b/install/install.sh index 8d6fb773a..07a453f39 100755 --- a/install/install.sh +++ b/install/install.sh @@ -332,6 +332,26 @@ create_user() { log_success "Service user created" } +# Ensure a directory exists and is owned by the current user. Used on macOS so +# the install tree stays user-owned (git/venv/npm never touch a root-owned dir). +# Only elevates when the target's parent is root-owned (e.g. /opt); a path under +# $HOME is created without any sudo prompt. +ensure_user_owned_dir() { + local dir="$1" + + if [[ -d "$dir" ]] && [[ -w "$dir" ]]; then + return + fi + + if mkdir -p "$dir" 2>/dev/null; then + return + fi + + log_info "Creating $dir (requires your password)..." + sudo mkdir -p "$dir" + sudo chown "$(id -un):$(id -gn)" "$dir" +} + download_bambuddy() { log_info "Downloading BamBuddy..." @@ -343,7 +363,23 @@ download_bambuddy() { exit 1 fi - if [[ -d "$INSTALL_PATH/.git" ]]; then + if [[ "$OS_TYPE" == "macos" ]]; then + # macOS has no service user — the whole install runs as the current user. + # Create the target user-owned (elevating only if its parent is root-owned, + # e.g. /opt), then clone/update without sudo so the venv/frontend the user + # builds next aren't fighting a root-owned tree. + ensure_user_owned_dir "$INSTALL_PATH" + if [[ -d "$INSTALL_PATH/.git" ]]; then + log_info "Existing installation found, updating..." + git config --global --add safe.directory "$INSTALL_PATH" 2>/dev/null || true + cd "$INSTALL_PATH" + git fetch origin + git checkout "$BRANCH" 2>/dev/null || git checkout -b "$BRANCH" "origin/$BRANCH" + git reset --hard "origin/$BRANCH" + else + git clone --branch "$BRANCH" https://github.com/maziggy/bambuddy.git "$INSTALL_PATH" + fi + elif [[ -d "$INSTALL_PATH/.git" ]]; then log_info "Existing installation found, updating..." # Add safe.directory to avoid "dubious ownership" error when running as root git config --global --add safe.directory "$INSTALL_PATH" 2>/dev/null || true @@ -472,9 +508,12 @@ build_frontend() { create_directories() { log_info "Creating data directories..." - sudo mkdir -p "$DATA_DIR" "$LOG_DIR" - - if [[ "$OS_TYPE" != "macos" ]]; then + if [[ "$OS_TYPE" == "macos" ]]; then + # Rootless: DATA_DIR/LOG_DIR default under the user-owned install path. + ensure_user_owned_dir "$DATA_DIR" + ensure_user_owned_dir "$LOG_DIR" + else + sudo mkdir -p "$DATA_DIR" "$LOG_DIR" sudo chown -R "$SERVICE_USER:$SERVICE_USER" "$DATA_DIR" "$LOG_DIR" fi @@ -503,11 +542,15 @@ LOG_LEVEL=$LOG_LEVEL LOG_TO_FILE=true EOF - sudo mv /tmp/bambuddy.env "$env_file" - if [[ "$OS_TYPE" != "macos" ]]; then + if [[ "$OS_TYPE" == "macos" ]]; then + # Rootless: install path is user-owned, so write it directly. + mv /tmp/bambuddy.env "$env_file" + chmod 600 "$env_file" + else + sudo mv /tmp/bambuddy.env "$env_file" sudo chown "$SERVICE_USER:$SERVICE_USER" "$env_file" + sudo chmod 600 "$env_file" fi - sudo chmod 600 "$env_file" log_success "Environment file created at $env_file" } @@ -845,6 +888,27 @@ main() { detect_os log_success "Detected: $OS_TYPE (package manager: $PKG_MANAGER)" + # macOS must run rootless. Homebrew hard-refuses to run as root, and a venv + # / node_modules created by root can't be managed by the launchd agent (which + # runs as the user). Bail early with an actionable message instead of dying + # halfway through on "brew: running as root is not supported". + if [[ "$OS_TYPE" == "macos" ]]; then + if [[ "$EUID" -eq 0 ]]; then + log_error "Don't run the macOS installer with sudo." + log_info "Homebrew, the Python venv, and the launchd agent must all be created as your" + log_info "normal user. Re-run without sudo (the script elevates only when it truly needs to):" + echo "" + echo " ./install.sh" + echo "" + exit 1 + fi + # /opt requires sudo to create and would leave a root-owned tree; default + # macOS installs to a user-owned location so the whole flow stays rootless. + if [[ "$DEFAULT_INSTALL_PATH" == "/opt/bambuddy" ]]; then + DEFAULT_INSTALL_PATH="$HOME/bambuddy" + fi + fi + # Check/install Python if ! detect_python; then log_info "Python 3.10+ not found, will install..."