Sync all scripts from website downloads — 352 scripts total

Includes updated JS challenge scripts with Claude-User whitelist,
same-site referer bypass, Blackbox-Exporter allowed bot, and all
new exporters, cheat sheets, and automation scripts.
This commit is contained in:
2026-05-25 03:31:08 +02:00
parent dbd6bf0324
commit a1a17e81a1
332 changed files with 174509 additions and 1106 deletions
+891
View File
@@ -0,0 +1,891 @@
#!/bin/bash
################################################################################
# Script Name: setup-web-server.sh
# Version: 1.1
# Description: Production setup for a dedicated nginx web server hosting a
# Hugo static site with GeoIP2 enriched logging. Subcommand-
# based: base hardening, nginx install, Hugo,
# TLS, security toolkit, and status dashboard.
#
# Author: Phil Connor
# Contact: contact@mylinux.work
# Website: https://mylinux.work
# License: MIT
#
# Subcommands:
# base — Server hardening (firewall, fail2ban, sysctl, unattended-upgrades)
# nginx — Install nginx from official repo + production config
# hugo — Install Hugo extended + site directory + deploy script
# tls — Let's Encrypt certificates
# security — Download and run nginx-security.sh toolkit
# status — Dashboard showing all component status
# all — Run base → nginx → hugo → tls → security
#
# Usage:
# sudo ./setup-web-server.sh base [OPTIONS]
# sudo ./setup-web-server.sh nginx --domain mylinux.work
# sudo ./setup-web-server.sh hugo --domain mylinux.work
# sudo ./setup-web-server.sh tls --domain mylinux.work --email admin@example.com
# sudo ./setup-web-server.sh security
# sudo ./setup-web-server.sh status
# sudo ./setup-web-server.sh all --domain mylinux.work
#
################################################################################
set -euo pipefail
# =============================================================================
# SHARED HELPERS
# =============================================================================
# --- Colors (TTY-aware) ---
if [[ -t 1 ]]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
else
RED="" GREEN="" YELLOW="" CYAN="" BOLD="" NC=""
fi
# --- Logging (prefixed with # for Prometheus comment compatibility) ---
info() { echo -e "# ${GREEN}[OK]${NC} $*"; }
warn() { echo -e "# ${YELLOW}[WARN]${NC} $*"; }
die() { echo -e "# ${RED}[FATAL]${NC} $*" >&2; exit 1; }
step() { echo -e "# ${CYAN}[STEP]${NC} $*"; }
banner() {
echo
echo -e "# ${BOLD}════════════════════════════════════════${NC}"
echo -e "# ${BOLD} $*${NC}"
echo -e "# ${BOLD}════════════════════════════════════════${NC}"
echo
}
# --- Root check ---
require_root() {
[[ $EUID -eq 0 ]] || die "This script must be run as root (or via sudo)"
}
# --- OS detection ---
OS="" OSVER="" PKG_MGR=""
detect_os() {
if [[ -f /etc/os-release ]]; then
OS=$(grep -oP '(?<=^ID=).+' /etc/os-release | tr -d '"' | tr '[:upper:]' '[:lower:]')
OSVER=$(grep -oP '(?<=^VERSION_ID=).+' /etc/os-release | tr -d '"' | cut -d. -f1)
fi
case "$OS" in
ubuntu|debian)
PKG_MGR="apt -y"
;;
rocky|almalinux|centos|rhel|ol)
PKG_MGR=$(command -v dnf >/dev/null 2>&1 && echo "dnf -y" || echo "yum -y")
;;
*)
die "Unsupported OS: ${OS:-unknown}. Requires Ubuntu/Debian or RHEL/Rocky."
;;
esac
info "Detected ${OS} ${OSVER}, using ${PKG_MGR}"
}
# --- Architecture ---
detect_arch() {
local arch
arch=$(uname -m)
case "$arch" in
x86_64) echo "amd64" ;;
aarch64) echo "arm64" ;;
*) die "Unsupported architecture: $arch" ;;
esac
}
# --- Backup file with timestamp ---
backup_file() {
local file="$1"
if [[ -f "$file" ]]; then
local ts
ts=$(date +%Y%m%d-%H%M%S)
cp "$file" "${file}.bak-${ts}"
info "Backed up ${file}"
fi
}
# --- nginx test and reload ---
nginx_test_and_reload() {
if nginx -t 2>&1; then
if systemctl is-active --quiet nginx 2>/dev/null; then
systemctl reload nginx
info "nginx config valid — reloaded"
else
systemctl start nginx
info "nginx config valid — started"
fi
else
die "nginx config test failed — not reloading"
fi
}
# --- Generate password ---
generate_password() {
openssl rand -base64 24 | tr -d '/+=' | head -c 24
}
# =============================================================================
# DEFAULTS
# =============================================================================
DOMAIN="mylinux.work"
HOSTNAME_SET="web01"
TIMEZONE="America/Chicago"
ADMIN_EMAIL="contact@mylinux.work"
ADMIN_NAME="chiefgeek"
ADMIN_PASS=""
DB_PASS=""
CERTBOT_EMAIL="contact@mylinux.work"
HUGO_VERSION="latest"
DRY_RUN=false
SKIP_HUGO=false
SKIP_TLS=false
# =============================================================================
# ARGUMENT PARSING
# =============================================================================
SUBCOMMAND=""
parse_args() {
if [[ $# -lt 1 ]]; then
show_help
exit 0
fi
# Handle help before subcommand assignment
case "$1" in
--help|-h|help) show_help; exit 0 ;;
--*) die "Missing subcommand. Usage: $0 <subcommand> [OPTIONS]\n# Run '$0 --help' for available subcommands." ;;
esac
SUBCOMMAND="$1"
shift
while [[ $# -gt 0 ]]; do
case "$1" in
--domain) DOMAIN="$2"; shift 2 ;;
--hostname) HOSTNAME_SET="$2"; shift 2 ;;
--timezone) TIMEZONE="$2"; shift 2 ;;
--admin-email) ADMIN_EMAIL="$2"; shift 2 ;;
--admin-name) ADMIN_NAME="$2"; shift 2 ;;
--admin-pass) ADMIN_PASS="$2"; shift 2 ;;
--db-pass) DB_PASS="$2"; shift 2 ;;
--email) CERTBOT_EMAIL="$2"; shift 2 ;;
--hugo-version) HUGO_VERSION="$2"; shift 2 ;;
--skip-hugo) SKIP_HUGO=true; shift ;;
--skip-tls) SKIP_TLS=true; shift ;;
--dry-run) DRY_RUN=true; shift ;;
--help|-h) show_help; exit 0 ;;
*) die "Unknown option: $1" ;;
esac
done
}
show_help() {
cat <<'EOF'
setup-web-server.sh — Production web server setup
USAGE:
sudo ./setup-web-server.sh SUBCOMMAND [OPTIONS]
SUBCOMMANDS:
base Server hardening (firewall, fail2ban, sysctl, updates)
nginx Install nginx from official repo + production config
hugo Install Hugo extended + site directory + deploy script
tls Let's Encrypt certificates
security Download and run nginx-security.sh toolkit
status Show all component status
all Run everything: base → nginx → hugo → tls → security
OPTIONS:
--domain DOMAIN Primary domain (default: mylinux.work)
--hostname NAME Server hostname (default: web01)
--timezone ZONE Timezone (default: America/Chicago)
--admin-email EMAIL Admin email (default: contact@mylinux.work)
--admin-name NAME Admin username (default: chiefgeek)
--admin-pass PASS Admin password (generated if not set)
--db-pass PASS MySQL password (generated if not set)
--email EMAIL Certbot email (default: contact@mylinux.work)
--hugo-version VERSION Hugo version (default: latest)
--skip-hugo Skip Hugo install in 'all' (for rsync-based deploys)
--skip-tls Skip TLS setup in 'all' (DNS not ready yet)
--dry-run Show what would be done
--help Show this help
EXAMPLES:
sudo ./setup-web-server.sh base --hostname web01 --timezone America/Chicago
sudo ./setup-web-server.sh nginx --domain mylinux.work
sudo ./setup-web-server.sh all --domain mylinux.work
sudo ./setup-web-server.sh status
EOF
}
# =============================================================================
# SUBCOMMAND: base
# =============================================================================
cmd_base() {
banner "Base Server Setup"
# --- Hostname ---
step "Setting hostname to ${HOSTNAME_SET}"
if [[ "$DRY_RUN" == "true" ]]; then
info "[DRY RUN] Would set hostname to ${HOSTNAME_SET}"
else
hostnamectl set-hostname "$HOSTNAME_SET"
info "Hostname set to ${HOSTNAME_SET}"
fi
# --- Timezone ---
step "Setting timezone to ${TIMEZONE}"
if [[ "$DRY_RUN" == "true" ]]; then
info "[DRY RUN] Would set timezone to ${TIMEZONE}"
else
timedatectl set-timezone "$TIMEZONE"
info "Timezone set to ${TIMEZONE}"
fi
# --- Update packages ---
step "Updating packages"
if [[ "$DRY_RUN" == "true" ]]; then
info "[DRY RUN] Would update packages"
else
case "$OS" in
ubuntu|debian)
apt update && apt upgrade -y
;;
*)
$PKG_MGR update
;;
esac
info "Packages updated"
fi
# --- Base tools ---
step "Installing base tools"
if [[ "$DRY_RUN" == "true" ]]; then
info "[DRY RUN] Would install base packages"
else
case "$OS" in
ubuntu|debian)
apt install -y curl wget git htop tmux unzip jq tree ncdu \
fail2ban ufw net-tools dnsutils software-properties-common \
gnupg2 ca-certificates lsb-release openssl
;;
*)
$PKG_MGR install curl wget git htop tmux unzip jq tree ncdu \
fail2ban firewalld net-tools bind-utils openssl
;;
esac
info "Base tools installed"
fi
# --- Firewall ---
step "Configuring firewall"
if [[ "$DRY_RUN" == "true" ]]; then
info "[DRY RUN] Would configure firewall (allow 22, 80, 443)"
else
case "$OS" in
ubuntu|debian)
ufw default deny incoming 2>/dev/null || true
ufw default allow outgoing 2>/dev/null || true
ufw allow 22/tcp comment 'SSH' 2>/dev/null || true
ufw allow 80/tcp comment 'HTTP' 2>/dev/null || true
ufw allow 443/tcp comment 'HTTPS' 2>/dev/null || true
ufw --force enable
;;
*)
systemctl enable --now firewalld
firewall-cmd --permanent --add-service=ssh
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
;;
esac
info "Firewall configured"
fi
# --- fail2ban ---
step "Configuring fail2ban"
if [[ "$DRY_RUN" == "true" ]]; then
info "[DRY RUN] Would configure fail2ban for SSH"
else
local logpath="/var/log/auth.log"
[[ "$OS" =~ ^(rocky|almalinux|centos|rhel|ol)$ ]] && logpath="/var/log/secure"
cat > /etc/fail2ban/jail.local <<JAILEOF
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
[sshd]
enabled = true
logpath = ${logpath}
JAILEOF
systemctl enable --now fail2ban
info "fail2ban configured for SSH"
fi
# --- Unattended upgrades ---
step "Configuring unattended upgrades"
if [[ "$DRY_RUN" == "true" ]]; then
info "[DRY RUN] Would enable unattended upgrades"
else
case "$OS" in
ubuntu|debian)
apt install -y unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades 2>/dev/null || true
;;
*)
$PKG_MGR install dnf-automatic
sed -i 's/apply_updates = no/apply_updates = yes/' /etc/dnf/automatic.conf 2>/dev/null || true
systemctl enable --now dnf-automatic.timer
;;
esac
info "Unattended upgrades enabled"
fi
# --- Sysctl tuning ---
step "Applying sysctl tuning for web serving"
if [[ "$DRY_RUN" == "true" ]]; then
info "[DRY RUN] Would write /etc/sysctl.d/99-web-tuning.conf"
else
cat > /etc/sysctl.d/99-web-tuning.conf <<'SYSEOF'
net.core.somaxconn = 65535
net.ipv4.tcp_fastopen = 3
fs.file-max = 2097152
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 65535
SYSEOF
sysctl --system >/dev/null 2>&1
info "Sysctl tuning applied"
fi
# --- File limits ---
step "Setting file descriptor limits"
if [[ "$DRY_RUN" == "true" ]]; then
info "[DRY RUN] Would set file limits"
else
if ! grep -q "# web-server-setup" /etc/security/limits.conf 2>/dev/null; then
cat >> /etc/security/limits.conf <<'LIMEOF'
# web-server-setup
* soft nofile 65536
* hard nofile 65536
root soft nofile 65536
root hard nofile 65536
LIMEOF
fi
info "File limits configured"
fi
info "Base server setup complete"
}
# =============================================================================
# SUBCOMMAND: nginx
# =============================================================================
cmd_nginx() {
banner "Nginx Installation"
# --- Install nginx from OS package manager ---
step "Installing nginx"
if [[ "$DRY_RUN" == "true" ]]; then
info "[DRY RUN] Would install nginx"
else
$PKG_MGR install nginx
info "nginx installed: $(nginx -v 2>&1)"
fi
# --- Production nginx.conf ---
step "Writing production nginx.conf"
if [[ "$DRY_RUN" == "true" ]]; then
info "[DRY RUN] Would write /etc/nginx/nginx.conf"
else
backup_file /etc/nginx/nginx.conf
local cores
cores=$(nproc)
local connections=$((cores * 4096))
[[ $connections -gt 65536 ]] && connections=65536
cat > /etc/nginx/nginx.conf <<NGXEOF
user www-data;
worker_processes auto;
worker_rlimit_nofile 65536;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
error_log /var/log/nginx/error.log warn;
events {
worker_connections ${connections};
multi_accept on;
use epoll;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '\$remote_addr - \$remote_user [\$time_local] '
'"\$request" \$status \$body_bytes_sent '
'"\$http_referer" "\$http_user_agent" '
'ssl=\$ssl_protocol rt=\$request_time '
'\$geoip2_country_code \"\$geoip2_asn_org\"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;
client_body_timeout 30;
client_header_timeout 30;
send_timeout 30;
client_body_buffer_size 16k;
client_max_body_size 50m;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_min_length 256;
gzip_types text/plain text/css text/javascript
application/javascript application/json
application/xml application/rss+xml
image/svg+xml;
server_tokens off;
# Drop requests with no matching server_name
server {
listen 80 default_server;
server_name _;
return 444;
}
include /etc/nginx/conf.d/*.conf;
}
NGXEOF
# Ensure www-data user exists
id www-data &>/dev/null || useradd -r -s /usr/sbin/nologin www-data
info "nginx.conf written (${cores} cores, ${connections} connections)"
fi
# --- Site directories ---
step "Creating nginx config structure"
if [[ "$DRY_RUN" == "true" ]]; then
info "[DRY RUN] Would create conf.d directory and site configs"
else
mkdir -p /etc/nginx/conf.d
# Remove default configs that conflict
rm -f /etc/nginx/conf.d/default.conf
rm -f /etc/nginx/sites-enabled/default 2>/dev/null || true
fi
# --- Hugo site server block ---
step "Creating nginx config for ${DOMAIN}"
if [[ "$DRY_RUN" == "true" ]]; then
info "[DRY RUN] Would create /etc/nginx/conf.d/${DOMAIN}.conf"
else
mkdir -p "/var/www/${DOMAIN}/public"
cat > "/etc/nginx/conf.d/${DOMAIN}.conf" <<SITEEOF
server {
listen 80;
server_name ${DOMAIN} www.${DOMAIN};
root /var/www/${DOMAIN}/public;
index index.html;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2?|ttf|eot)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
location / {
try_files \$uri \$uri/ =404;
}
location ~ /\. {
deny all;
}
error_page 404 /404.html;
}
SITEEOF
info "Created ${DOMAIN} server block"
fi
# --- Start nginx ---
if [[ "$DRY_RUN" == "false" ]]; then
systemctl enable nginx
nginx_test_and_reload
info "nginx installed and running"
fi
}
# =============================================================================
# SUBCOMMAND: hugo
# =============================================================================
cmd_hugo() {
banner "Hugo Installation"
local arch
arch=$(detect_arch)
# --- Resolve version ---
local version="$HUGO_VERSION"
if [[ "$version" == "latest" ]]; then
step "Resolving latest Hugo version"
version=$(curl -fsSL https://api.github.com/repos/gohugoio/hugo/releases/latest | \
grep -oP '"tag_name":\s*"v\K[^"]+' | head -1)
[[ -z "$version" ]] && die "Could not resolve latest Hugo version"
info "Latest Hugo version: ${version}"
fi
# --- Install Hugo ---
step "Installing Hugo extended ${version}"
if [[ "$DRY_RUN" == "true" ]]; then
info "[DRY RUN] Would install Hugo extended ${version}"
else
if command -v hugo &>/dev/null; then
local current
current=$(hugo version 2>/dev/null | grep -oP 'v\K[0-9.]+' | head -1)
if [[ "$current" == "$version" ]]; then
info "Hugo ${version} already installed"
else
info "Upgrading Hugo from ${current} to ${version}"
fi
fi
local url="https://github.com/gohugoio/hugo/releases/download/v${version}/hugo_extended_${version}_linux-${arch}.tar.gz"
local tmp="/tmp/hugo-install-$$"
mkdir -p "$tmp"
wget -qO "${tmp}/hugo.tar.gz" "$url" || die "Failed to download Hugo ${version}"
tar -xzf "${tmp}/hugo.tar.gz" -C "$tmp"
mv "${tmp}/hugo" /usr/local/bin/hugo
chmod +x /usr/local/bin/hugo
rm -rf "$tmp"
info "Hugo installed: $(hugo version 2>&1 | head -1)"
fi
# --- Site directory ---
step "Creating site directory /var/www/${DOMAIN}"
if [[ "$DRY_RUN" == "true" ]]; then
info "[DRY RUN] Would create /var/www/${DOMAIN} and deploy user"
else
mkdir -p "/var/www/${DOMAIN}/public"
mkdir -p "/var/www/${DOMAIN}/source"
# Create deploy user
if ! id www-deploy &>/dev/null; then
useradd -r -s /bin/bash -m -d /home/www-deploy www-deploy
info "Created www-deploy user"
fi
# Ensure www-data group exists
getent group www-data &>/dev/null || groupadd -r www-data
usermod -aG www-data www-deploy 2>/dev/null || true
chown -R www-deploy:www-data "/var/www/${DOMAIN}"
chmod -R 775 "/var/www/${DOMAIN}"
info "Site directory ready"
fi
# --- Deploy script ---
step "Creating deploy script"
if [[ "$DRY_RUN" == "true" ]]; then
info "[DRY RUN] Would create /usr/local/bin/deploy-site.sh"
else
cat > /usr/local/bin/deploy-site.sh <<DEPLOYEOF
#!/usr/bin/env bash
set -euo pipefail
SITE_DIR="/var/www/${DOMAIN}"
BRANCH="\${1:-main}"
if [[ ! -d "\$SITE_DIR/source/.git" ]]; then
echo "# No git repo in \$SITE_DIR/source — clone your repo first:"
echo "# git clone --depth 1 YOUR_REPO_URL \$SITE_DIR/source"
exit 1
fi
cd "\$SITE_DIR/source"
git fetch origin "\$BRANCH"
git reset --hard "origin/\$BRANCH"
hugo --minify --destination "\$SITE_DIR/public"
echo "# Deploy complete: \$(date -u +%Y-%m-%dT%H:%M:%SZ)"
DEPLOYEOF
chmod +x /usr/local/bin/deploy-site.sh
info "Deploy script created at /usr/local/bin/deploy-site.sh"
fi
info "Hugo setup complete"
}
# =============================================================================
# SUBCOMMAND: tls
# =============================================================================
cmd_tls() {
banner "TLS Certificate Setup"
step "Installing certbot"
if [[ "$DRY_RUN" == "true" ]]; then
info "[DRY RUN] Would install certbot and obtain certificates"
else
case "$OS" in
ubuntu|debian)
apt install -y certbot python3-certbot-nginx
;;
*)
$PKG_MGR install certbot python3-certbot-nginx
;;
esac
step "Obtaining certificate for ${DOMAIN}"
certbot --nginx -d "${DOMAIN}" -d "www.${DOMAIN}" \
--non-interactive --agree-tos --email "${CERTBOT_EMAIL}" \
--redirect 2>&1 || warn "certbot for ${DOMAIN} returned non-zero (may already exist)"
step "Verifying auto-renewal"
certbot renew --dry-run 2>&1 || warn "Renewal dry-run had issues"
info "TLS certificates configured"
fi
}
# =============================================================================
# SUBCOMMAND: security
# =============================================================================
cmd_security() {
banner "Security Toolkit"
local script_path="/usr/local/bin/nginx-security.sh"
step "Checking for nginx-security.sh"
if [[ "$DRY_RUN" == "true" ]]; then
info "[DRY RUN] Would download and run nginx-security.sh"
return
fi
if [[ ! -f "$script_path" ]]; then
step "Downloading nginx-security.sh"
wget -qO "$script_path" "https://mylinux.work/downloads/nginx-security.sh" || \
die "Failed to download nginx-security.sh"
chmod +x "$script_path"
info "Downloaded nginx-security.sh"
else
info "nginx-security.sh already present"
fi
step "Running bot-block"
"$script_path" bot-block || warn "bot-block returned non-zero"
step "Running js-challenge"
"$script_path" js-challenge --db-ip || warn "js-challenge returned non-zero"
step "Running crowdsec"
"$script_path" crowdsec || warn "crowdsec returned non-zero"
echo
info "Security toolkit applied"
info "Optional: ${script_path} block-head"
info "Status: ${script_path} status"
}
# =============================================================================
# SUBCOMMAND: status
# =============================================================================
cmd_status() {
banner "Server Status Dashboard"
echo -e "# ${BOLD}System${NC}"
echo -e "# Hostname: $(hostname)"
echo -e "# OS: $(grep PRETTY_NAME /etc/os-release 2>/dev/null | cut -d'"' -f2)"
echo -e "# Kernel: $(uname -r)"
echo -e "# Uptime: $(uptime -p 2>/dev/null || uptime)"
echo -e "# CPU: $(nproc) cores"
echo -e "# RAM: $(free -h | awk '/Mem:/{print $2}') total, $(free -h | awk '/Mem:/{print $3}') used"
echo -e "# Disk: $(df -h / | awk 'NR==2{print $3"/"$2" ("$5" used)"}')"
echo
echo -e "# ${BOLD}Nginx${NC}"
if systemctl is-active --quiet nginx 2>/dev/null; then
echo -e "# Status: ${GREEN}running${NC}"
echo -e "# Version: $(nginx -v 2>&1 | cut -d/ -f2)"
if nginx -t 2>/dev/null; then
echo -e "# Config: ${GREEN}valid${NC}"
else
echo -e "# Config: ${RED}invalid${NC}"
fi
else
echo -e "# Status: ${RED}stopped${NC}"
fi
echo
echo -e "# ${BOLD}Hugo Site${NC}"
if [[ -d "/var/www/${DOMAIN}/public" ]]; then
local file_count
file_count=$(find "/var/www/${DOMAIN}/public" -type f 2>/dev/null | wc -l)
echo -e "# Directory: /var/www/${DOMAIN}/public"
echo -e "# Files: ${file_count}"
if [[ -d "/var/www/${DOMAIN}/source/.git" ]]; then
local last_commit
last_commit=$(git -C "/var/www/${DOMAIN}/source" log -1 --format='%ci' 2>/dev/null || echo "unknown")
echo -e "# Last git: ${last_commit}"
fi
else
echo -e "# Status: ${YELLOW}not deployed${NC}"
fi
echo
echo -e "# ${BOLD}TLS Certificates${NC}"
for d in "$DOMAIN"; do
local cert="/etc/letsencrypt/live/${d}/fullchain.pem"
if [[ -f "$cert" ]]; then
local expiry
expiry=$(openssl x509 -enddate -noout -in "$cert" 2>/dev/null | cut -d= -f2)
echo -e "# ${d}: expires ${expiry}"
else
echo -e "# ${d}: ${YELLOW}no certificate${NC}"
fi
done
echo
echo -e "# ${BOLD}Firewall${NC}"
if command -v ufw &>/dev/null; then
local ufw_status
ufw_status=$(ufw status 2>/dev/null | head -1)
echo -e "# UFW: ${ufw_status}"
elif command -v firewall-cmd &>/dev/null; then
echo -e "# firewalld: $(firewall-cmd --state 2>/dev/null)"
else
echo -e "# Status: ${YELLOW}no firewall detected${NC}"
fi
echo
echo -e "# ${BOLD}Fail2ban${NC}"
if systemctl is-active --quiet fail2ban 2>/dev/null; then
echo -e "# Status: ${GREEN}running${NC}"
local jails
jails=$(fail2ban-client status 2>/dev/null | grep "Jail list" | cut -d: -f2 | xargs)
echo -e "# Jails: ${jails:-none}"
else
echo -e "# Status: ${RED}stopped${NC}"
fi
echo
echo -e "# ${BOLD}CrowdSec${NC}"
if systemctl is-active --quiet crowdsec 2>/dev/null; then
echo -e "# Engine: ${GREEN}running${NC}"
local decisions
decisions=$(cscli decisions list -o raw 2>/dev/null | tail -n +2 | wc -l)
echo -e "# Decisions: ${decisions}"
else
echo -e "# Engine: ${YELLOW}not installed${NC}"
fi
}
# =============================================================================
# SUBCOMMAND: all
# =============================================================================
cmd_all() {
banner "Full Server Setup"
if [[ -z "$DOMAIN" ]]; then
die "--domain is required for the 'all' subcommand"
fi
cmd_base
cmd_nginx
if [[ "$SKIP_HUGO" == "true" ]]; then
info "Skipping Hugo install (--skip-hugo) — site directory still created by nginx subcommand"
else
cmd_hugo
fi
if [[ "$SKIP_TLS" == "true" ]]; then
info "Skipping TLS setup (--skip-tls) — run 'tls' subcommand after DNS is pointed"
else
cmd_tls
fi
cmd_security
banner "Setup Complete"
echo
echo -e "# ${BOLD}Summary${NC}"
echo -e "# Hugo site: https://${DOMAIN}"
echo
echo -e "# ${BOLD}Next Steps${NC}"
echo -e "# 1. Clone your Hugo site repo to /var/www/${DOMAIN}/source"
echo -e "# 2. Run deploy-site.sh to build"
echo -e "# 3. Lock down SSH (key-only, disable root, change port)"
echo
}
# =============================================================================
# MAIN
# =============================================================================
main() {
parse_args "$@"
case "$SUBCOMMAND" in
status)
detect_os 2>/dev/null || true
cmd_status
;;
base|nginx|hugo|tls|security|all)
require_root
detect_os
case "$SUBCOMMAND" in
base) cmd_base ;;
nginx) cmd_nginx ;;
hugo) cmd_hugo ;;
tls) cmd_tls ;;
security) cmd_security ;;
all) cmd_all ;;
esac
;;
*)
die "Unknown subcommand: ${SUBCOMMAND}. Run with --help for usage."
;;
esac
}
main "$@"