initial commit

This commit is contained in:
mwwalters
2026-07-25 15:39:10 -04:00
parent 499913c017
commit 53b46c9001
4 changed files with 341 additions and 0 deletions
@@ -0,0 +1,14 @@
# --- add this snippet to your existing docker-compose.yml file ---
# --- this will define a small container that will serve the maintenance page you define in /maintenance/index.html---
# --- this assumes you have a docker network by the name nginxproxymanager ---
# --- this container must have access to the same docker network as your nginx proxy manager container to function ---
# --- Maintenance page container ---
maintenance:
image: nginx:alpine
restart: unless-stopped
volumes:
- ./maintenance:/usr/share/nginx/html:ro
networks:
- nginxproxymanager
# ----------------------------------
+54
View File
@@ -0,0 +1,54 @@
services:
app:
build: .
image: jc21/nginx-proxy-manager:custom
restart: unless-stopped
ports:
- '80:80' # Public HTTP Port
- '443:443' # Public HTTPS Port
- '81:81' # Admin Web Port
environment:
DB_MYSQL_HOST: 'db'
DB_MYSQL_PORT: '3306'
DB_MYSQL_USER: 'npm'
DB_MYSQL_PASSWORD: 'your_secure_password' # Replace with your secure password
DB_MYSQL_NAME: 'npm'
TZ: 'your_timezone_here' # Set the timezone
# Uncomment this if IPv6 is not enabled on your host
# DISABLE_IPV6: 'true'
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
- /root/nginx-proxy-manager/logs:/data/logs # Bind host logs directory
depends_on:
- db
networks:
- nginxproxymanager
db:
image: 'jc21/mariadb-aria:latest'
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: 'your_secure_root_password' # Replace with your secure root password
MYSQL_DATABASE: 'npm'
MYSQL_USER: 'npm'
MYSQL_PASSWORD: 'your_secure_password' # Replace with your secure password
MARIADB_AUTO_UPGRADE: '1'
volumes:
- ./mysql:/var/lib/mysql
networks:
- nginxproxymanager
# --- Maintenance page container ---
maintenance:
image: nginx:alpine
restart: unless-stopped
volumes:
- ./maintenance:/usr/share/nginx/html:ro
networks:
- nginxproxymanager
# ----------------------------------
networks:
nginxproxymanager:
external: true
+36
View File
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Down for Maintenance</title>
<style>
body {
font-family: sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background: #f4f4f4;
}
.box {
text-align: center;
background: white;
padding: 2rem 3rem;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0,0,0,0.1);
}
h1 { color: #e74c3c; }
p { color: #555; }
a { color: #3498db; }
</style>
</head>
<body>
<div class="box">
<h1>Down for Maintenance</h1>
<p>This resource is currently unavailable while we perform scheduled maintenance.</p>
<p>Please contact the administrator with any questions.</p>
</div>
</body>
</html>
+237
View File
@@ -0,0 +1,237 @@
#!/usr/bin/env bash
# npm-maintenance.sh
# Toggles maintenance mode across all NPM proxy hosts via the NPM REST API
set -euo pipefail
# ============================================================
# Configuration
# ============================================================
NPM_URL="http://localhost:81"
NPM_USER="username"
NPM_PASS="password"
MAINTENANCE_HOST="maintenance"
MAINTENANCE_PORT=80
MAINTENANCE_SCHEME="http"
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
BACKUP_FILE="${SCRIPT_DIR}/proxy_backup.json"
LOG_DIR="${SCRIPT_DIR}"
LOG_OUT="${LOG_DIR}/npm-maintenance.out"
LOG_ERR="${LOG_DIR}/npm-maintenance.err"
TRIGGERED_BY="$(whoami)"
# ============================================================
# Logging
# ============================================================
log_separator() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [----] ----------------------------------------" | tee -a "$LOG_OUT"
}
log_info() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $1" | tee -a "$LOG_OUT"
}
log_warn() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [WARN] $1" | tee -a "$LOG_OUT" >&2
}
log_error() {
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $1"
echo "$msg" | tee -a "$LOG_OUT"
echo "$msg" >> "$LOG_ERR"
}
# ============================================================
# Preflight checks
# ============================================================
preflight_checks() {
if [ ! -d "$LOG_DIR" ]; then
mkdir -p "$LOG_DIR"
log_info "[PREFLIGHT] Log directory not found, created: $LOG_DIR"
fi
if [ ! -f "$LOG_OUT" ]; then
touch "$LOG_OUT"
log_info "[PREFLIGHT] Log file not found, created: $LOG_OUT"
fi
if [ ! -f "$LOG_ERR" ]; then
touch "$LOG_ERR"
log_info "[PREFLIGHT] Error log file not found, created: $LOG_ERR"
fi
if [ ! -f "$BACKUP_FILE" ]; then
echo "[]" > "$BACKUP_FILE"
log_info "[PREFLIGHT] Backup file not found, created empty: $BACKUP_FILE"
fi
}
# ============================================================
# API helpers
# ============================================================
get_token() {
local response
response=$(curl -s -X POST "${NPM_URL}/api/tokens" \
-H "Content-Type: application/json" \
-d "{\"identity\":\"${NPM_USER}\",\"secret\":\"${NPM_PASS}\"}")
local token
token=$(echo "$response" | jq -r '.token // empty')
if [ -z "$token" ]; then
log_error "Failed to authenticate with NPM API. Check NPM_USER and NPM_PASS."
exit 1
fi
echo "$token"
}
get_proxy_hosts() {
local token="$1"
curl -s -X GET "${NPM_URL}/api/nginx/proxy-hosts" \
-H "Authorization: Bearer ${token}"
}
update_proxy_host() {
local token="$1"
local id="$2"
local body="$3"
curl -s -X PUT "${NPM_URL}/api/nginx/proxy-hosts/${id}" \
-H "Authorization: Bearer ${token}" \
-H "Content-Type: application/json" \
-d "$body"
}
# ============================================================
# Enable maintenance
# ============================================================
enable_maintenance() {
log_separator
log_info "START: Enabling maintenance mode (triggered by ${TRIGGERED_BY})"
local token
token=$(get_token)
local hosts
hosts=$(get_proxy_hosts "$token")
local total
total=$(echo "$hosts" | jq length)
if [ "$total" -eq 0 ]; then
log_warn "No proxy hosts found. Nothing to do."
log_separator
exit 0
fi
# Save backup: id, forward_host, forward_port, forward_scheme
echo "$hosts" | jq '[.[] | {id, forward_host, forward_port, forward_scheme}]' > "$BACKUP_FILE"
log_info "Backed up $total proxy host(s) to $BACKUP_FILE"
local success=0
local failed=0
for i in $(seq 0 $((total - 1))); do
local id domain body response
id=$(echo "$hosts" | jq -r ".[$i].id")
domain=$(echo "$hosts" | jq -r ".[$i].domain_names[0]")
# Build update payload from existing host, only swapping forward fields
body=$(echo "$hosts" | jq -c \
".[$i] | del(.id, .created_on, .modified_on, .owner_user_id) | .forward_host = \"${MAINTENANCE_HOST}\" | .forward_port = ${MAINTENANCE_PORT} | .forward_scheme = \"${MAINTENANCE_SCHEME}\"")
response=$(update_proxy_host "$token" "$id" "$body")
if echo "$response" | jq -e '.id' > /dev/null 2>&1; then
log_info " ✓ [$((i+1))/$total] $domain (id: $id) → ${MAINTENANCE_HOST}:${MAINTENANCE_PORT}"
success=$((success + 1))
else
log_error " ✗ [$((i+1))/$total] $domain (id: $id) — API error: $(echo "$response" | jq -r '.error.message // .message // "unknown"')"
fi
done
log_info "DONE: $success succeeded, $failed failed out of $total host(s)"
log_separator
}
# ============================================================
# Disable maintenance
# ============================================================
disable_maintenance() {
log_separator
log_info "START: Disabling maintenance mode (triggered by ${TRIGGERED_BY})"
local count
count=$(jq length "$BACKUP_FILE")
if [ "$count" -eq 0 ]; then
log_error "Backup file exists at $BACKUP_FILE but contains no hosts. Was enable ever run? Aborting."
log_separator
exit 1
fi
local token
token=$(get_token)
local hosts
hosts=$(get_proxy_hosts "$token")
local success=0
local failed=0
for i in $(seq 0 $((count - 1))); do
local id fwd_host fwd_port fwd_scheme current_host domain body response
id=$(jq -r ".[$i].id" "$BACKUP_FILE")
fwd_host=$(jq -r ".[$i].forward_host" "$BACKUP_FILE")
fwd_port=$(jq -r ".[$i].forward_port" "$BACKUP_FILE")
fwd_scheme=$(jq -r ".[$i].forward_scheme" "$BACKUP_FILE")
# Get the current live config for this host and restore only the forward fields
current_host=$(echo "$hosts" | jq -c ".[] | select(.id == $id)")
domain=$(echo "$current_host" | jq -r '.domain_names[0]')
body=$(echo "$current_host" | jq -c \
"del(.id, .created_on, .modified_on, .owner_user_id) | .forward_host = \"${fwd_host}\" | .forward_port = ${fwd_port} | .forward_scheme = \"${fwd_scheme}\"")
response=$(update_proxy_host "$token" "$id" "$body")
if echo "$response" | jq -e '.id' > /dev/null 2>&1; then
log_info " ✓ [$((i+1))/$count] $domain (id: $id) → ${fwd_host}:${fwd_port}"
success=$((success + 1))
else
log_error " ✗ [$((i+1))/$total] $domain (id: $id) — API error: $(echo "$response" | jq -r '.error.message // .message // "unknown"')"
fi
done
if [ "$failed" -eq 0 ]; then
echo "[]" > "$BACKUP_FILE"
log_info "Backup cleared after full successful restore"
else
log_warn "Backup retained at $BACKUP_FILE due to $failed failure(s)"
fi
log_info "DONE: $success succeeded, $failed failed out of $count host(s)"
log_separator
}
# ============================================================
# Entry point
# ============================================================
case "$1" in
enable)
preflight_checks
enable_maintenance
;;
disable)
preflight_checks
disable_maintenance
;;
*)
log_error "Invalid or missing argument. Usage: npm-maintenance {enable|disable}"
exit 1
;;
esac