automated-setup/script/start.sh

419 lines
15 KiB
Bash

#!/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=""
# Docker Hub authentication (optional)
DOCKER_USER=""
DOCKER_TOKEN=""
# =============================================================================
# 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 " --docker-user Docker Hub username (optional, to bypass rate limits)"
echo " --docker-token Docker Hub Personal Access Token (optional)"
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')
DOCKER_USER=$(echo "$json" | jq -r '.docker_user // empty')
DOCKER_TOKEN=$(echo "$json" | jq -r '.docker_token // 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
;;
--docker-user)
DOCKER_USER="$2"
shift 2
;;
--docker-token)
DOCKER_TOKEN="$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 " Converting line endings to Unix format..."
eval "$SSH_CMD 'sed -i \"s/\r\$//\" ${REMOTE_BASE}/scripts/env_setup.sh ${REMOTE_BASE}/scripts/setup.sh 2>/dev/null || true'"
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/"
echo " Converting line endings to Unix format..."
eval "$SSH_CMD 'sed -i \"s/\r\$//\" ${REMOTE_BASE}/scripts/backups.sh 2>/dev/null || true'"
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")"
[[ -n "$DOCKER_USER" ]] && ENV_ARGS="$ENV_ARGS --docker-user $(printf '%q' "$DOCKER_USER")"
# Build environment variables to pass to remote script
ENV_VARS=""
[[ -n "$SYSADMIN_REGISTRATION_TOKEN" ]] && ENV_VARS="SYSADMIN_REGISTRATION_TOKEN=$(printf '%q' "$SYSADMIN_REGISTRATION_TOKEN")"
if [[ -n "$ENV_ARGS" ]]; then
eval "$SSH_CMD \"$ENV_VARS bash ${REMOTE_BASE}/scripts/env_setup.sh $ENV_ARGS && touch ${REMOTE_BASE}/.env_installed\"" || \
eval "$SSH_CMD \"$ENV_VARS 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 $(printf '%q' "$TOOLS")"
[[ -n "$DOMAIN" ]] && SETUP_ARGS="$SETUP_ARGS --domain $(printf '%q' "$DOMAIN")"
[[ "$SKIP_SSL" == "true" ]] && SETUP_ARGS="$SETUP_ARGS --skip-ssl"
[[ "$ROOT_SSL" == "true" ]] && SETUP_ARGS="$SETUP_ARGS --root-ssl"
[[ -n "$DOCKER_USER" ]] && SETUP_ARGS="$SETUP_ARGS --docker-user $(printf '%q' "$DOCKER_USER")"
[[ -n "$DOCKER_TOKEN" ]] && SETUP_ARGS="$SETUP_ARGS --docker-token $(printf '%q' "$DOCKER_TOKEN")"
# 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