Improve deployment script

This commit is contained in:
2025-12-01 18:22:44 +03:00
Unverified
parent 620c1db260
commit ebf558720c
2 changed files with 176 additions and 61 deletions
+1
View File
@@ -112,6 +112,7 @@ web_modules/
# dotenv environment variable files # dotenv environment variable files
.env .env
.env.prod
.env.development.local .env.development.local
.env.test.local .env.test.local
.env.production.local .env.production.local
+170 -56
View File
@@ -19,7 +19,7 @@ BOLD='\033[1m'
info() { echo -e "${BLUE}${NC} $1"; } info() { echo -e "${BLUE}${NC} $1"; }
success() { echo -e "${GREEN}${NC} $1"; } success() { echo -e "${GREEN}${NC} $1"; }
warning() { echo -e "${YELLOW}${NC} $1"; } warning() { echo -e "${YELLOW}${NC} $1"; }
error() { echo -e "${RED}${NC} $1"; exit 1; } error() { echo -e "${RED}${NC} $1"; }
step() { echo -e "${CYAN}${BOLD}${NC} ${BOLD}$1${NC}"; } step() { echo -e "${CYAN}${BOLD}${NC} ${BOLD}$1${NC}"; }
substep() { substep() {
if [ "$2" = "-n" ]; then if [ "$2" = "-n" ]; then
@@ -29,6 +29,44 @@ substep() {
fi fi
} }
echo -e "${MAGENTA}${BOLD}🚀 Deployment${NC}\n"
# Read password with asterisks
read_password() {
local password=""
local char
local old_stty
# Save current terminal settings
old_stty=$(stty -g 2>/dev/null)
# Disable echo
stty -echo 2>/dev/null
# Read characters one by one
while IFS= read -rs -n 1 char; do
# Check for Enter key (empty means Enter was pressed)
if [ -z "$char" ]; then
break
fi
# Check for backspace/delete (ASCII 127)
if [ "$char" = $'\177' ] || [ "$char" = $'\b' ]; then
if [ ${#password} -gt 0 ]; then
password="${password%?}"
printf "\b \b" >&2
fi
else
password+="$char"
printf "*" >&2
fi
done
# Restore terminal settings
stty "$old_stty" 2>/dev/null
echo "" >&2
echo "$password"
}
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
DEPLOYMENT_DIR="$PROJECT_ROOT/deployment" DEPLOYMENT_DIR="$PROJECT_ROOT/deployment"
@@ -53,8 +91,9 @@ fi
# Read server from environment variable (from .env), command line argument, or fallback # Read server from environment variable (from .env), command line argument, or fallback
SERVER="${1:-${DEPLOYMENT_SERVER:-}}" SERVER="${1:-${DEPLOYMENT_SERVER:-}}"
DEPLOY_PATH="${2:-${DEPLOY_PATH:-/home/denis0001-dev/actions-runner/_work/FromChat/FromChat}}" REPO_NAME="FromChat"
PLATFORM="${3:-linux/arm64}" DEPLOY_PATH="~/actions-runner/_work/$REPO_NAME/$REPO_NAME"
PLATFORM="linux/arm64"
# Check if server is provided # Check if server is provided
if [ -z "$SERVER" ]; then if [ -z "$SERVER" ]; then
@@ -68,13 +107,68 @@ if [ -z "$SERVER" ]; then
exit 1 exit 1
fi fi
echo -e "${MAGENTA}${BOLD}🚀 Deployment${NC}\n" # ============================================================================
# SSH AUTHENTICATION
# ============================================================================
step "Authentication"
SSH_KEY_FILE="$HOME/.ssh/id_rsa"
# Ensure ssh-agent is running
if [ -z "$SSH_AUTH_SOCK" ]; then
eval "$(ssh-agent -s)" > /dev/null 2>&1
fi
# Add SSH key to agent if not already loaded
if [ -f "$SSH_KEY_FILE" ]; then
# Check if key is already loaded
KEY_LOADED=false
if ssh-add -l > /dev/null 2>&1; then
# Check if this specific key is loaded by trying to match the public key
KEY_FINGERPRINT=$(ssh-keygen -lf "$SSH_KEY_FILE" 2>/dev/null | awk '{print $2}')
if [ -n "$KEY_FINGERPRINT" ] && ssh-add -l 2>/dev/null | grep -q "$KEY_FINGERPRINT"; then
KEY_LOADED=true
fi
fi
if [ "$KEY_LOADED" = false ]; then
substep "Adding SSH key to agent..."
ssh-add "$SSH_KEY_FILE" 2>/dev/null || true
fi
else
warning "SSH key not found at $SSH_KEY_FILE"
fi
# Test SSH connection once to cache the key (this will prompt for passphrase if needed)
ssh -o ConnectTimeout=5 "$SERVER" "echo" > /dev/null 2>&1 || true
# ============================================================================
# SUDO AUTHENTICATION
# ============================================================================
SUDO_PASSWORD=""
while true; do
substep "Sudo password: " -n
SUDO_PASSWORD=$(read_password)
if [ -z "$SUDO_PASSWORD" ]; then
warning "No password provided - assuming passwordless sudo"
break
fi
if echo "$SUDO_PASSWORD" | ssh "$SERVER" "sudo -S -v" > /dev/null 2>&1; then
export SUDO_PASSWORD
break
else
echo -n " " && error "Invalid password, please try again"
fi
done
# ============================================================================ # ============================================================================
# BUILD PHASE # BUILD PHASE
# ============================================================================ # ============================================================================
echo -e "${MAGENTA}${BOLD}🔨 Building Docker images${NC}" echo -e "\n${MAGENTA}${BOLD}🔨 Building Docker images${NC}\n"
# Determine project name # Determine project name
if [ -n "$SERVER" ]; then if [ -n "$SERVER" ]; then
@@ -84,11 +178,48 @@ else
PROJECT_NAME=$(basename "$DEPLOYMENT_DIR") PROJECT_NAME=$(basename "$DEPLOYMENT_DIR")
fi fi
# Check if Docker daemon is running
check_docker_daemon() {
docker info > /dev/null 2>&1
}
# Start Docker Desktop
start_docker_desktop() {
substep "Starting Docker Desktop..."
if ! docker desktop start > /dev/null 2>&1; then
return 1
fi
# Wait for Docker to be ready (max 60 seconds)
substep "Waiting for Docker to start..." -n
local max_wait=60
local waited=0
while [ $waited -lt $max_wait ]; do
if check_docker_daemon; then
echo ""
return 0
fi
sleep 2
waited=$((waited + 2))
echo -n "."
done
echo ""
return 1
}
# Check buildx # Check buildx
if ! docker buildx version > /dev/null 2>&1; then if ! docker buildx version > /dev/null 2>&1; then
error "Docker buildx not available. Install Docker Desktop." error "Docker buildx not available. Install Docker Desktop."
fi fi
# Check Docker daemon
if ! check_docker_daemon; then
warning "Docker daemon is not running"
if ! start_docker_desktop; then
error "Failed to start Docker Desktop. Please start it manually and try again."
fi
fi
# Setup buildx builder # Setup buildx builder
step "Setting up buildx builder" step "Setting up buildx builder"
BUILDER_NAME="fromchat-builder" BUILDER_NAME="fromchat-builder"
@@ -209,31 +340,6 @@ success "Build complete! ${#BUILT_IMAGES[@]} image(s) ready"
echo -e "\n${MAGENTA}${BOLD}🚀 Deploying to ${SERVER}${NC}\n" echo -e "\n${MAGENTA}${BOLD}🚀 Deploying to ${SERVER}${NC}\n"
# Ask for sudo password at the beginning
step "Authentication"
SUDO_PASSWORD=""
while true; do
substep "Sudo password: " -n
read -sp "" SUDO_PASSWORD
echo ""
if [ -z "$SUDO_PASSWORD" ]; then
warning "No password provided - assuming passwordless sudo"
break
fi
if echo "$SUDO_PASSWORD" | ssh "$SERVER" "sudo -S -v" > /dev/null 2>&1; then
export SUDO_PASSWORD
break
else
error "Invalid password, please try again"
fi
done
# Check SSH connection (silent)
if ! ssh -o BatchMode=yes -o ConnectTimeout=5 "$SERVER" "echo" > /dev/null 2>&1; then
warning "SSH key auth not available, will prompt when needed"
fi
# Check docker pussh # Check docker pussh
if ! docker pussh --help > /dev/null 2>&1; then if ! docker pussh --help > /dev/null 2>&1; then
@@ -275,42 +381,51 @@ fi
# Transfer files # Transfer files
step "Transferring deployment files" step "Transferring deployment files"
TEMP_DIR="/tmp/fromchat-deploy-$$"
ssh "$SERVER" "mkdir -p $TEMP_DIR" > /dev/null 2>&1
# Copy docker-compose.yml # Ensure destination directory exists with proper permissions
if scp "$DEPLOYMENT_DIR/docker-compose.yml" "$SERVER:$TEMP_DIR/docker-compose.yml" > /dev/null 2>&1; then
if [ -n "$SUDO_PASSWORD" ]; then if [ -n "$SUDO_PASSWORD" ]; then
ssh "$SERVER" bash << REMOTE_SUDO_SCRIPT > /dev/null 2>&1 ssh "$SERVER" bash << REMOTE_SUDO_SCRIPT > /dev/null 2>&1
set -e set -e
echo '$SUDO_PASSWORD' | sudo -S -p '' mkdir -p $DEPLOY_PATH/deployment 2>/dev/null || true echo '$SUDO_PASSWORD' | sudo -S -p '' mkdir -p $DEPLOY_PATH/deployment 2>/dev/null || true
echo '$SUDO_PASSWORD' | sudo -S -p '' cp $TEMP_DIR/docker-compose.yml $DEPLOY_PATH/deployment/ 2>/dev/null || true echo '$SUDO_PASSWORD' | sudo -S -p '' chown -R \$(whoami):\$(whoami) $DEPLOY_PATH/deployment 2>/dev/null || true
echo '$SUDO_PASSWORD' | sudo -S -p '' chown \$(whoami):\$(whoami) $DEPLOY_PATH/deployment/docker-compose.yml 2>/dev/null || true
REMOTE_SUDO_SCRIPT REMOTE_SUDO_SCRIPT
else else
ssh "$SERVER" "sudo mkdir -p $DEPLOY_PATH/deployment && sudo cp $TEMP_DIR/docker-compose.yml $DEPLOY_PATH/deployment/ && sudo chown \$(whoami):\$(whoami) $DEPLOY_PATH/deployment/docker-compose.yml" > /dev/null 2>&1 || true ssh "$SERVER" "sudo mkdir -p $DEPLOY_PATH/deployment && sudo chown -R \$(whoami):\$(whoami) $DEPLOY_PATH/deployment" > /dev/null 2>&1 || true
fi
fi fi
# Copy service file # Copy deployment directory excluding gitignored files
scp "$DEPLOYMENT_DIR/fromchat.service" "$SERVER:$TEMP_DIR/fromchat.service" > /dev/null 2>&1 || { cd "$PROJECT_ROOT"
error "Failed to copy fromchat.service" substep "Copying deployment directory..."
}
if [ -n "$SUDO_PASSWORD" ]; then # Generate exclude file for rsync using git ls-files to list ignored files
ssh "$SERVER" bash << REMOTE_SUDO_SCRIPT > /dev/null 2>&1 EXCLUDE_FILE="/tmp/fromchat-rsync-exclude-$$"
set -e RSYNC_ERROR="/tmp/fromchat-rsync-error-$$"
echo '$SUDO_PASSWORD' | sudo -S -p '' mkdir -p $DEPLOY_PATH/deployment 2>/dev/null
echo '$SUDO_PASSWORD' | sudo -S -p '' cp $TEMP_DIR/fromchat.service $DEPLOY_PATH/deployment/ 2>/dev/null # Get ignored files in deployment directory and convert to rsync exclude patterns
echo '$SUDO_PASSWORD' | sudo -S -p '' chown \$(whoami):\$(whoami) $DEPLOY_PATH/deployment/fromchat.service 2>/dev/null git ls-files --others --ignored --exclude-standard deployment/ 2>/dev/null | \
REMOTE_SUDO_SCRIPT sed 's|^deployment/||' > "$EXCLUDE_FILE" || true
# Use rsync with native --exclude-from option
if rsync -avz --delete --exclude-from="$EXCLUDE_FILE" \
"$DEPLOYMENT_DIR/" \
"$SERVER:$DEPLOY_PATH/deployment/" > "$RSYNC_ERROR" 2>&1; then
rm -f "$EXCLUDE_FILE" "$RSYNC_ERROR"
else else
ssh "$SERVER" "sudo mkdir -p $DEPLOY_PATH/deployment && sudo cp $TEMP_DIR/fromchat.service $DEPLOY_PATH/deployment/ && sudo chown \$(whoami):\$(whoami) $DEPLOY_PATH/deployment/fromchat.service" > /dev/null 2>&1 || { echo -e " ${RED}${NC} Rsync failed. Error output:"
error "Failed to copy fromchat.service" cat "$RSYNC_ERROR" | sed 's/^/ /'
} rm -f "$EXCLUDE_FILE" "$RSYNC_ERROR"
echo -n " " && error "Failed to copy deployment directory"
fi fi
ssh "$SERVER" "rm -rf $TEMP_DIR" > /dev/null 2>&1 || true # Copy .env.prod to .env on server (bypassing gitignore)
if [ -f "$DEPLOYMENT_DIR/.env.prod" ]; then
substep "Copying .env.prod to .env..."
if ! scp "$DEPLOYMENT_DIR/.env.prod" "$SERVER:$DEPLOY_PATH/deployment/.env" > /dev/null 2>&1; then
warning "Failed to copy .env.prod to .env"
fi
else
warning ".env.prod not found in deployment directory"
fi
# Deploy on server # Deploy on server
step "Deploying on server" step "Deploying on server"
@@ -352,13 +467,12 @@ sudo_cmd systemctl daemon-reload
sudo_cmd systemctl restart fromchat sudo_cmd systemctl restart fromchat
sleep 3 sleep 3
if systemctl is-active --quiet fromchat; then if ! systemctl is-active --quiet fromchat; then
echo "✅ Service started"
else
echo "❌ Service failed to start" echo "❌ Service failed to start"
sudo_cmd journalctl --no-pager -xeu fromchat -n 30 sudo_cmd journalctl --no-pager -xeu fromchat -n 30
exit 1 exit 1
fi fi
REMOTE_SCRIPT REMOTE_SCRIPT
echo
success "Deployment complete!" success "Deployment complete!"