#!/bin/bash # # LetsBe Deployment Script # Deploys the LetsBe setup to a remote server via SSH. # # Usage: # ./start.sh --host 192.168.1.100 --password "mypassword" --action setup # ./start.sh --host 192.168.1.100 --key ./id_ed25519 --action setup --tools "all" # ./start.sh --host 192.168.1.100 --key ./id_ed25519 --action connect # ./start.sh --config config.json # # Arguments: # --host Server IP address (required) # --port SSH port (default: 22, after setup: 22022) # --password SSH password for root (initial setup) # --key Path to SSH private key (for stefan user) # --action "setup" or "connect" (required) # --tools Tools to deploy (passed to setup.sh) # --skip-ssl Skip SSL setup (passed to setup.sh) # --config Path to JSON config file # --json Inline JSON configuration # # JSON Config Format: # { # "host": "192.168.1.100", # "port": 22, # "password": "...", # "key": "./id_ed25519", # "action": "setup", # "tools": "all", # "skip_ssl": false, # "customer": "acme", # "domain": "acme.com", # "company_name": "Acme Corp" # } # set -euo pipefail # ============================================================================= # CONFIGURATION DEFAULTS # ============================================================================= SERVER_IP="" SERVER_PORT="22" SERVER_PASSWORD="" SSH_KEY="" ACTION="" TOOLS="" SKIP_SSL="" ROOT_SSL="" CONFIG_JSON="" # env_setup.sh parameters CUSTOMER="" DOMAIN="" COMPANY_NAME="" # ============================================================================= # HELPER FUNCTIONS # ============================================================================= usage() { echo "Usage: $0 --host IP --action [setup|connect] [options]" echo "" echo "Required:" echo " --host Server IP address" echo " --action 'setup' or 'connect'" echo "" echo "Authentication (one required for setup):" echo " --password SSH password for root (initial setup)" echo " --key Path to SSH private key" echo "" echo "Optional:" echo " --port SSH port (default: 22)" echo " --tools Tools to deploy (comma-separated or 'all')" echo " --skip-ssl Skip SSL certificate setup" echo " --root-ssl Include root domain in SSL certificate" echo " --customer Customer name for env_setup.sh" echo " --domain Domain for env_setup.sh" echo " --company Company name for env_setup.sh" echo "" echo "JSON Input:" echo " --config Path to JSON config file" echo " --json Inline JSON configuration" echo "" echo "Examples:" echo " $0 --host 1.2.3.4 --password 'pass' --action setup --tools all" echo " $0 --host 1.2.3.4 --key ./id_ed25519 --port 22022 --action connect" echo " $0 --config config.json" exit 1 } check_and_replace_ssh_key() { local server_ip=$1 local server_port=$2 # Ensure ~/.ssh directory exists mkdir -p ~/.ssh 2>/dev/null || true chmod 700 ~/.ssh 2>/dev/null || true # Remove old host key if it exists ssh-keygen -R "[$server_ip]:$server_port" >/dev/null 2>&1 || true ssh-keygen -R "$server_ip" >/dev/null 2>&1 || true # Add current host key ssh-keyscan -p "$server_port" "$server_ip" >> ~/.ssh/known_hosts 2>/dev/null || true } parse_json() { local json="$1" # Check if jq is available if ! command -v jq &> /dev/null; then echo "ERROR: jq is required for JSON parsing. Install with: apt install jq" exit 1 fi SERVER_IP=$(echo "$json" | jq -r '.host // empty') SERVER_PORT=$(echo "$json" | jq -r '.port // "22"') SERVER_PASSWORD=$(echo "$json" | jq -r '.password // empty') SSH_KEY=$(echo "$json" | jq -r '.key // empty') ACTION=$(echo "$json" | jq -r '.action // empty') TOOLS=$(echo "$json" | jq -r '.tools // empty') SKIP_SSL=$(echo "$json" | jq -r 'if .skip_ssl == true then "true" else "" end') CUSTOMER=$(echo "$json" | jq -r '.customer // empty') DOMAIN=$(echo "$json" | jq -r '.domain // empty') COMPANY_NAME=$(echo "$json" | jq -r '.company_name // empty') } # ============================================================================= # ARGUMENT PARSING # ============================================================================= while [[ $# -gt 0 ]]; do case $1 in --host) SERVER_IP="$2" shift 2 ;; --port) SERVER_PORT="$2" shift 2 ;; --password) SERVER_PASSWORD="$2" shift 2 ;; --key) SSH_KEY="$2" shift 2 ;; --action) ACTION="$2" shift 2 ;; --tools) TOOLS="$2" shift 2 ;; --skip-ssl) SKIP_SSL="true" shift ;; --root-ssl) ROOT_SSL="true" shift ;; --customer) CUSTOMER="$2" shift 2 ;; --domain) DOMAIN="$2" shift 2 ;; --company) COMPANY_NAME="$2" shift 2 ;; --config) CONFIG_JSON=$(cat "$2") parse_json "$CONFIG_JSON" shift 2 ;; --json) parse_json "$2" shift 2 ;; --help|-h) usage ;; *) echo "Unknown option: $1" usage ;; esac done # ============================================================================= # VALIDATION # ============================================================================= if [[ -z "$SERVER_IP" ]]; then echo "ERROR: --host is required" usage fi if [[ -z "$ACTION" ]]; then echo "ERROR: --action is required (setup or connect)" usage fi if [[ "$ACTION" != "setup" && "$ACTION" != "connect" ]]; then echo "ERROR: --action must be 'setup' or 'connect'" usage fi # ============================================================================= # SSH COMMAND BUILDERS # ============================================================================= # SSH options for reliable connections with keep-alive SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=~/.ssh/known_hosts -o ConnectTimeout=30 -o ServerAliveInterval=15 -o ServerAliveCountMax=3" # Build SSH/SCP commands based on auth method if [[ -n "$SERVER_PASSWORD" ]]; then # Password-based authentication (for initial root setup) if ! command -v sshpass &> /dev/null; then echo "ERROR: sshpass is required for password auth. Install with: apt install sshpass" exit 1 fi SSH_CMD="sshpass -p '$SERVER_PASSWORD' ssh $SSH_OPTS -p $SERVER_PORT root@$SERVER_IP" SCP_CMD="sshpass -p '$SERVER_PASSWORD' scp $SSH_OPTS -P $SERVER_PORT" SSH_USER="root" elif [[ -n "$SSH_KEY" ]]; then # Key-based authentication (for stefan user) if [[ ! -f "$SSH_KEY" ]]; then echo "ERROR: SSH key file not found: $SSH_KEY" exit 1 fi SSH_CMD="ssh $SSH_OPTS -i $SSH_KEY -p $SERVER_PORT stefan@$SERVER_IP" SCP_CMD="scp $SSH_OPTS -i $SSH_KEY -P $SERVER_PORT" SSH_USER="stefan" else echo "ERROR: Either --password or --key is required" usage fi # ============================================================================= # MAIN EXECUTION # ============================================================================= echo "=== LetsBe Deployment ===" echo "Server: $SERVER_IP:$SERVER_PORT" echo "Action: $ACTION" echo "Auth: ${SSH_USER:-unknown}" echo "" # Update SSH known hosts check_and_replace_ssh_key "$SERVER_IP" "$SERVER_PORT" if [[ "$ACTION" == "setup" ]]; then # ========================================================================= # SETUP MODE # ========================================================================= if [[ "$SSH_USER" != "root" ]]; then echo "WARNING: Setup typically requires root access for initial deployment." echo "Proceeding with $SSH_USER user..." fi REMOTE_BASE="/opt/letsbe" echo "[0/6] Creating remote directory structure..." eval "$SSH_CMD 'mkdir -p ${REMOTE_BASE}/{scripts,env,stacks,nginx,config,data}'" echo "[1/6] Uploading setup scripts..." eval "$SCP_CMD env_setup.sh ${SSH_USER}@${SERVER_IP}:${REMOTE_BASE}/scripts/" 2>/dev/null || \ eval "$SCP_CMD env_setup.sh ${SSH_USER}@${SERVER_IP}:/tmp/" eval "$SCP_CMD setup.sh ${SSH_USER}@${SERVER_IP}:${REMOTE_BASE}/scripts/" 2>/dev/null || \ eval "$SCP_CMD setup.sh ${SSH_USER}@${SERVER_IP}:/tmp/" echo "[2/6] Uploading backups script..." if ! eval "$SSH_CMD '[ -f ${REMOTE_BASE}/scripts/backups.sh ]'" 2>/dev/null; then eval "$SCP_CMD backups.sh ${SSH_USER}@${SERVER_IP}:${REMOTE_BASE}/scripts/" 2>/dev/null || \ eval "$SCP_CMD backups.sh ${SSH_USER}@${SERVER_IP}:/tmp/" else echo " backups.sh already exists, skipping." fi echo "[3/6] Uploading nginx configurations..." # Check if nginx folder has .conf files (not just exists as empty dir) if ! eval "$SSH_CMD 'ls ${REMOTE_BASE}/nginx/*.conf >/dev/null 2>&1'" 2>/dev/null; then eval "$SCP_CMD -r nginx/* ${SSH_USER}@${SERVER_IP}:${REMOTE_BASE}/nginx/" 2>/dev/null || \ eval "$SCP_CMD -r nginx ${SSH_USER}@${SERVER_IP}:/tmp/" else echo " nginx configs already exist, skipping." fi echo "[4/6] Uploading docker stacks..." # Check if stacks folder has docker-compose files (not just exists as empty dir) if ! eval "$SSH_CMD 'ls ${REMOTE_BASE}/stacks/*/docker-compose.yml >/dev/null 2>&1'" 2>/dev/null; then eval "$SCP_CMD -r stacks/* ${SSH_USER}@${SERVER_IP}:${REMOTE_BASE}/stacks/" 2>/dev/null || \ eval "$SCP_CMD -r stacks ${SSH_USER}@${SERVER_IP}:/tmp/" else echo " stacks already exist, skipping." fi echo "[5/6] Running env_setup.sh..." if ! eval "$SSH_CMD '[ -f ${REMOTE_BASE}/.env_installed ]'" 2>/dev/null; then # Build env_setup.sh arguments (escape values for nested shell) ENV_ARGS="" [[ -n "$CUSTOMER" ]] && ENV_ARGS="$ENV_ARGS --customer $(printf '%q' "$CUSTOMER")" [[ -n "$DOMAIN" ]] && ENV_ARGS="$ENV_ARGS --domain $(printf '%q' "$DOMAIN")" [[ -n "$COMPANY_NAME" ]] && ENV_ARGS="$ENV_ARGS --company $(printf '%q' "$COMPANY_NAME")" if [[ -n "$ENV_ARGS" ]]; then eval "$SSH_CMD \"bash ${REMOTE_BASE}/scripts/env_setup.sh $ENV_ARGS && touch ${REMOTE_BASE}/.env_installed\"" || \ eval "$SSH_CMD \"bash /tmp/env_setup.sh $ENV_ARGS && touch /tmp/.env_installed\"" else echo " WARNING: No customer/domain/company provided. Skipping env_setup.sh" echo " Run manually: env_setup.sh --customer X --domain Y --company Z" fi else echo " env_setup.sh already ran, skipping." fi # Create initial backup before setup if ! eval "$SSH_CMD '[ -f ${REMOTE_BASE}/initial_setup_backup.zip ]'" 2>/dev/null; then echo " Creating initial setup backup..." eval "$SSH_CMD 'apt-get update && apt-get install -y zip && cd ${REMOTE_BASE} && zip -r initial_setup_backup.zip stacks/* nginx/* scripts/backups.sh 2>/dev/null || true'" eval "$SCP_CMD ${SSH_USER}@${SERVER_IP}:${REMOTE_BASE}/initial_setup_backup.zip ." 2>/dev/null || true fi echo "[6/6] Running setup.sh..." if ! eval "$SSH_CMD '[ -f ${REMOTE_BASE}/.setup_installed ]'" 2>/dev/null; then # Build setup.sh arguments SETUP_ARGS="" [[ -n "$TOOLS" ]] && SETUP_ARGS="$SETUP_ARGS --tools '$TOOLS'" [[ -n "$DOMAIN" ]] && SETUP_ARGS="$SETUP_ARGS --domain '$DOMAIN'" [[ "$SKIP_SSL" == "true" ]] && SETUP_ARGS="$SETUP_ARGS --skip-ssl" [[ "$ROOT_SSL" == "true" ]] && SETUP_ARGS="$SETUP_ARGS --root-ssl" # Run setup.sh directly in foreground (connection stays alive with PermitRootLogin yes) echo "Running setup.sh (this may take 10-15 minutes)..." eval "$SSH_CMD 'bash ${REMOTE_BASE}/scripts/setup.sh $SETUP_ARGS && touch ${REMOTE_BASE}/.setup_installed'" echo "Setup completed successfully!" else echo " setup.sh already ran, skipping." fi echo "" echo "==============================================" echo " Setup Complete!" echo "==============================================" echo "" echo "SSH is now on port 22022 with key-only auth." echo "To connect: $0 --host $SERVER_IP --key ./id_ed25519 --port 22022 --action connect" echo "" elif [[ "$ACTION" == "connect" ]]; then # ========================================================================= # CONNECT MODE # ========================================================================= echo "Connecting to server..." echo "(Disconnect with Ctrl+C or 'exit')" echo "" # Try password auth first, then key auth if [[ -n "$SERVER_PASSWORD" ]]; then eval "$SSH_CMD" || { echo "Password auth failed. Trying key auth..." if [[ -n "$SSH_KEY" ]]; then ssh -i "$SSH_KEY" -p "$SERVER_PORT" stefan@"$SERVER_IP" else echo "No SSH key provided. Connection failed." exit 1 fi } elif [[ -n "$SSH_KEY" ]]; then ssh -i "$SSH_KEY" -p "$SERVER_PORT" stefan@"$SERVER_IP" || { echo "Key auth failed. Check SSH configuration." exit 1 } fi fi