mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-08-11 00:30:12 -04:00
556 lines
18 KiB
Bash
Executable file
556 lines
18 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# BamBuddy Docker Installation Script
|
|
# Supports: Linux (all distros), macOS
|
|
#
|
|
# Usage:
|
|
# Interactive: curl -fsSL https://raw.githubusercontent.com/maziggy/bambuddy/main/install/docker-install.sh -o docker-install.sh && chmod +x docker-install.sh && ./docker-install.sh
|
|
# Unattended: ./docker-install.sh --path /opt/bambuddy --port 8000 --yes
|
|
#
|
|
# Options:
|
|
# --path PATH Installation directory (default: /opt/bambuddy)
|
|
# --port PORT Port to expose (default: 8000)
|
|
# --tz TIMEZONE Timezone (default: system timezone or UTC)
|
|
# --build Build from source instead of using pre-built image
|
|
# --yes, -y Non-interactive mode, accept defaults
|
|
# --redirect-990 (Deprecated, no longer needed — FTP binds to port 990 directly)
|
|
# --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"
|
|
|
|
# Script variables
|
|
INSTALL_PATH=""
|
|
PORT=""
|
|
TIMEZONE=""
|
|
BUILD_FROM_SOURCE="false"
|
|
NON_INTERACTIVE="false"
|
|
OS_TYPE=""
|
|
DOCKER_CMD=""
|
|
REDIRECT_990="false"
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Helper Functions
|
|
# -----------------------------------------------------------------------------
|
|
|
|
print_banner() {
|
|
echo -e "${CYAN}"
|
|
echo "╔════════════════════════════════════════════════════════╗"
|
|
echo "║ ║"
|
|
echo "║ ____ _ _ _ ║"
|
|
echo "║ | __ ) __ _ _ __ ___ | |__ _ _ __| | __| |_ _ ║"
|
|
echo "║ | _ \\ / _\` | '_ \` _ \\| '_ \\| | | |/ _\` |/ _\` | | | | ║"
|
|
echo "║ | |_) | (_| | | | | | | |_) | |_| | (_| | (_| | |_| | ║"
|
|
echo "║ |____/ \\__,_|_| |_| |_|_.__/ \\__,_|\\__,_|\\__,_|\\__, | ║"
|
|
echo "║ |___/ ║"
|
|
echo "║ ║"
|
|
echo "║ Docker 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 Docker Installation Script"
|
|
echo ""
|
|
echo "Usage: $0 [OPTIONS]"
|
|
echo ""
|
|
echo "Options:"
|
|
echo " --path PATH Installation directory (default: /opt/bambuddy)"
|
|
echo " --port PORT Port to expose (default: 8000)"
|
|
echo " --tz TIMEZONE Timezone (default: system timezone or UTC)"
|
|
echo " --build Build from source instead of using pre-built image"
|
|
echo " --yes, -y Non-interactive mode, accept defaults"
|
|
echo " --redirect-990 (Deprecated, no longer needed)"
|
|
echo " --help, -h Show this help message"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " Interactive installation:"
|
|
echo " ./docker-install.sh"
|
|
echo ""
|
|
echo " Unattended installation with custom settings:"
|
|
echo " ./docker-install.sh --path /srv/bambuddy --port 3000 --tz America/New_York --yes"
|
|
echo ""
|
|
echo " Build from source:"
|
|
echo " ./docker-install.sh --build --yes"
|
|
exit 0
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# System Detection
|
|
# -----------------------------------------------------------------------------
|
|
|
|
detect_os() {
|
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
OS_TYPE="macos"
|
|
return
|
|
fi
|
|
|
|
if [[ -f /etc/os-release ]]; then
|
|
OS_TYPE="linux"
|
|
else
|
|
log_error "Cannot detect operating system"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
detect_docker() {
|
|
# Check for docker compose (v2) or docker-compose (v1)
|
|
if docker compose version &>/dev/null 2>&1; then
|
|
DOCKER_CMD="docker compose"
|
|
log_success "Found Docker Compose v2"
|
|
return 0
|
|
elif docker-compose --version &>/dev/null 2>&1; then
|
|
DOCKER_CMD="docker-compose"
|
|
log_success "Found Docker Compose v1"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Installation Functions
|
|
# -----------------------------------------------------------------------------
|
|
|
|
install_docker() {
|
|
log_info "Docker not found, installing..."
|
|
|
|
case "$OS_TYPE" in
|
|
linux)
|
|
# Use Docker's convenience script
|
|
curl -fsSL https://get.docker.com | sh
|
|
|
|
# Add current user to docker group
|
|
if [[ -n "$SUDO_USER" ]]; then
|
|
sudo usermod -aG docker "$SUDO_USER"
|
|
log_warn "Added $SUDO_USER to docker group. You may need to log out and back in."
|
|
else
|
|
sudo usermod -aG docker "$USER"
|
|
log_warn "Added $USER to docker group. You may need to log out and back in."
|
|
fi
|
|
|
|
# Start Docker service
|
|
sudo systemctl enable docker
|
|
sudo systemctl start docker
|
|
;;
|
|
macos)
|
|
log_error "Docker Desktop not found."
|
|
log_error "Please install Docker Desktop for Mac from: https://www.docker.com/products/docker-desktop"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
log_success "Docker installed"
|
|
}
|
|
|
|
create_install_dir() {
|
|
log_info "Creating installation directory..."
|
|
|
|
mkdir -p "$INSTALL_PATH"
|
|
cd "$INSTALL_PATH"
|
|
|
|
log_success "Directory created: $INSTALL_PATH"
|
|
}
|
|
|
|
download_compose_file() {
|
|
log_info "Downloading docker-compose.yml..."
|
|
|
|
if [[ "$BUILD_FROM_SOURCE" == "true" ]]; then
|
|
# Clone the full repo for building
|
|
if [[ -d ".git" ]]; then
|
|
log_info "Existing repository found, updating..."
|
|
git fetch origin
|
|
git reset --hard origin/main
|
|
else
|
|
git clone https://github.com/maziggy/bambuddy.git .
|
|
fi
|
|
else
|
|
# Just download the compose file
|
|
curl -fsSL -o docker-compose.yml \
|
|
https://raw.githubusercontent.com/maziggy/bambuddy/main/docker-compose.yml
|
|
fi
|
|
|
|
log_success "docker-compose.yml ready"
|
|
}
|
|
|
|
create_env_file() {
|
|
log_info "Creating environment configuration..."
|
|
|
|
cat > .env << EOF
|
|
# BamBuddy Docker Configuration
|
|
# Generated by docker-install.sh on $(date)
|
|
|
|
# Port BamBuddy runs on
|
|
PORT=$PORT
|
|
|
|
# Timezone
|
|
TZ=$TIMEZONE
|
|
EOF
|
|
|
|
log_success "Environment file created"
|
|
}
|
|
|
|
customize_compose() {
|
|
# Detect if we need to disable host networking (macOS/Windows in Docker Desktop)
|
|
if [[ "$OS_TYPE" == "macos" ]]; then
|
|
log_warn "Docker Desktop detected. Host networking is not supported."
|
|
log_info "Modifying docker-compose.yml for port mapping..."
|
|
|
|
# Create a modified compose file for macOS
|
|
if [[ -f docker-compose.yml ]]; then
|
|
# Comment out network_mode: host and uncomment ports section
|
|
sed -i.bak \
|
|
-e 's/^[[:space:]]*network_mode: host/# network_mode: host/' \
|
|
-e 's/^[[:space:]]*#ports:/ ports:/' \
|
|
-e 's/^[[:space:]]*#[[:space:]]*- "\${PORT:-8000}:8000"/ - "\${PORT:-8000}:8000"/' \
|
|
docker-compose.yml
|
|
|
|
log_warn "Printer discovery may not work. Add printers manually by IP address."
|
|
fi
|
|
fi
|
|
}
|
|
|
|
start_container() {
|
|
log_info "Starting BamBuddy..."
|
|
|
|
if [[ "$BUILD_FROM_SOURCE" == "true" ]]; then
|
|
$DOCKER_CMD up -d --build
|
|
else
|
|
$DOCKER_CMD up -d
|
|
fi
|
|
|
|
# Wait for container to start
|
|
log_info "Waiting for container to start..."
|
|
local max_attempts=15
|
|
local attempt=0
|
|
|
|
while [[ $attempt -lt $max_attempts ]]; do
|
|
# Check if container is running (Up)
|
|
if $DOCKER_CMD ps | grep -q "Up"; then
|
|
log_success "BamBuddy container is running"
|
|
return 0
|
|
fi
|
|
|
|
# Check if container failed
|
|
if $DOCKER_CMD ps -a | grep -q "Exited"; then
|
|
log_error "Container failed to start"
|
|
log_info "Check logs with: $DOCKER_CMD logs bambuddy"
|
|
return 1
|
|
fi
|
|
|
|
sleep 2
|
|
((attempt++))
|
|
done
|
|
|
|
log_warn "Container may still be starting. Check with: $DOCKER_CMD ps"
|
|
}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Main Installation Flow
|
|
# -----------------------------------------------------------------------------
|
|
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--path)
|
|
INSTALL_PATH="$2"
|
|
shift 2
|
|
;;
|
|
--port)
|
|
PORT="$2"
|
|
shift 2
|
|
;;
|
|
--tz)
|
|
TIMEZONE="$2"
|
|
shift 2
|
|
;;
|
|
--build)
|
|
BUILD_FROM_SOURCE="true"
|
|
shift
|
|
;;
|
|
--yes|-y)
|
|
NON_INTERACTIVE="true"
|
|
shift
|
|
;;
|
|
--redirect-990)
|
|
REDIRECT_990="true"
|
|
shift
|
|
;;
|
|
--help|-h)
|
|
show_help
|
|
;;
|
|
*)
|
|
log_error "Unknown option: $1"
|
|
show_help
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
check_sudo() {
|
|
if ! command -v sudo &>/dev/null; then
|
|
log_error "sudo is required for iptables redirect but is not installed. Skipping iptables redirect."
|
|
return 1
|
|
fi
|
|
if ! command -v iptables &>/dev/null; then
|
|
log_error "iptables is required for iptables redirect but is not installed. Skipping iptables redirect."
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
configure_iptables_redirect() {
|
|
# Deprecated: FTP now binds directly to port 990 (requires CAP_NET_BIND_SERVICE).
|
|
# The iptables 990→9990 redirect is no longer needed and caused issues with
|
|
# multi-VP setups (REDIRECT rewrites dest IP to the interface's primary address).
|
|
if [[ "$REDIRECT_990" == "true" ]]; then
|
|
log_warn "The --redirect-990 flag is deprecated. FTP now binds directly to port 990."
|
|
log_warn "No iptables redirect is needed. Skipping."
|
|
fi
|
|
}
|
|
|
|
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
|
|
|
|
# Port
|
|
[[ -z "$PORT" ]] && prompt "Port to expose" "$DEFAULT_PORT" PORT
|
|
|
|
# Timezone
|
|
detect_timezone
|
|
prompt "Timezone" "$TIMEZONE" TIMEZONE
|
|
|
|
# Build from source?
|
|
if [[ "$BUILD_FROM_SOURCE" != "true" ]] && [[ "$NON_INTERACTIVE" != "true" ]]; then
|
|
if prompt_yes_no "Build from source? (No = use pre-built image)" "n"; then
|
|
BUILD_FROM_SOURCE="true"
|
|
fi
|
|
fi
|
|
|
|
# Confirm
|
|
echo ""
|
|
echo -e "${BOLD}Installation Summary${NC}"
|
|
echo -e "${CYAN}─────────────────────────────────────────${NC}"
|
|
echo -e " Install path: ${GREEN}$INSTALL_PATH${NC}"
|
|
echo -e " Port: ${GREEN}$PORT${NC}"
|
|
echo -e " Timezone: ${GREEN}$TIMEZONE${NC}"
|
|
echo -e " Build source: ${GREEN}$BUILD_FROM_SOURCE${NC}"
|
|
echo -e " Redirect 990: ${GREEN}$REDIRECT_990${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 docker-install.sh && chmod +x docker-install.sh && ./docker-install.sh"
|
|
echo ""
|
|
exit 1
|
|
fi
|
|
|
|
# Detect system
|
|
log_info "Detecting system..."
|
|
detect_os
|
|
log_success "Detected: $OS_TYPE"
|
|
|
|
# Check for Docker
|
|
if ! command -v docker &>/dev/null; then
|
|
install_docker
|
|
fi
|
|
|
|
if ! detect_docker; then
|
|
log_error "Docker Compose not found. Please install Docker Compose."
|
|
exit 1
|
|
fi
|
|
|
|
# Check if Docker daemon is running
|
|
if ! docker info &>/dev/null; then
|
|
log_error "Docker daemon is not running. Please start Docker and try again."
|
|
exit 1
|
|
fi
|
|
|
|
# Gather configuration
|
|
gather_config
|
|
|
|
# Install steps
|
|
echo ""
|
|
echo -e "${BOLD}Starting Installation${NC}"
|
|
echo -e "${CYAN}─────────────────────────────────────────${NC}"
|
|
echo ""
|
|
|
|
create_install_dir
|
|
download_compose_file
|
|
create_env_file
|
|
customize_compose
|
|
start_container
|
|
configure_iptables_redirect
|
|
|
|
# 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 ""
|
|
local ip_addr
|
|
ip_addr=$(hostname -I 2>/dev/null | awk '{print $1}') || ip_addr="<your-ip>"
|
|
echo -e " ${BOLD}Access BamBuddy:${NC} ${CYAN}http://localhost:$PORT${NC}"
|
|
echo -e " ${CYAN}http://$ip_addr:$PORT${NC} (from other devices)"
|
|
echo ""
|
|
echo -e " ${BOLD}Manage container:${NC}"
|
|
echo -e " Status: cd $INSTALL_PATH && $DOCKER_CMD ps"
|
|
echo -e " Logs: cd $INSTALL_PATH && $DOCKER_CMD logs -f bambuddy"
|
|
echo -e " Stop: cd $INSTALL_PATH && $DOCKER_CMD down"
|
|
echo -e " Start: cd $INSTALL_PATH && $DOCKER_CMD up -d"
|
|
echo -e " Restart: cd $INSTALL_PATH && $DOCKER_CMD restart"
|
|
echo ""
|
|
echo -e " ${BOLD}Update BamBuddy:${NC}"
|
|
if [[ "$BUILD_FROM_SOURCE" == "true" ]]; then
|
|
echo -e " cd $INSTALL_PATH && git pull && $DOCKER_CMD up -d --build"
|
|
else
|
|
echo -e " cd $INSTALL_PATH && $DOCKER_CMD pull && $DOCKER_CMD up -d"
|
|
fi
|
|
echo ""
|
|
echo -e " ${BOLD}Data location:${NC} Docker volumes (bambuddy_data, bambuddy_logs)"
|
|
echo ""
|
|
echo -e " ${BOLD}Documentation:${NC} ${CYAN}https://wiki.bambuddy.cool${NC}"
|
|
echo ""
|
|
|
|
# Warn about iptables persistence
|
|
if [[ "$REDIRECT_990" == "true" ]] && [[ "$OS_TYPE" == "linux" ]]; then
|
|
echo -e " ${YELLOW}Note:${NC} iptables redirect rules do NOT survive reboot."
|
|
echo -e " Install 'iptables-persistent' if persistence is required."
|
|
echo ""
|
|
fi
|
|
|
|
if [[ "$OS_TYPE" == "macos" ]]; then
|
|
echo -e " ${YELLOW}Note:${NC} Printer discovery may not work with Docker Desktop."
|
|
echo -e " Add printers manually using their IP address."
|
|
echo ""
|
|
fi
|
|
}
|
|
|
|
main "$@"
|