Files
office_translator/scripts/npm-failover.sh
sepehr 3f980ad537
Some checks failed
Deploy to Production / Build and Deploy (push) Has been cancelled
feat: add NAS backup, verification, and DR scripts
2026-06-07 11:12:01 +02:00

326 lines
11 KiB
Bash

#!/bin/bash
# ==============================================================================
# Wordly.art - NPM Failover via API
# ==============================================================================
# Automatically updates Nginx Proxy Manager's forward host via its REST API.
# Called by disaster-recovery.sh after a successful health check on the new server.
#
# Usage:
# ./npm-failover.sh --target-ip 192.168.1.98 # Switch to new server
# ./npm-failover.sh --target-ip 192.168.1.151 # Rollback to original server
# ./npm-failover.sh --dry-run --target-ip 192.168.1.98 # Test without modifying NPM
# ==============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log() { echo -e "[NPM-Failover ${TIMESTAMP}] $1"; }
log_success() { echo -e "[NPM-Failover ${TIMESTAMP}] ${GREEN}$1${NC}"; }
log_warning() { echo -e "[NPM-Failover ${TIMESTAMP}] ${YELLOW}WARNING: $1${NC}"; }
log_error() { echo -e "[NPM-Failover ${TIMESTAMP}] ${RED}ERROR: $1${NC}"; }
log_info() { echo -e "[NPM-Failover ${TIMESTAMP}] ${BLUE}$1${NC}"; }
# ==============================================================================
# 1. LOAD CONFIGURATION FROM .env
# ==============================================================================
ENV_FILE="${PROJECT_ROOT}/.env"
if [ -f "${ENV_FILE}" ]; then
set -a
source "${ENV_FILE}"
set +a
fi
NPM_API_URL="${NPM_API_URL:-}"
NPM_ADMIN_EMAIL="${NPM_ADMIN_EMAIL:-}"
NPM_ADMIN_PASSWORD="${NPM_ADMIN_PASSWORD:-}"
NPM_PROXY_HOST_DOMAIN="${NPM_PROXY_HOST_DOMAIN:-wordly.art}"
TELEGRAM_BOT_TOKEN="${TELEGRAM_BOT_TOKEN:-}"
TELEGRAM_CHAT_ID="${TELEGRAM_CHAT_ID:-}"
# ==============================================================================
# 2. ARGUMENT PARSING
# ==============================================================================
TARGET_IP=""
DRY_RUN=false
while [[ $# -gt 0 ]]; do
case "$1" in
--target-ip)
TARGET_IP="$2"
shift 2
;;
--dry-run)
DRY_RUN=true
shift
;;
*)
log_error "Unknown argument: $1"
echo "Usage: $0 --target-ip <IP> [--dry-run]"
exit 1
;;
esac
done
# ==============================================================================
# 3. VALIDATION
# ==============================================================================
validate_config() {
local errors=0
if [ -z "${TARGET_IP}" ]; then
log_error "--target-ip is required."
errors=$((errors + 1))
fi
if [ -z "${NPM_API_URL}" ]; then
log_error "NPM_API_URL is not set in .env (example: http://192.168.1.184:81/api)"
errors=$((errors + 1))
fi
if [ -z "${NPM_ADMIN_EMAIL}" ]; then
log_error "NPM_ADMIN_EMAIL is not set in .env"
errors=$((errors + 1))
fi
if [ -z "${NPM_ADMIN_PASSWORD}" ]; then
log_error "NPM_ADMIN_PASSWORD is not set in .env"
errors=$((errors + 1))
fi
if ! command -v curl &>/dev/null; then
log_error "curl is not installed. Required for NPM API calls."
errors=$((errors + 1))
fi
if ! command -v jq &>/dev/null; then
log_error "jq is not installed. Required for JSON parsing. Install: apt-get install jq"
errors=$((errors + 1))
fi
if [ "${errors}" -gt 0 ]; then
exit 1
fi
}
# ==============================================================================
# 4. TELEGRAM NOTIFICATION
# ==============================================================================
send_telegram() {
local message="$1"
if [ -n "${TELEGRAM_BOT_TOKEN}" ] && [ -n "${TELEGRAM_CHAT_ID}" ]; then
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-d "chat_id=${TELEGRAM_CHAT_ID}" \
-d "text=${message}" \
-d "parse_mode=Markdown" \
>/dev/null 2>&1 || true
fi
}
# ==============================================================================
# 5. NPM API AUTHENTICATION
# ==============================================================================
npm_authenticate() {
log "Authenticating with NPM API at ${NPM_API_URL}..."
local response
response=$(curl -s -w "\n%{http_code}" \
-X POST "${NPM_API_URL}/tokens" \
-H "Content-Type: application/json" \
-d "{\"identity\": \"${NPM_ADMIN_EMAIL}\", \"secret\": \"${NPM_ADMIN_PASSWORD}\"}" \
--connect-timeout 10 \
--max-time 15)
local http_code
http_code=$(echo "${response}" | tail -n1)
local body
body=$(echo "${response}" | head -n-1)
if [ "${http_code}" != "200" ]; then
log_error "NPM authentication failed (HTTP ${http_code}). Check NPM_ADMIN_EMAIL and NPM_ADMIN_PASSWORD."
log_error "Response: ${body}"
return 1
fi
local token
token=$(echo "${body}" | jq -r '.token // empty')
if [ -z "${token}" ]; then
log_error "Could not extract token from NPM response."
log_error "Response: ${body}"
return 1
fi
log_success "NPM authentication successful."
echo "${token}"
}
# ==============================================================================
# 6. FIND PROXY HOST BY DOMAIN
# ==============================================================================
npm_find_proxy_host() {
local token="$1"
log "Looking up proxy host for domain: ${NPM_PROXY_HOST_DOMAIN}..."
local response
response=$(curl -s -w "\n%{http_code}" \
-X GET "${NPM_API_URL}/nginx/proxy-hosts?expand=domain_names" \
-H "Authorization: Bearer ${token}" \
--connect-timeout 10 \
--max-time 15)
local http_code
http_code=$(echo "${response}" | tail -n1)
local body
body=$(echo "${response}" | head -n-1)
if [ "${http_code}" != "200" ]; then
log_error "Failed to retrieve proxy hosts (HTTP ${http_code})"
return 1
fi
# Find the proxy host ID matching our domain
local host_id
host_id=$(echo "${body}" | jq -r \
--arg domain "${NPM_PROXY_HOST_DOMAIN}" \
'.[] | select(.domain_names[] == $domain) | .id' | head -n1)
if [ -z "${host_id}" ]; then
log_error "No proxy host found for domain '${NPM_PROXY_HOST_DOMAIN}' in NPM."
log_error "Available domains:"
echo "${body}" | jq -r '.[].domain_names[]' | sed 's/^/ - /' >&2
return 1
fi
log_success "Found proxy host ID: ${host_id} for ${NPM_PROXY_HOST_DOMAIN}"
# Also retrieve current forward_host for logging
local current_host
current_host=$(echo "${body}" | jq -r \
--arg domain "${NPM_PROXY_HOST_DOMAIN}" \
'.[] | select(.domain_names[] == $domain) | .forward_host' | head -n1)
log_info "Current forward host: ${current_host}"
echo "${host_id}|${current_host}"
}
# ==============================================================================
# 7. UPDATE PROXY HOST FORWARD IP
# ==============================================================================
npm_update_proxy_host() {
local token="$1"
local host_id="$2"
local new_ip="$3"
log "Updating proxy host ${host_id} → forward to ${new_ip}..."
# First, get the full current configuration to preserve all existing settings
local current_config
current_config=$(curl -s \
-X GET "${NPM_API_URL}/nginx/proxy-hosts/${host_id}" \
-H "Authorization: Bearer ${token}" \
--connect-timeout 10 \
--max-time 15)
# Build the update payload preserving existing config, only changing forward_host
local update_payload
update_payload=$(echo "${current_config}" | jq \
--arg new_ip "${new_ip}" \
'. + {"forward_host": $new_ip}')
if [ "${DRY_RUN}" = "true" ]; then
log_warning "[DRY RUN] Would send PUT to ${NPM_API_URL}/nginx/proxy-hosts/${host_id}"
log_warning "[DRY RUN] Payload: ${update_payload}"
log_success "[DRY RUN] NPM failover simulation complete — no changes made."
return 0
fi
local response
response=$(curl -s -w "\n%{http_code}" \
-X PUT "${NPM_API_URL}/nginx/proxy-hosts/${host_id}" \
-H "Authorization: Bearer ${token}" \
-H "Content-Type: application/json" \
-d "${update_payload}" \
--connect-timeout 10 \
--max-time 15)
local http_code
http_code=$(echo "${response}" | tail -n1)
local body
body=$(echo "${response}" | head -n-1)
if [ "${http_code}" != "200" ]; then
log_error "Failed to update proxy host (HTTP ${http_code})"
log_error "Response: ${body}"
return 1
fi
# Verify the change was applied
local confirmed_host
confirmed_host=$(echo "${body}" | jq -r '.forward_host // empty')
if [ "${confirmed_host}" != "${new_ip}" ]; then
log_error "NPM accepted the request but the forward_host is '${confirmed_host}', expected '${new_ip}'."
return 1
fi
log_success "NPM proxy host updated successfully: ${NPM_PROXY_HOST_DOMAIN}${new_ip}"
}
# ==============================================================================
# 8. MAIN
# ==============================================================================
main() {
echo ""
echo "========================================================="
echo " Wordly.art — NPM Failover"
echo " Target IP : ${TARGET_IP:-NOT SET}"
echo " NPM API : ${NPM_API_URL:-NOT SET}"
echo " Domain : ${NPM_PROXY_HOST_DOMAIN}"
echo " Dry Run : ${DRY_RUN}"
echo "========================================================="
echo ""
validate_config
# Step 1: Authenticate
local token
token=$(npm_authenticate)
# Step 2: Find proxy host ID and current IP
local host_info
host_info=$(npm_find_proxy_host "${token}")
local host_id="${host_info%%|*}"
local current_ip="${host_info##*|}"
if [ "${current_ip}" = "${TARGET_IP}" ]; then
log_warning "NPM already points to ${TARGET_IP}. No change needed."
exit 0
fi
# Step 3: Update forward host
npm_update_proxy_host "${token}" "${host_id}" "${TARGET_IP}"
# Step 4: Notify
if [ "${DRY_RUN}" = "false" ]; then
local msg="🔀 *Wordly.art NPM Failover*
Domaine : \`${NPM_PROXY_HOST_DOMAIN}\`
Ancien serveur : \`${current_ip}\`
Nouveau serveur : \`${TARGET_IP}\`
Heure : $(date '+%Y-%m-%d %H:%M:%S')"
send_telegram "${msg}"
log_success "Telegram notification sent."
fi
echo ""
log_success "========================================================="
log_success "NPM Failover COMPLETE"
log_success " ${NPM_PROXY_HOST_DOMAIN} now routes to → ${TARGET_IP}"
log_success "========================================================="
echo ""
}
main "$@"