#!/bin/bash # # BotServer Embedded Deployment Script # For Orange Pi, Raspberry Pi, and other ARM/x86 SBCs # # Usage: ./deploy-embedded.sh [target-host] [options] # # Examples: # ./deploy-embedded.sh orangepi@192.168.1.100 # ./deploy-embedded.sh pi@raspberrypi.local --with-ui # ./deploy-embedded.sh --local --with-ui # set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" BOTUI_DIR="$(dirname "$PROJECT_DIR")/botui" # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # Default values TARGET_HOST="" WITH_UI=false WITH_LLAMA=false LOCAL_INSTALL=false ARCH="" SERVICE_NAME="botserver" LLAMA_MODEL="tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf" LLAMA_URL="https://huggingface.co/TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF/resolve/main" print_banner() { echo -e "${BLUE}" echo "╔════════════════════════════════════════════════════════════╗" echo "║ BotServer Embedded Deployment ║" echo "║ Orange Pi / Raspberry Pi / ARM SBCs ║" echo "╚════════════════════════════════════════════════════════════╝" echo -e "${NC}" } detect_arch() { local arch=$(uname -m) case $arch in aarch64|arm64) ARCH="aarch64-unknown-linux-gnu" echo -e "${GREEN}Detected: ARM64${NC}" ;; armv7l|armhf) ARCH="armv7-unknown-linux-gnueabihf" echo -e "${GREEN}Detected: ARMv7 (32-bit)${NC}" ;; x86_64) ARCH="x86_64-unknown-linux-gnu" echo -e "${GREEN}Detected: x86_64${NC}" ;; *) echo -e "${RED}Unknown architecture: $arch${NC}" exit 1 ;; esac } install_rust_target() { echo -e "${YELLOW}Installing Rust target: $ARCH${NC}" rustup target add $ARCH 2>/dev/null || true } install_cross_compiler() { echo -e "${YELLOW}Installing cross-compilation tools...${NC}" case $ARCH in aarch64-unknown-linux-gnu) if ! command -v aarch64-linux-gnu-gcc &> /dev/null; then echo "Installing aarch64 cross-compiler..." sudo apt-get update sudo apt-get install -y gcc-aarch64-linux-gnu fi export CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc ;; armv7-unknown-linux-gnueabihf) if ! command -v arm-linux-gnueabihf-gcc &> /dev/null; then echo "Installing armv7 cross-compiler..." sudo apt-get update sudo apt-get install -y gcc-arm-linux-gnueabihf fi export CC_armv7_unknown_linux_gnueabihf=arm-linux-gnueabihf-gcc export CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=arm-linux-gnueabihf-gcc ;; esac } build_botserver() { echo -e "${YELLOW}Building botserver for $ARCH...${NC}" cd "$PROJECT_DIR" # Build release cargo build --release --target $ARCH BINARY_PATH="$PROJECT_DIR/target/$ARCH/release/botserver" if [ -f "$BINARY_PATH" ]; then echo -e "${GREEN}Build successful: $BINARY_PATH${NC}" ls -lh "$BINARY_PATH" else echo -e "${RED}Build failed!${NC}" exit 1 fi } build_local() { echo -e "${YELLOW}Building botserver locally...${NC}" cd "$PROJECT_DIR" cargo build --release BINARY_PATH="$PROJECT_DIR/target/release/botserver" } create_systemd_service() { cat > /tmp/botserver.service << 'EOF' [Unit] Description=BotServer - General Bots Server After=network.target postgresql.service Wants=network-online.target [Service] Type=simple User=botserver Group=botserver WorkingDirectory=/opt/botserver ExecStart=/opt/botserver/botserver Restart=always RestartSec=5 Environment=RUST_LOG=info Environment=DATABASE_URL=postgres://localhost/botserver # Security NoNewPrivileges=true ProtectSystem=strict ProtectHome=true ReadWritePaths=/opt/botserver/data [Install] WantedBy=multi-user.target EOF } create_kiosk_service() { cat > /tmp/botui-kiosk.service << 'EOF' [Unit] Description=BotUI Kiosk Mode After=graphical.target botserver.service Wants=botserver.service [Service] Type=simple User=pi Environment=DISPLAY=:0 ExecStartPre=/bin/sleep 5 ExecStart=/usr/bin/chromium-browser --kiosk --noerrdialogs --disable-infobars --disable-session-crashed-bubble --app=http://localhost:8088/embedded/ Restart=always RestartSec=10 [Install] WantedBy=graphical.target EOF } deploy_remote() { local host=$1 echo -e "${YELLOW}Deploying to $host...${NC}" # Create remote directory ssh $host "sudo mkdir -p /opt/botserver/data && sudo chown -R \$(whoami):\$(whoami) /opt/botserver" # Copy binary echo "Copying botserver binary..." scp "$BINARY_PATH" "$host:/opt/botserver/botserver" ssh $host "chmod +x /opt/botserver/botserver" # Copy config if [ -f "$PROJECT_DIR/.env.example" ]; then scp "$PROJECT_DIR/.env.example" "$host:/opt/botserver/.env" fi # Copy systemd service create_systemd_service scp /tmp/botserver.service "$host:/tmp/" ssh $host "sudo mv /tmp/botserver.service /etc/systemd/system/" # Create user and setup ssh $host "sudo useradd -r -s /bin/false botserver 2>/dev/null || true" ssh $host "sudo chown -R botserver:botserver /opt/botserver" # Enable and start service ssh $host "sudo systemctl daemon-reload" ssh $host "sudo systemctl enable botserver" ssh $host "sudo systemctl start botserver" echo -e "${GREEN}BotServer deployed and running on $host${NC}" # Deploy UI if requested if [ "$WITH_UI" = true ]; then deploy_ui_remote $host fi } deploy_ui_remote() { local host=$1 echo -e "${YELLOW}Deploying embedded UI to $host...${NC}" # Copy embedded UI files if [ -d "$BOTUI_DIR/ui/embedded" ]; then ssh $host "mkdir -p /opt/botserver/ui/embedded" scp -r "$BOTUI_DIR/ui/embedded/"* "$host:/opt/botserver/ui/embedded/" fi # Setup kiosk mode create_kiosk_service scp /tmp/botui-kiosk.service "$host:/tmp/" ssh $host "sudo mv /tmp/botui-kiosk.service /etc/systemd/system/" ssh $host "sudo systemctl daemon-reload" ssh $host "sudo systemctl enable botui-kiosk" echo -e "${GREEN}Kiosk mode configured. Reboot to start.${NC}" } deploy_local() { echo -e "${YELLOW}Installing locally...${NC}" # Build build_local # Install sudo mkdir -p /opt/botserver/data sudo cp "$BINARY_PATH" /opt/botserver/ sudo chmod +x /opt/botserver/botserver if [ -f "$PROJECT_DIR/.env.example" ]; then sudo cp "$PROJECT_DIR/.env.example" /opt/botserver/.env fi # Create user sudo useradd -r -s /bin/false botserver 2>/dev/null || true sudo chown -R botserver:botserver /opt/botserver # Setup systemd create_systemd_service sudo mv /tmp/botserver.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable botserver sudo systemctl start botserver echo -e "${GREEN}BotServer installed and running locally${NC}" if [ "$WITH_UI" = true ]; then # Copy UI files sudo mkdir -p /opt/botserver/ui/embedded sudo cp -r "$BOTUI_DIR/ui/embedded/"* /opt/botserver/ui/embedded/ # Setup kiosk create_kiosk_service sudo mv /tmp/botui-kiosk.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable botui-kiosk echo -e "${GREEN}Kiosk mode configured. Reboot to start.${NC}" fi } install_llama_cpp() { local host=$1 local is_local=$2 echo -e "${YELLOW}Installing llama.cpp...${NC}" local commands=' # Install dependencies sudo apt-get update sudo apt-get install -y build-essential cmake git # Clone and build llama.cpp cd /opt if [ ! -d "llama.cpp" ]; then sudo git clone https://github.com/ggerganov/llama.cpp.git sudo chown -R $(whoami):$(whoami) llama.cpp fi cd llama.cpp # Build with optimizations for ARM mkdir -p build && cd build cmake .. -DLLAMA_NATIVE=ON -DCMAKE_BUILD_TYPE=Release make -j$(nproc) # Create models directory mkdir -p /opt/llama.cpp/models ' if [ "$is_local" = true ]; then eval "$commands" else ssh $host "$commands" fi } download_model() { local host=$1 local is_local=$2 echo -e "${YELLOW}Downloading model: $LLAMA_MODEL...${NC}" local commands=" cd /opt/llama.cpp/models if [ ! -f '$LLAMA_MODEL' ]; then wget -c '$LLAMA_URL/$LLAMA_MODEL' fi ls -lh /opt/llama.cpp/models/ " if [ "$is_local" = true ]; then eval "$commands" else ssh $host "$commands" fi } create_llama_service() { cat > /tmp/llama-server.service << 'EOF' [Unit] Description=llama.cpp Server - Local LLM Inference After=network.target [Service] Type=simple User=root WorkingDirectory=/opt/llama.cpp ExecStart=/opt/llama.cpp/build/bin/llama-server \ -m /opt/llama.cpp/models/tinyllama-1.1b-chat-v1.0.Q4_K_M.gguf \ --host 0.0.0.0 \ --port 8080 \ -c 2048 \ -ngl 0 \ --threads 4 Restart=always RestartSec=5 Environment=LLAMA_LOG_LEVEL=info [Install] WantedBy=multi-user.target EOF } setup_llama_service() { local host=$1 local is_local=$2 echo -e "${YELLOW}Setting up llama.cpp systemd service...${NC}" create_llama_service if [ "$is_local" = true ]; then sudo mv /tmp/llama-server.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable llama-server sudo systemctl start llama-server else scp /tmp/llama-server.service "$host:/tmp/" ssh $host "sudo mv /tmp/llama-server.service /etc/systemd/system/" ssh $host "sudo systemctl daemon-reload" ssh $host "sudo systemctl enable llama-server" ssh $host "sudo systemctl start llama-server" fi echo -e "${GREEN}llama.cpp server configured on port 8080${NC}" } deploy_llama() { local host=$1 local is_local=${2:-false} install_llama_cpp "$host" "$is_local" download_model "$host" "$is_local" setup_llama_service "$host" "$is_local" } show_help() { echo "Usage: $0 [target-host] [options]" echo "" echo "Options:" echo " --local Install on this machine" echo " --with-ui Also deploy embedded UI with kiosk mode" echo " --with-llama Install llama.cpp for local LLM inference" echo " --model NAME Specify GGUF model (default: TinyLlama 1.1B Q4)" echo " --arch ARCH Force target architecture" echo " -h, --help Show this help" echo "" echo "Examples:" echo " $0 orangepi@192.168.1.100" echo " $0 pi@raspberrypi.local --with-ui" echo " $0 --local --with-ui" echo "" echo "Supported boards:" echo " - Raspberry Pi (Zero, 3, 4, 5)" echo " - Orange Pi (Zero, One, PC, etc)" echo " - Banana Pi" echo " - Rock Pi" echo " - ODROID" echo " - Any ARM64/ARMv7/x86_64 Linux SBC" } # Parse arguments while [[ $# -gt 0 ]]; do case $1 in --local) LOCAL_INSTALL=true shift ;; --with-ui) WITH_UI=true shift ;; --with-llama) WITH_LLAMA=true shift ;; --model) LLAMA_MODEL="$2" shift 2 ;; --arch) ARCH="$2" shift 2 ;; -h|--help) show_help exit 0 ;; *) if [ -z "$TARGET_HOST" ]; then TARGET_HOST="$1" fi shift ;; esac done # Main print_banner if [ "$LOCAL_INSTALL" = true ]; then detect_arch deploy_local if [ "$WITH_LLAMA" = true ]; then deploy_llama "" true fi elif [ -n "$TARGET_HOST" ]; then # Get remote arch echo "Detecting remote architecture..." REMOTE_ARCH=$(ssh $TARGET_HOST "uname -m") case $REMOTE_ARCH in aarch64|arm64) ARCH="aarch64-unknown-linux-gnu" ;; armv7l|armhf) ARCH="armv7-unknown-linux-gnueabihf" ;; x86_64) ARCH="x86_64-unknown-linux-gnu" ;; esac echo -e "${GREEN}Remote arch: $ARCH${NC}" install_rust_target install_cross_compiler build_botserver deploy_remote $TARGET_HOST if [ "$WITH_LLAMA" = true ]; then deploy_llama $TARGET_HOST false fi else show_help exit 1 fi echo "" echo -e "${GREEN}Deployment complete!${NC}" echo "" echo "Check status:" echo " ssh $TARGET_HOST 'sudo systemctl status botserver'" echo "" echo "View logs:" echo " ssh $TARGET_HOST 'sudo journalctl -u botserver -f'" echo "" if [ "$WITH_UI" = true ]; then echo "Access UI at: http://$TARGET_HOST:8088/embedded/" fi if [ "$WITH_LLAMA" = true ]; then echo "" echo "llama.cpp server running at: http://$TARGET_HOST:8080" echo "Test: curl http://$TARGET_HOST:8080/v1/models" fi