#!/usr/bin/env bash # # BamBuddy Native Installation Script # Supports: Debian/Ubuntu, RHEL/Fedora/CentOS, Arch Linux, macOS # # Usage: # Interactive: curl -fsSL https://raw.githubusercontent.com/maziggy/bambuddy/main/install/install.sh -o install.sh && chmod +x install.sh && ./install.sh # Unattended: ./install.sh --path /opt/bambuddy --port 8000 --yes # # Options: # --path PATH Installation directory (default: /opt/bambuddy) # --port PORT Port to listen on (default: 8000) # --bind ADDRESS Bind address: 0.0.0.0 (network) or 127.0.0.1 (local only) # --tz TIMEZONE Timezone (default: system timezone or UTC) # --data-dir PATH Data directory (default: INSTALL_PATH/data) # --log-dir PATH Log directory (default: INSTALL_PATH/logs) # --debug Enable debug mode # --log-level LEVEL Log level: DEBUG, INFO, WARNING, ERROR (default: INFO) # --branch BRANCH Git branch to install (default: main) # --no-service Skip systemd service setup (Linux only) # --set-system-tz Set system timezone to match (for unattended installs) # --yes, -y Non-interactive mode, accept defaults # --help, -h Show this help message # set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' # No Color BOLD='\033[1m' # Default values DEFAULT_INSTALL_PATH="/opt/bambuddy" DEFAULT_PORT="8000" DEFAULT_BIND_ADDRESS="0.0.0.0" DEFAULT_LOG_LEVEL="INFO" DEFAULT_DEBUG="false" # Script variables INSTALL_PATH="" PORT="" BIND_ADDRESS="" TIMEZONE="" DATA_DIR="" LOG_DIR="" DEBUG_MODE="" LOG_LEVEL="" SKIP_SERVICE="false" SET_SYSTEM_TZ="" NON_INTERACTIVE="false" OS_TYPE="" PKG_MANAGER="" PYTHON_CMD="" BRANCH="" SERVICE_USER="bambuddy" # ----------------------------------------------------------------------------- # Helper Functions # ----------------------------------------------------------------------------- print_banner() { echo -e "${CYAN}" echo "╔════════════════════════════════════════════════════════╗" echo "║ ║" echo "║ ____ _ _ _ ║" echo "║ | __ ) __ _ _ __ ___ | |__ _ _ __| | __| |_ _ ║" echo "║ | _ \\ / _\` | '_ \` _ \\| '_ \\| | | |/ _\` |/ _\` | | | | ║" echo "║ | |_) | (_| | | | | | | |_) | |_| | (_| | (_| | |_| | ║" echo "║ |____/ \\__,_|_| |_| |_|_.__/ \\__,_|\\__,_|\\__,_|\\__, | ║" echo "║ |___/ ║" echo "║ ║" echo "║ Native Installation Script ║" echo "║ ║" echo "╚════════════════════════════════════════════════════════╝" echo -e "${NC}" } log_info() { echo -e "${BLUE}[INFO]${NC} $1" } log_success() { echo -e "${GREEN}[OK]${NC} $1" } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1" } log_error() { echo -e "${RED}[ERROR]${NC} $1" } prompt() { local prompt_text="$1" local default_value="$2" local var_name="$3" if [[ "$NON_INTERACTIVE" == "true" ]]; then eval "$var_name=\"$default_value\"" return fi if [[ -n "$default_value" ]]; then echo -en "${BOLD}$prompt_text${NC} [${CYAN}$default_value${NC}]: " else echo -en "${BOLD}$prompt_text${NC}: " fi read -r input if [[ -z "$input" ]]; then eval "$var_name=\"$default_value\"" else eval "$var_name=\"$input\"" fi } prompt_yes_no() { local prompt_text="$1" local default="$2" # y or n if [[ "$NON_INTERACTIVE" == "true" ]]; then [[ "$default" == "y" ]] && return 0 || return 1 fi local yn_hint="[y/n]" [[ "$default" == "y" ]] && yn_hint="[Y/n]" [[ "$default" == "n" ]] && yn_hint="[y/N]" while true; do echo -en "${BOLD}$prompt_text${NC} $yn_hint: " read -r yn [[ -z "$yn" ]] && yn="$default" case "$yn" in [Yy]* ) return 0;; [Nn]* ) return 1;; * ) echo "Please answer yes or no.";; esac done } show_help() { echo "BamBuddy Native Installation Script" echo "" echo "Usage: $0 [OPTIONS]" echo "" echo "Options:" echo " --path PATH Installation directory (default: /opt/bambuddy)" echo " --port PORT Port to listen on (default: 8000)" echo " --bind ADDRESS Bind address: 0.0.0.0 (network) or 127.0.0.1 (local only)" echo " --tz TIMEZONE Timezone (default: system timezone or UTC)" echo " --data-dir PATH Data directory (default: INSTALL_PATH/data)" echo " --log-dir PATH Log directory (default: INSTALL_PATH/logs)" echo " --debug Enable debug mode" echo " --log-level LEVEL Log level: DEBUG, INFO, WARNING, ERROR (default: INFO)" echo " --branch BRANCH Git branch to install (default: main)" echo " --no-service Skip systemd service setup (Linux only)" echo " --set-system-tz Set system timezone to match (for unattended installs)" echo " --yes, -y Non-interactive mode, accept defaults" echo " --help, -h Show this help message" echo "" echo "Examples:" echo " Interactive installation:" echo " ./install.sh" echo "" echo " Unattended installation with custom settings:" echo " ./install.sh --path /srv/bambuddy --port 3000 --tz America/New_York --yes" echo "" echo " Minimal unattended installation:" echo " ./install.sh -y" exit 0 } # ----------------------------------------------------------------------------- # System Detection # ----------------------------------------------------------------------------- detect_os() { if [[ "$OSTYPE" == "darwin"* ]]; then OS_TYPE="macos" PKG_MANAGER="brew" return fi if [[ -f /etc/os-release ]]; then . /etc/os-release case "$ID" in ubuntu|debian|raspbian|linuxmint|pop) OS_TYPE="debian" PKG_MANAGER="apt" ;; fedora|rhel|centos|rocky|almalinux|ol) OS_TYPE="rhel" if command -v dnf &>/dev/null; then PKG_MANAGER="dnf" else PKG_MANAGER="yum" fi ;; arch|manjaro|endeavouros) OS_TYPE="arch" PKG_MANAGER="pacman" ;; opensuse*|sles) OS_TYPE="suse" PKG_MANAGER="zypper" ;; *) log_error "Unsupported Linux distribution: $ID" exit 1 ;; esac else log_error "Cannot detect operating system" exit 1 fi } detect_python() { # Try python3 first, then python if command -v python3 &>/dev/null; then PYTHON_CMD="python3" elif command -v python &>/dev/null; then local version version=$(python --version 2>&1 | cut -d' ' -f2 | cut -d'.' -f1) if [[ "$version" -ge 3 ]]; then PYTHON_CMD="python" fi fi if [[ -z "$PYTHON_CMD" ]]; then return 1 fi # Check version >= 3.10 local version version=$($PYTHON_CMD -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') local major minor major=$(echo "$version" | cut -d'.' -f1) minor=$(echo "$version" | cut -d'.' -f2) if [[ "$major" -lt 3 ]] || { [[ "$major" -eq 3 ]] && [[ "$minor" -lt 10 ]]; }; then log_warn "Python $version found, but 3.10 or newer is required" return 1 fi log_success "Found Python $version" return 0 } detect_timezone() { if [[ -n "$TIMEZONE" ]]; then return 0 fi # Try to get system timezone (with error handling for set -e) TIMEZONE="" if [[ -f /etc/timezone ]]; then TIMEZONE=$(cat /etc/timezone 2>/dev/null) || true fi if [[ -z "$TIMEZONE" ]] && [[ -L /etc/localtime ]]; then TIMEZONE=$(readlink /etc/localtime 2>/dev/null | sed 's|.*/zoneinfo/||') || true fi if [[ -z "$TIMEZONE" ]] && command -v timedatectl &>/dev/null; then TIMEZONE=$(timedatectl show --property=Timezone --value 2>/dev/null) || true fi # Default to UTC if not found (use if/then to avoid set -e issue with &&) if [[ -z "$TIMEZONE" ]]; then TIMEZONE="UTC" fi return 0 } # ----------------------------------------------------------------------------- # Package Installation # ----------------------------------------------------------------------------- install_dependencies() { log_info "Installing system dependencies..." case "$PKG_MANAGER" in apt) sudo apt-get update sudo apt-get install -y python3 python3-pip python3-venv git curl ffmpeg ;; dnf|yum) sudo $PKG_MANAGER install -y python3 python3-pip git curl ffmpeg ;; pacman) sudo pacman -Sy --noconfirm python python-pip git curl ffmpeg ;; zypper) sudo zypper install -y python3 python3-pip git curl ffmpeg ;; brew) # Check if Homebrew is installed if ! command -v brew &>/dev/null; then log_error "Homebrew not found. Please install it first: https://brew.sh" exit 1 fi brew install python git curl ffmpeg ;; esac log_success "System dependencies installed" } # ----------------------------------------------------------------------------- # Installation Steps # ----------------------------------------------------------------------------- create_user() { if [[ "$OS_TYPE" == "macos" ]]; then return # Skip user creation on macOS fi if id "$SERVICE_USER" &>/dev/null; then log_info "User '$SERVICE_USER' already exists" return fi log_info "Creating service user '$SERVICE_USER'..." sudo useradd --system --shell /usr/sbin/nologin --home-dir "$INSTALL_PATH" "$SERVICE_USER" log_success "Service user created" } download_bambuddy() { log_info "Downloading BamBuddy..." # Validate branch exists on remote before proceeding if ! git ls-remote --exit-code --heads https://github.com/maziggy/bambuddy.git "$BRANCH" &>/dev/null; then log_error "Branch '$BRANCH' not found in the BamBuddy repository." log_info "Available branches:" git ls-remote --heads https://github.com/maziggy/bambuddy.git | sed 's|.*refs/heads/| - |' exit 1 fi if [[ -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 cd "$INSTALL_PATH" git fetch origin git checkout "$BRANCH" 2>/dev/null || git checkout -b "$BRANCH" "origin/$BRANCH" git reset --hard "origin/$BRANCH" # Ensure correct ownership after update sudo chown -R "$SERVICE_USER:$SERVICE_USER" "$INSTALL_PATH" 2>/dev/null || true else # Clone as root so we have write access regardless of the installing user, # then hand ownership to the service user. Previously we chown'd the empty # dir to the service user before the clone, which left the install-running # user (not root, not bambuddy) unable to write .git into it. sudo mkdir -p "$INSTALL_PATH" sudo git clone --branch "$BRANCH" https://github.com/maziggy/bambuddy.git "$INSTALL_PATH" sudo chown -R "$SERVICE_USER:$SERVICE_USER" "$INSTALL_PATH" 2>/dev/null || true fi log_success "BamBuddy downloaded to $INSTALL_PATH (branch: $BRANCH)" } setup_virtualenv() { log_info "Setting up Python virtual environment..." cd "$INSTALL_PATH" if [[ "$OS_TYPE" == "macos" ]]; then $PYTHON_CMD -m venv venv "$INSTALL_PATH/venv/bin/pip" install --upgrade pip "$INSTALL_PATH/venv/bin/pip" install -r requirements.txt else # Venv is owned by the service user, so pip must also run as that user — # otherwise `pip install --upgrade pip` fails trying to rewrite its own # binary inside the venv it doesn't own. sudo -u "$SERVICE_USER" $PYTHON_CMD -m venv venv 2>/dev/null || $PYTHON_CMD -m venv venv sudo -u "$SERVICE_USER" "$INSTALL_PATH/venv/bin/pip" install --upgrade pip sudo -u "$SERVICE_USER" "$INSTALL_PATH/venv/bin/pip" install -r requirements.txt fi log_success "Virtual environment configured" } check_node_version() { # Returns 0 if Node.js 20+ is available, 1 otherwise if ! command -v node &>/dev/null; then return 1 fi local version version=$(node --version 2>/dev/null | sed 's/^v//') local major major=$(echo "$version" | cut -d'.' -f1) if [[ "$major" -ge 20 ]]; then log_success "Found Node.js v$version" return 0 else log_warn "Found Node.js v$version (need 20+)" return 1 fi } install_nodejs() { log_info "Installing Node.js 22..." case "$PKG_MANAGER" in apt) # Remove old nodejs if present sudo apt-get remove -y nodejs npm 2>/dev/null || true curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - sudo apt-get install -y nodejs ;; dnf|yum) sudo $PKG_MANAGER remove -y nodejs npm 2>/dev/null || true curl -fsSL https://rpm.nodesource.com/setup_22.x | sudo bash - sudo $PKG_MANAGER install -y nodejs ;; pacman) sudo pacman -S --noconfirm nodejs npm ;; zypper) sudo zypper install -y nodejs22 ;; brew) brew install node@22 brew link --overwrite node@22 ;; *) log_error "Please install Node.js 20+ manually: https://nodejs.org/" exit 1 ;; esac # Refresh PATH hash -r 2>/dev/null || true } build_frontend() { log_info "Building frontend..." cd "$INSTALL_PATH/frontend" # Check for Node.js 20+ if ! check_node_version; then install_nodejs # Verify installation if ! check_node_version; then log_error "Failed to install Node.js 20+. Please install manually." exit 1 fi fi # Frontend tree is owned by the service user, so npm must run as that user — # otherwise creating node_modules/ and writing build output fails. macOS # keeps the current-user flow since it has no service user. if [[ "$OS_TYPE" == "macos" ]]; then npm ci npm run build else sudo -H -u "$SERVICE_USER" npm ci sudo -H -u "$SERVICE_USER" npm run build fi log_success "Frontend built" } create_directories() { log_info "Creating data directories..." sudo mkdir -p "$DATA_DIR" "$LOG_DIR" if [[ "$OS_TYPE" != "macos" ]]; then sudo chown -R "$SERVICE_USER:$SERVICE_USER" "$DATA_DIR" "$LOG_DIR" fi log_success "Directories created" } create_env_file() { log_info "Creating environment configuration..." local env_file="$INSTALL_PATH/.env" # Note: Only include settings recognized by the app's pydantic Settings class # Other settings (PORT, BIND_ADDRESS, DATA_DIR, LOG_DIR, TZ) are set in systemd service cat > /tmp/bambuddy.env << EOF # BamBuddy Configuration # Generated by install.sh on $(date) # Debug mode (true = verbose logging) DEBUG=$DEBUG_MODE # Log level (only used when DEBUG=false) # Options: DEBUG, INFO, WARNING, ERROR LOG_LEVEL=$LOG_LEVEL # Enable file logging LOG_TO_FILE=true EOF sudo mv /tmp/bambuddy.env "$env_file" if [[ "$OS_TYPE" != "macos" ]]; then sudo chown "$SERVICE_USER:$SERVICE_USER" "$env_file" fi sudo chmod 600 "$env_file" log_success "Environment file created at $env_file" } create_systemd_service() { if [[ "$OS_TYPE" == "macos" ]] || [[ "$SKIP_SERVICE" == "true" ]]; then return fi log_info "Creating systemd service..." cat > /tmp/bambuddy.service << EOF [Unit] Description=BamBuddy - Bambu Lab Print Management Documentation=https://github.com/maziggy/bambuddy After=network.target [Service] Type=simple User=$SERVICE_USER Group=$SERVICE_USER WorkingDirectory=$INSTALL_PATH # App settings from .env file EnvironmentFile=$INSTALL_PATH/.env # Service settings (not in .env to avoid pydantic validation errors) Environment="DATA_DIR=$DATA_DIR" Environment="LOG_DIR=$LOG_DIR" Environment="TZ=$TIMEZONE" ExecStart=$INSTALL_PATH/venv/bin/uvicorn backend.app.main:app --host $BIND_ADDRESS --port $PORT Restart=on-failure RestartSec=5 StandardOutput=journal StandardError=journal # Allow binding to privileged ports (322, 990, 2024-2026) for Virtual Printer proxy mode AmbientCapabilities=CAP_NET_BIND_SERVICE # Security hardening NoNewPrivileges=true PrivateTmp=true ProtectSystem=strict ProtectHome=true ReadWritePaths=$DATA_DIR $LOG_DIR $INSTALL_PATH [Install] WantedBy=multi-user.target EOF sudo mv /tmp/bambuddy.service /etc/systemd/system/bambuddy.service sudo systemctl daemon-reload log_success "Systemd service created" if prompt_yes_no "Enable BamBuddy to start on boot?" "y"; then sudo systemctl enable bambuddy log_success "Service enabled" fi if prompt_yes_no "Start BamBuddy now?" "y"; then sudo systemctl start bambuddy sleep 2 if sudo systemctl is-active --quiet bambuddy; then log_success "BamBuddy is running" else log_warn "Service may have failed to start. Check: sudo journalctl -u bambuddy -f" fi fi } create_launchd_service() { if [[ "$OS_TYPE" != "macos" ]] || [[ "$SKIP_SERVICE" == "true" ]]; then return fi log_info "Creating launchd service..." local plist_path="$HOME/Library/LaunchAgents/com.bambuddy.app.plist" cat > "$plist_path" << EOF Label com.bambuddy.app ProgramArguments $INSTALL_PATH/venv/bin/uvicorn backend.app.main:app --host $BIND_ADDRESS --port $PORT WorkingDirectory $INSTALL_PATH EnvironmentVariables DEBUG $DEBUG_MODE LOG_LEVEL $LOG_LEVEL DATA_DIR $DATA_DIR LOG_DIR $LOG_DIR TZ $TIMEZONE RunAtLoad KeepAlive StandardOutPath $LOG_DIR/bambuddy.log StandardErrorPath $LOG_DIR/bambuddy.error.log EOF log_success "Launchd plist created at $plist_path" if prompt_yes_no "Load BamBuddy service now?" "y"; then launchctl load "$plist_path" sleep 2 if launchctl list | grep -q "com.bambuddy.app"; then log_success "BamBuddy is running" else log_warn "Service may have failed to start. Check: cat $LOG_DIR/bambuddy.error.log" fi fi } # ----------------------------------------------------------------------------- # Main Installation Flow # ----------------------------------------------------------------------------- parse_args() { while [[ $# -gt 0 ]]; do case "$1" in --path) INSTALL_PATH="$2" shift 2 ;; --port) PORT="$2" shift 2 ;; --bind) BIND_ADDRESS="$2" shift 2 ;; --tz) TIMEZONE="$2" shift 2 ;; --data-dir) DATA_DIR="$2" shift 2 ;; --log-dir) LOG_DIR="$2" shift 2 ;; --debug) DEBUG_MODE="true" shift ;; --log-level) LOG_LEVEL="$2" shift 2 ;; --branch) BRANCH="$2" shift 2 ;; --no-service) SKIP_SERVICE="true" shift ;; --set-system-tz) SET_SYSTEM_TZ="true" shift ;; --yes|-y) NON_INTERACTIVE="true" shift ;; --help|-h) show_help ;; *) log_error "Unknown option: $1" show_help ;; esac done } gather_config() { echo "" echo -e "${BOLD}Installation Configuration${NC}" echo -e "${CYAN}─────────────────────────────────────────${NC}" echo "" # Installation path [[ -z "$INSTALL_PATH" ]] && prompt "Installation directory" "$DEFAULT_INSTALL_PATH" INSTALL_PATH # Branch [[ -z "$BRANCH" ]] && prompt "Git branch" "main" BRANCH # Port [[ -z "$PORT" ]] && prompt "Port to listen on" "$DEFAULT_PORT" PORT # Bind address if [[ -z "$BIND_ADDRESS" ]]; then echo "" echo "Network access:" echo " 0.0.0.0 - Accessible from other devices on your network (recommended)" echo " 127.0.0.1 - Only accessible from this machine" prompt "Bind address" "$DEFAULT_BIND_ADDRESS" BIND_ADDRESS fi # Timezone detect_timezone prompt "Timezone" "$TIMEZONE" TIMEZONE # Offer to set system timezone if different from current (skip if already set via --set-system-tz) if [[ -z "$SET_SYSTEM_TZ" ]]; then local current_tz current_tz=$(timedatectl show --property=Timezone --value 2>/dev/null) || true if [[ -n "$TIMEZONE" ]] && [[ "$TIMEZONE" != "$current_tz" ]]; then # Default to "n" so unattended installs don't change system TZ unless --set-system-tz is used if prompt_yes_no "Set system timezone to $TIMEZONE?" "n"; then SET_SYSTEM_TZ="true" else SET_SYSTEM_TZ="false" fi else SET_SYSTEM_TZ="false" fi fi # Data directory [[ -z "$DATA_DIR" ]] && DATA_DIR="$INSTALL_PATH/data" prompt "Data directory" "$DATA_DIR" DATA_DIR # Log directory [[ -z "$LOG_DIR" ]] && LOG_DIR="$INSTALL_PATH/logs" prompt "Log directory" "$LOG_DIR" LOG_DIR # Debug mode if [[ -z "$DEBUG_MODE" ]]; then if prompt_yes_no "Enable debug mode?" "n"; then DEBUG_MODE="true" else DEBUG_MODE="false" fi fi # Log level if [[ -z "$LOG_LEVEL" ]]; then echo "" echo "Log levels: DEBUG, INFO, WARNING, ERROR" prompt "Log level" "$DEFAULT_LOG_LEVEL" LOG_LEVEL fi # Confirm echo "" echo -e "${BOLD}Installation Summary${NC}" echo -e "${CYAN}─────────────────────────────────────────${NC}" echo -e " Install path: ${GREEN}$INSTALL_PATH${NC}" if [[ "$BRANCH" != "main" ]]; then echo -e " Branch: ${YELLOW}$BRANCH${NC} (beta)" else echo -e " Branch: ${GREEN}$BRANCH${NC}" fi echo -e " Port: ${GREEN}$PORT${NC}" echo -e " Bind address: ${GREEN}$BIND_ADDRESS${NC}" echo -e " Timezone: ${GREEN}$TIMEZONE${NC}" echo -e " Data dir: ${GREEN}$DATA_DIR${NC}" echo -e " Log dir: ${GREEN}$LOG_DIR${NC}" echo -e " Debug mode: ${GREEN}$DEBUG_MODE${NC}" echo -e " Log level: ${GREEN}$LOG_LEVEL${NC}" echo "" if ! prompt_yes_no "Proceed with installation?" "y"; then echo "Installation cancelled." exit 0 fi } main() { parse_args "$@" print_banner # Check if running via pipe (curl | bash) - interactive mode won't work if [[ ! -t 0 ]] && [[ "$NON_INTERACTIVE" != "true" ]]; then log_error "Interactive mode requires a terminal." log_info "When using 'curl | bash', you must use non-interactive mode:" echo "" echo " curl -fsSL URL | bash -s -- --yes" echo "" log_info "Or download and run directly:" echo "" echo " curl -fsSL URL -o install.sh && chmod +x install.sh && ./install.sh" echo "" exit 1 fi # Check for root (we need sudo for some operations) if [[ "$EUID" -eq 0 ]] && [[ "$OS_TYPE" != "macos" ]]; then log_warn "Running as root. Consider using a regular user with sudo privileges." fi # Detect system log_info "Detecting system..." detect_os log_success "Detected: $OS_TYPE (package manager: $PKG_MANAGER)" # Check/install Python if ! detect_python; then log_info "Python 3.10+ not found, will install..." fi # Gather configuration gather_config # Install steps echo "" echo -e "${BOLD}Starting Installation${NC}" echo -e "${CYAN}─────────────────────────────────────────${NC}" echo "" install_dependencies detect_python || { log_error "Failed to install Python"; exit 1; } # Set system timezone if requested if [[ "$SET_SYSTEM_TZ" == "true" ]]; then log_info "Setting system timezone to $TIMEZONE..." if [[ "$OS_TYPE" == "macos" ]]; then sudo systemsetup -settimezone "$TIMEZONE" 2>/dev/null || true else sudo timedatectl set-timezone "$TIMEZONE" 2>/dev/null || true fi log_success "System timezone set to $TIMEZONE" fi if [[ "$OS_TYPE" != "macos" ]]; then create_user else SERVICE_USER="$USER" fi download_bambuddy setup_virtualenv build_frontend create_directories create_env_file if [[ "$OS_TYPE" == "macos" ]]; then create_launchd_service else create_systemd_service fi # Done! echo "" echo -e "${GREEN}╔══════════════════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}║ ║${NC}" echo -e "${GREEN}║ Installation Complete! ║${NC}" echo -e "${GREEN}║ ║${NC}" echo -e "${GREEN}╚══════════════════════════════════════════════════════════════╝${NC}" echo "" # Show appropriate URL based on bind address if [[ "$BIND_ADDRESS" == "0.0.0.0" ]]; then local ip_addr ip_addr=$(hostname -I 2>/dev/null | awk '{print $1}') || ip_addr="" echo -e " ${BOLD}Access BamBuddy:${NC} ${CYAN}http://localhost:$PORT${NC}" echo -e " ${CYAN}http://$ip_addr:$PORT${NC} (from other devices)" else echo -e " ${BOLD}Access BamBuddy:${NC} ${CYAN}http://localhost:$PORT${NC}" fi echo "" if [[ "$OS_TYPE" == "macos" ]]; then echo -e " ${BOLD}Manage service:${NC}" echo -e " Start: launchctl load ~/Library/LaunchAgents/com.bambuddy.app.plist" echo -e " Stop: launchctl unload ~/Library/LaunchAgents/com.bambuddy.app.plist" echo -e " Logs: tail -f $LOG_DIR/bambuddy.log" else echo -e " ${BOLD}Manage service:${NC}" echo -e " Status: sudo systemctl status bambuddy" echo -e " Start: sudo systemctl start bambuddy" echo -e " Stop: sudo systemctl stop bambuddy" echo -e " Logs: sudo journalctl -u bambuddy -f" fi echo "" echo -e " ${BOLD}Update BamBuddy:${NC}" echo -e " cd $INSTALL_PATH && git pull && source venv/bin/activate" echo -e " pip install -r requirements.txt && cd frontend && npm ci && npm run build" if [[ "$OS_TYPE" != "macos" ]]; then echo -e " sudo systemctl restart bambuddy" fi echo "" echo -e " ${BOLD}Documentation:${NC} ${CYAN}https://wiki.bambuddy.cool${NC}" echo "" } main "$@"