feat: add Nginx to Caddy migration toolkit with scripts and usage guide
This commit is contained in:
26
setup/nginx-to-caddy/CUTOVER_CHECKLIST.md
Normal file
26
setup/nginx-to-caddy/CUTOVER_CHECKLIST.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Cutover Checklist
|
||||||
|
|
||||||
|
## Pre-cutover
|
||||||
|
- [ ] `nginx -T` snapshot captured (`output/nginx-full.conf`)
|
||||||
|
- [ ] Generated Caddyfile reviewed
|
||||||
|
- [ ] `conversion-warnings.txt` reviewed and resolved for canary site
|
||||||
|
- [ ] `validate_caddy.sh` passes
|
||||||
|
- [ ] DNS TTL lowered for canary domain
|
||||||
|
|
||||||
|
## Canary
|
||||||
|
- [ ] One subdomain switched to Caddy
|
||||||
|
- [ ] HTTPS cert valid
|
||||||
|
- [ ] UI and API calls work
|
||||||
|
- [ ] Websocket/live components work
|
||||||
|
- [ ] Access/error logs clean
|
||||||
|
|
||||||
|
## Full rollout
|
||||||
|
- [ ] Remaining subdomains switched in batches
|
||||||
|
- [ ] Monitor 24h
|
||||||
|
- [ ] Keep Nginx rollback config archived
|
||||||
|
|
||||||
|
## Rollback trigger
|
||||||
|
- [ ] TLS failures
|
||||||
|
- [ ] High 4xx/5xx
|
||||||
|
- [ ] Broken websockets/realtime components
|
||||||
|
- [ ] Unexpected redirects or auth loops
|
||||||
44
setup/nginx-to-caddy/README.md
Normal file
44
setup/nginx-to-caddy/README.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Nginx to Caddy Toolkit
|
||||||
|
|
||||||
|
Purpose: inventory an existing Nginx reverse-proxy setup and generate a first-pass Caddyfile for simple host/path proxying.
|
||||||
|
|
||||||
|
This module is intentionally conservative:
|
||||||
|
- it auto-converts common/basic patterns
|
||||||
|
- it flags complex directives for manual review
|
||||||
|
- it never edits live Nginx config
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
- `extract_nginx_inventory.sh`
|
||||||
|
- SSH into a host and collect `nginx -T`, `/etc/nginx` tarball, and a quick inventory summary.
|
||||||
|
- `nginx_to_caddy.sh`
|
||||||
|
- Convert basic Nginx server blocks into a generated Caddyfile.
|
||||||
|
- `validate_caddy.sh`
|
||||||
|
- Run `caddy fmt`, `caddy adapt`, and `caddy validate` on the generated Caddyfile.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd setup/nginx-to-caddy
|
||||||
|
|
||||||
|
./extract_nginx_inventory.sh --host=<host> --user=<user> --port=22 --yes
|
||||||
|
./nginx_to_caddy.sh --input=./output/nginx-full.conf --output=./output/Caddyfile.generated --tls-mode=cloudflare --yes
|
||||||
|
./validate_caddy.sh --config=./output/Caddyfile.generated --docker
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conversion Scope
|
||||||
|
|
||||||
|
Automatically handled:
|
||||||
|
- `server_name`
|
||||||
|
- `listen` (80/443 hints)
|
||||||
|
- `ssl_certificate`, `ssl_certificate_key`
|
||||||
|
- `return 301/302 ...`
|
||||||
|
- `location` + `proxy_pass` (simple prefix or exact path)
|
||||||
|
|
||||||
|
Manual follow-up required for:
|
||||||
|
- regex locations (`~`, `~*`)
|
||||||
|
- `rewrite`, `try_files`, `if`, `map`
|
||||||
|
- FastCGI/uWSGI/SCGI/gRPC backends
|
||||||
|
- auth subrequests, Lua, or advanced caching directives
|
||||||
|
|
||||||
|
See `USAGE_GUIDE.md` for a safe migration workflow.
|
||||||
95
setup/nginx-to-caddy/USAGE_GUIDE.md
Normal file
95
setup/nginx-to-caddy/USAGE_GUIDE.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# Usage Guide: Nginx to Caddy Migration
|
||||||
|
|
||||||
|
This guide helps you migrate safely without big-bang risk.
|
||||||
|
|
||||||
|
## 1) Collect Nginx inventory
|
||||||
|
|
||||||
|
Run from your admin machine:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd setup/nginx-to-caddy
|
||||||
|
./extract_nginx_inventory.sh --host=<nginx-host-ip> --user=<ssh-user> --port=22 --yes
|
||||||
|
```
|
||||||
|
|
||||||
|
You will get:
|
||||||
|
- `output/nginx-full.conf`
|
||||||
|
- `output/etc-nginx.tar.gz`
|
||||||
|
- `output/inventory-summary.txt`
|
||||||
|
|
||||||
|
## 2) Generate initial Caddyfile
|
||||||
|
|
||||||
|
For Cloudflare DNS-01 flow:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./nginx_to_caddy.sh \
|
||||||
|
--input=./output/nginx-full.conf \
|
||||||
|
--output=./output/Caddyfile.generated \
|
||||||
|
--warnings=./output/conversion-warnings.txt \
|
||||||
|
--tls-mode=cloudflare \
|
||||||
|
--yes
|
||||||
|
```
|
||||||
|
|
||||||
|
Review warnings:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat ./output/conversion-warnings.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
If warnings include rewrite/regex/auth directives, expect manual edits.
|
||||||
|
|
||||||
|
## 3) Validate generated config
|
||||||
|
|
||||||
|
If `caddy` is not installed locally, use Docker:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./validate_caddy.sh --config=./output/Caddyfile.generated --docker
|
||||||
|
```
|
||||||
|
|
||||||
|
If local `caddy` is installed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./validate_caddy.sh --config=./output/Caddyfile.generated
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4) Canary migration (recommended)
|
||||||
|
|
||||||
|
Migrate one low-risk subdomain first:
|
||||||
|
1. Copy only one site block from generated Caddyfile to your live Caddy config.
|
||||||
|
2. Point only that DNS record to Caddy target.
|
||||||
|
3. Verify:
|
||||||
|
- TLS cert valid
|
||||||
|
- page loads
|
||||||
|
- API/websocket calls work
|
||||||
|
4. Keep Nginx serving all other subdomains.
|
||||||
|
|
||||||
|
## 5) Full migration after canary success
|
||||||
|
|
||||||
|
When the canary is stable:
|
||||||
|
1. Add remaining site blocks.
|
||||||
|
2. Move DNS entries in batches.
|
||||||
|
3. Keep Nginx config snapshots for rollback.
|
||||||
|
4. Decommission Nginx only after monitoring period.
|
||||||
|
|
||||||
|
## 6) Rollback plan
|
||||||
|
|
||||||
|
If a site fails after cutover:
|
||||||
|
1. Repoint affected DNS entry back to Nginx endpoint.
|
||||||
|
2. Restore previous Nginx server block.
|
||||||
|
3. Investigate conversion warnings for that block.
|
||||||
|
|
||||||
|
## 7) Domain/TLS note for your current setup
|
||||||
|
|
||||||
|
You confirmed the domain is `privacyindesign.com`.
|
||||||
|
|
||||||
|
If you use `TLS_MODE=cloudflare` with Caddy, ensure:
|
||||||
|
- Caddy site labels use `*.privacyindesign.com` or specific subdomains under it.
|
||||||
|
- Cloudflare token has DNS edit on the same zone.
|
||||||
|
- DNS records point to the Caddy ingress path you intend (direct or via edge proxy).
|
||||||
|
|
||||||
|
## 8) Suggested next step for Phase 8
|
||||||
|
|
||||||
|
Given your current repo config:
|
||||||
|
- keep Phase 8 Caddy focused on `source.privacyindesign.com`
|
||||||
|
- migrate broader Nginx estate separately with this toolkit
|
||||||
|
|
||||||
|
That keeps Gitea cutover small and lowers blast radius.
|
||||||
113
setup/nginx-to-caddy/extract_nginx_inventory.sh
Executable file
113
setup/nginx-to-caddy/extract_nginx_inventory.sh
Executable file
@@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
# shellcheck source=./lib.sh
|
||||||
|
source "$SCRIPT_DIR/lib.sh"
|
||||||
|
|
||||||
|
REMOTE_HOST=""
|
||||||
|
REMOTE_USER=""
|
||||||
|
REMOTE_PORT="22"
|
||||||
|
OUT_DIR="$SCRIPT_DIR/output"
|
||||||
|
AUTO_YES=false
|
||||||
|
USE_SUDO=true
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<USAGE
|
||||||
|
Usage: $(basename "$0") --host=IP --user=NAME [options]
|
||||||
|
|
||||||
|
Collect Nginx config inventory from a remote host for conversion planning.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--host=IP Remote host/IP (required)
|
||||||
|
--user=NAME SSH username (required)
|
||||||
|
--port=PORT SSH port (default: 22)
|
||||||
|
--out-dir=PATH Output directory (default: setup/nginx-to-caddy/output)
|
||||||
|
--no-sudo Use nginx commands without sudo
|
||||||
|
--yes, -y Skip confirmation prompt
|
||||||
|
--help, -h Show help
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
nginx-version.txt
|
||||||
|
nginx-full.conf (nginx -T combined output)
|
||||||
|
etc-nginx.tar.gz (/etc/nginx snapshot tarball)
|
||||||
|
inventory-summary.txt (basic discovered domains/proxy targets)
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--host=*) REMOTE_HOST="${arg#*=}" ;;
|
||||||
|
--user=*) REMOTE_USER="${arg#*=}" ;;
|
||||||
|
--port=*) REMOTE_PORT="${arg#*=}" ;;
|
||||||
|
--out-dir=*) OUT_DIR="${arg#*=}" ;;
|
||||||
|
--no-sudo) USE_SUDO=false ;;
|
||||||
|
--yes|-y) AUTO_YES=true ;;
|
||||||
|
--help|-h) usage; exit 0 ;;
|
||||||
|
*) log_error "Unknown argument: $arg"; usage; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
require_cmd ssh tar awk sed grep
|
||||||
|
|
||||||
|
if [[ -z "$REMOTE_HOST" || -z "$REMOTE_USER" ]]; then
|
||||||
|
log_error "--host and --user are required"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! confirm_action "Collect nginx config from ${REMOTE_USER}@${REMOTE_HOST}?" "$AUTO_YES"; then
|
||||||
|
log_info "Cancelled"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
|
||||||
|
sudo_prefix=""
|
||||||
|
if [[ "$USE_SUDO" == "true" ]]; then
|
||||||
|
sudo_prefix="sudo "
|
||||||
|
fi
|
||||||
|
|
||||||
|
ssh_target="${REMOTE_USER}@${REMOTE_HOST}"
|
||||||
|
ssh_opts=( -p "$REMOTE_PORT" -o BatchMode=yes -o StrictHostKeyChecking=accept-new )
|
||||||
|
|
||||||
|
log_info "Checking SSH connectivity..."
|
||||||
|
ssh "${ssh_opts[@]}" "$ssh_target" "echo connected" >/dev/null
|
||||||
|
|
||||||
|
log_info "Capturing nginx version and build info..."
|
||||||
|
ssh "${ssh_opts[@]}" "$ssh_target" "${sudo_prefix}nginx -V 2>&1" > "${OUT_DIR}/nginx-version.txt"
|
||||||
|
|
||||||
|
log_info "Capturing full rendered nginx config (nginx -T)..."
|
||||||
|
ssh "${ssh_opts[@]}" "$ssh_target" "${sudo_prefix}nginx -T 2>&1" > "${OUT_DIR}/nginx-full.conf"
|
||||||
|
|
||||||
|
log_info "Capturing /etc/nginx snapshot..."
|
||||||
|
ssh "${ssh_opts[@]}" "$ssh_target" "${sudo_prefix}tar -C / -czf - etc/nginx" > "${OUT_DIR}/etc-nginx.tar.gz"
|
||||||
|
|
||||||
|
log_info "Building inventory summary..."
|
||||||
|
{
|
||||||
|
echo "Inventory generated: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
|
echo
|
||||||
|
echo "Server names:"
|
||||||
|
awk '
|
||||||
|
/^[[:space:]]*server_name[[:space:]]+/ {
|
||||||
|
line=$0
|
||||||
|
sub(/^[[:space:]]*server_name[[:space:]]+/, "", line)
|
||||||
|
sub(/[[:space:]]*;[[:space:]]*$/, "", line)
|
||||||
|
gsub(/[[:space:]]+/, " ", line)
|
||||||
|
print " - " line
|
||||||
|
}
|
||||||
|
' "${OUT_DIR}/nginx-full.conf" | sort -u
|
||||||
|
echo
|
||||||
|
echo "Proxy targets:"
|
||||||
|
awk '
|
||||||
|
/^[[:space:]]*proxy_pass[[:space:]]+/ {
|
||||||
|
line=$0
|
||||||
|
sub(/^[[:space:]]*proxy_pass[[:space:]]+/, "", line)
|
||||||
|
sub(/[[:space:]]*;[[:space:]]*$/, "", line)
|
||||||
|
print " - " line
|
||||||
|
}
|
||||||
|
' "${OUT_DIR}/nginx-full.conf" | sort -u
|
||||||
|
} > "${OUT_DIR}/inventory-summary.txt"
|
||||||
|
|
||||||
|
log_success "Nginx inventory collected in: ${OUT_DIR}"
|
||||||
|
log_info "Next: run nginx_to_caddy.sh --input=${OUT_DIR}/nginx-full.conf"
|
||||||
51
setup/nginx-to-caddy/lib.sh
Executable file
51
setup/nginx-to-caddy/lib.sh
Executable file
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [[ -t 2 ]]; then
|
||||||
|
_C_RESET='\033[0m'
|
||||||
|
_C_RED='\033[0;31m'
|
||||||
|
_C_GREEN='\033[0;32m'
|
||||||
|
_C_YELLOW='\033[0;33m'
|
||||||
|
_C_BLUE='\033[0;34m'
|
||||||
|
else
|
||||||
|
_C_RESET='' _C_RED='' _C_GREEN='' _C_YELLOW='' _C_BLUE=''
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info() {
|
||||||
|
printf '%b[INFO]%b %s\n' "$_C_BLUE" "$_C_RESET" "$*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
log_warn() {
|
||||||
|
printf '%b[WARN]%b %s\n' "$_C_YELLOW" "$_C_RESET" "$*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
log_error() {
|
||||||
|
printf '%b[ERROR]%b %s\n' "$_C_RED" "$_C_RESET" "$*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
log_success() {
|
||||||
|
printf '%b[OK]%b %s\n' "$_C_GREEN" "$_C_RESET" "$*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
require_cmd() {
|
||||||
|
local cmd
|
||||||
|
for cmd in "$@"; do
|
||||||
|
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||||
|
log_error "Required command not found: $cmd"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm_action() {
|
||||||
|
local prompt="${1:-Continue?}"
|
||||||
|
local auto_yes="${2:-false}"
|
||||||
|
|
||||||
|
if [[ "$auto_yes" == "true" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s [y/N] ' "$prompt"
|
||||||
|
read -r reply
|
||||||
|
[[ "$reply" =~ ^[Yy]$ ]]
|
||||||
|
}
|
||||||
617
setup/nginx-to-caddy/nginx_to_caddy.sh
Executable file
617
setup/nginx-to-caddy/nginx_to_caddy.sh
Executable file
@@ -0,0 +1,617 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
# shellcheck source=./lib.sh
|
||||||
|
source "$SCRIPT_DIR/lib.sh"
|
||||||
|
|
||||||
|
INPUT_FILE=""
|
||||||
|
OUTPUT_FILE="$SCRIPT_DIR/output/Caddyfile.generated"
|
||||||
|
WARNINGS_FILE="$SCRIPT_DIR/output/conversion-warnings.txt"
|
||||||
|
TLS_MODE="auto"
|
||||||
|
STRICT=false
|
||||||
|
AUTO_YES=false
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<USAGE
|
||||||
|
Usage: $(basename "$0") --input=nginx-full.conf [options]
|
||||||
|
|
||||||
|
Convert basic Nginx server/location/proxy_pass blocks to a Caddyfile.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--input=PATH Input config (usually nginx -T output)
|
||||||
|
--output=PATH Output Caddyfile (default: setup/nginx-to-caddy/output/Caddyfile.generated)
|
||||||
|
--warnings=PATH Warning report path (default: setup/nginx-to-caddy/output/conversion-warnings.txt)
|
||||||
|
--tls-mode=MODE TLS strategy: auto|cloudflare|existing (default: auto)
|
||||||
|
--strict Exit non-zero if any unsupported directives are found
|
||||||
|
--yes, -y Skip confirmation prompt
|
||||||
|
--help, -h Show help
|
||||||
|
|
||||||
|
Coverage:
|
||||||
|
- server_name
|
||||||
|
- listen (80/443 detection)
|
||||||
|
- ssl_certificate / ssl_certificate_key
|
||||||
|
- return 301/302 redirects
|
||||||
|
- location + proxy_pass (prefix and exact paths)
|
||||||
|
|
||||||
|
Not automatically converted:
|
||||||
|
- regex locations (~, ~*)
|
||||||
|
- fastcgi/uwsgi/scgi/grpc
|
||||||
|
- try_files/rewrite/map/if/auth_request/lua
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--input=*) INPUT_FILE="${arg#*=}" ;;
|
||||||
|
--output=*) OUTPUT_FILE="${arg#*=}" ;;
|
||||||
|
--warnings=*) WARNINGS_FILE="${arg#*=}" ;;
|
||||||
|
--tls-mode=*) TLS_MODE="${arg#*=}" ;;
|
||||||
|
--strict) STRICT=true ;;
|
||||||
|
--yes|-y) AUTO_YES=true ;;
|
||||||
|
--help|-h) usage; exit 0 ;;
|
||||||
|
*) log_error "Unknown argument: $arg"; usage; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
require_cmd awk sed grep sort mktemp
|
||||||
|
|
||||||
|
if [[ -z "$INPUT_FILE" ]]; then
|
||||||
|
log_error "--input is required"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -f "$INPUT_FILE" ]]; then
|
||||||
|
log_error "Input file not found: $INPUT_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$TLS_MODE" in
|
||||||
|
auto|cloudflare|existing) ;;
|
||||||
|
*) log_error "Invalid --tls-mode '$TLS_MODE' (use: auto|cloudflare|existing)"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$OUTPUT_FILE")" "$(dirname "$WARNINGS_FILE")"
|
||||||
|
: > "$WARNINGS_FILE"
|
||||||
|
|
||||||
|
warn() {
|
||||||
|
local msg="$1"
|
||||||
|
printf '%s\n' "$msg" >> "$WARNINGS_FILE"
|
||||||
|
log_warn "$msg"
|
||||||
|
}
|
||||||
|
|
||||||
|
join_by_comma() {
|
||||||
|
local first=true
|
||||||
|
local out=""
|
||||||
|
local item
|
||||||
|
for item in "$@"; do
|
||||||
|
if [[ "$first" == true ]]; then
|
||||||
|
out="$item"
|
||||||
|
first=false
|
||||||
|
else
|
||||||
|
out+=", $item"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
printf '%s' "$out"
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize_redirect_target() {
|
||||||
|
local target="$1"
|
||||||
|
target="$(printf '%s' "$target" | sed \
|
||||||
|
-e 's/\$server_name/{host}/g' \
|
||||||
|
-e 's/\$http_host/{host}/g' \
|
||||||
|
-e 's/\$host/{host}/g' \
|
||||||
|
-e 's/\$request_uri/{uri}/g' \
|
||||||
|
-e 's/\$uri/{uri}/g' \
|
||||||
|
-e 's/\$scheme/{scheme}/g')"
|
||||||
|
printf '%s' "$target"
|
||||||
|
}
|
||||||
|
|
||||||
|
extract_upstream_targets() {
|
||||||
|
local src="$1"
|
||||||
|
|
||||||
|
awk '
|
||||||
|
function clean_comments(s) {
|
||||||
|
sub(/[[:space:]]*#.*/, "", s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
function trim(s) {
|
||||||
|
sub(/^[[:space:]]+/, "", s)
|
||||||
|
sub(/[[:space:]]+$/, "", s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
function brace_delta(s, tmp, opens, closes) {
|
||||||
|
tmp = s
|
||||||
|
opens = gsub(/\{/, "{", tmp)
|
||||||
|
closes = gsub(/\}/, "}", tmp)
|
||||||
|
return opens - closes
|
||||||
|
}
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
in_upstream = 0
|
||||||
|
depth = 0
|
||||||
|
upstream_name = ""
|
||||||
|
targets = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
raw = $0
|
||||||
|
line = clean_comments(raw)
|
||||||
|
|
||||||
|
if (!in_upstream) {
|
||||||
|
if (line ~ /^[[:space:]]*upstream[[:space:]]+[A-Za-z0-9_.-]+[[:space:]]*\{[[:space:]]*$/) {
|
||||||
|
tmp = line
|
||||||
|
sub(/^[[:space:]]*upstream[[:space:]]+/, "", tmp)
|
||||||
|
sub(/[[:space:]]*\{[[:space:]]*$/, "", tmp)
|
||||||
|
upstream_name = trim(tmp)
|
||||||
|
in_upstream = 1
|
||||||
|
depth = 1
|
||||||
|
targets = ""
|
||||||
|
next
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (line ~ /^[[:space:]]*server[[:space:]]+/) {
|
||||||
|
tmp = line
|
||||||
|
sub(/^[[:space:]]*server[[:space:]]+/, "", tmp)
|
||||||
|
sub(/[[:space:]]*;[[:space:]]*$/, "", tmp)
|
||||||
|
tmp = trim(tmp)
|
||||||
|
if (tmp != "") {
|
||||||
|
if (targets == "") {
|
||||||
|
targets = tmp
|
||||||
|
} else {
|
||||||
|
targets = targets "," tmp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
depth += brace_delta(line)
|
||||||
|
if (depth <= 0) {
|
||||||
|
if (upstream_name != "" && targets != "") {
|
||||||
|
print upstream_name "|" targets
|
||||||
|
}
|
||||||
|
in_upstream = 0
|
||||||
|
depth = 0
|
||||||
|
upstream_name = ""
|
||||||
|
targets = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "$src"
|
||||||
|
}
|
||||||
|
|
||||||
|
UPSTREAM_NAMES=()
|
||||||
|
UPSTREAM_TARGETS=()
|
||||||
|
|
||||||
|
load_upstream_map() {
|
||||||
|
local line name targets first_target
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[[ -z "$line" ]] && continue
|
||||||
|
IFS='|' read -r name targets <<< "$line"
|
||||||
|
[[ -z "$name" || -z "$targets" ]] && continue
|
||||||
|
first_target="${targets%%,*}"
|
||||||
|
|
||||||
|
UPSTREAM_NAMES+=("$name")
|
||||||
|
UPSTREAM_TARGETS+=("$first_target")
|
||||||
|
|
||||||
|
if [[ "$targets" == *,* ]]; then
|
||||||
|
warn "upstream '${name}' has multiple servers (${targets}); using first target '${first_target}'"
|
||||||
|
unsupported_count=$((unsupported_count + 1))
|
||||||
|
fi
|
||||||
|
done < <(extract_upstream_targets "$INPUT_FILE")
|
||||||
|
}
|
||||||
|
|
||||||
|
lookup_upstream_target() {
|
||||||
|
local name="$1"
|
||||||
|
local i
|
||||||
|
for i in "${!UPSTREAM_NAMES[@]}"; do
|
||||||
|
if [[ "${UPSTREAM_NAMES[$i]}" == "$name" ]]; then
|
||||||
|
printf '%s' "${UPSTREAM_TARGETS[$i]}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_proxy_target() {
|
||||||
|
local proxy_target="$1"
|
||||||
|
local block_id="$2"
|
||||||
|
local scheme host port path mapped
|
||||||
|
|
||||||
|
if [[ "$proxy_target" =~ ^(https?)://([^/:]+)(:[0-9]+)?(.*)$ ]]; then
|
||||||
|
scheme="${BASH_REMATCH[1]}"
|
||||||
|
host="${BASH_REMATCH[2]}"
|
||||||
|
port="${BASH_REMATCH[3]}"
|
||||||
|
path="${BASH_REMATCH[4]}"
|
||||||
|
|
||||||
|
if mapped="$(lookup_upstream_target "$host")"; then
|
||||||
|
printf '%s://%s%s' "$scheme" "$mapped" "$path"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If host has no dot and no upstream definition, call it out.
|
||||||
|
if [[ "$host" != *.* ]]; then
|
||||||
|
warn "${block_id}: upstream '${host}' not found; leaving proxy target as '${proxy_target}'"
|
||||||
|
unsupported_count=$((unsupported_count + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s' "$proxy_target"
|
||||||
|
}
|
||||||
|
|
||||||
|
extract_server_blocks() {
|
||||||
|
local src="$1"
|
||||||
|
local out_dir="$2"
|
||||||
|
|
||||||
|
awk -v out_dir="$out_dir" '
|
||||||
|
function clean_comments(s) {
|
||||||
|
sub(/[[:space:]]*#.*/, "", s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
function brace_delta(s, tmp, opens, closes) {
|
||||||
|
tmp = s
|
||||||
|
opens = gsub(/\{/, "{", tmp)
|
||||||
|
closes = gsub(/\}/, "}", tmp)
|
||||||
|
return opens - closes
|
||||||
|
}
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
in_server = 0
|
||||||
|
depth = 0
|
||||||
|
idx = 0
|
||||||
|
file = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
raw = $0
|
||||||
|
line = clean_comments(raw)
|
||||||
|
|
||||||
|
if (!in_server) {
|
||||||
|
if (line ~ /^[[:space:]]*server[[:space:]]*\{[[:space:]]*$/) {
|
||||||
|
in_server = 1
|
||||||
|
depth = 0
|
||||||
|
idx += 1
|
||||||
|
file = sprintf("%s/server_%03d.conf", out_dir, idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_server) {
|
||||||
|
print raw >> file
|
||||||
|
depth += brace_delta(line)
|
||||||
|
|
||||||
|
if (depth <= 0) {
|
||||||
|
close(file)
|
||||||
|
in_server = 0
|
||||||
|
depth = 0
|
||||||
|
file = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
END {
|
||||||
|
print idx
|
||||||
|
}
|
||||||
|
' "$src"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! confirm_action "Convert ${INPUT_FILE} to Caddyfile now?" "$AUTO_YES"; then
|
||||||
|
log_info "Cancelled"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp_dir="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$tmp_dir"' EXIT
|
||||||
|
|
||||||
|
server_count="$(extract_server_blocks "$INPUT_FILE" "$tmp_dir")"
|
||||||
|
if [[ -z "$server_count" || "$server_count" -eq 0 ]]; then
|
||||||
|
log_error "No Nginx server blocks found in $INPUT_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# ---------------------------------------------------------------------------"
|
||||||
|
echo "# Generated by setup/nginx-to-caddy/nginx_to_caddy.sh"
|
||||||
|
echo "# Source: $INPUT_FILE"
|
||||||
|
echo "# Generated at: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
|
echo "# ---------------------------------------------------------------------------"
|
||||||
|
echo
|
||||||
|
} > "$OUTPUT_FILE"
|
||||||
|
|
||||||
|
converted_blocks=0
|
||||||
|
unsupported_count=0
|
||||||
|
load_upstream_map
|
||||||
|
|
||||||
|
for block in "$tmp_dir"/server_*.conf; do
|
||||||
|
[[ -f "$block" ]] || continue
|
||||||
|
block_id="$(basename "$block" .conf)"
|
||||||
|
|
||||||
|
# Collect server names
|
||||||
|
names=()
|
||||||
|
while IFS= read -r line; do
|
||||||
|
[[ -z "$line" ]] && continue
|
||||||
|
for token in $line; do
|
||||||
|
[[ "$token" == "_" ]] && continue
|
||||||
|
seen=false
|
||||||
|
for existing in "${names[@]:-}"; do
|
||||||
|
if [[ "$existing" == "$token" ]]; then
|
||||||
|
seen=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ "$seen" == false ]]; then
|
||||||
|
names+=("$token")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done < <(
|
||||||
|
awk '
|
||||||
|
/^[[:space:]]*server_name[[:space:]]+/ {
|
||||||
|
line=$0
|
||||||
|
sub(/^[[:space:]]*server_name[[:space:]]+/, "", line)
|
||||||
|
sub(/[[:space:]]*;[[:space:]]*$/, "", line)
|
||||||
|
gsub(/[[:space:]]+/, " ", line)
|
||||||
|
print line
|
||||||
|
}
|
||||||
|
' "$block"
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ ${#names[@]} -eq 0 ]]; then
|
||||||
|
warn "${block_id}: no server_name found; skipping block"
|
||||||
|
unsupported_count=$((unsupported_count + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Detect listen hints
|
||||||
|
listen_443=false
|
||||||
|
listen_80=false
|
||||||
|
if grep -Eq '^[[:space:]]*listen[[:space:]][^;]*(443|ssl)' "$block"; then
|
||||||
|
listen_443=true
|
||||||
|
fi
|
||||||
|
if grep -Eq '^[[:space:]]*listen[[:space:]][^;]*80([^0-9]|$)' "$block"; then
|
||||||
|
listen_80=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
ssl_cert="$(awk '/^[[:space:]]*ssl_certificate[[:space:]]+/ { line=$0; sub(/^[[:space:]]*ssl_certificate[[:space:]]+/, "", line); sub(/[[:space:]]*;[[:space:]]*$/, "", line); print line; exit }' "$block")"
|
||||||
|
ssl_key="$(awk '/^[[:space:]]*ssl_certificate_key[[:space:]]+/ { line=$0; sub(/^[[:space:]]*ssl_certificate_key[[:space:]]+/, "", line); sub(/[[:space:]]*;[[:space:]]*$/, "", line); print line; exit }' "$block")"
|
||||||
|
|
||||||
|
redirect_code=""
|
||||||
|
redirect_target=""
|
||||||
|
redirect_line="$(awk '/^[[:space:]]*return[[:space:]]+30[12][[:space:]]+/ { line=$0; sub(/^[[:space:]]*/, "", line); sub(/[[:space:]]*;[[:space:]]*$/, "", line); print line; exit }' "$block")"
|
||||||
|
if [[ -n "$redirect_line" ]]; then
|
||||||
|
if [[ "$redirect_line" =~ ^return[[:space:]]+([0-9]{3})[[:space:]]+(.+)$ ]]; then
|
||||||
|
redirect_code="${BASH_REMATCH[1]}"
|
||||||
|
redirect_target="$(normalize_redirect_target "${BASH_REMATCH[2]}")"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
map_lines="$(awk '
|
||||||
|
function trim(s) {
|
||||||
|
sub(/^[[:space:]]+/, "", s)
|
||||||
|
sub(/[[:space:]]+$/, "", s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
function clean_comments(s) {
|
||||||
|
sub(/[[:space:]]*#.*/, "", s)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
function brace_delta(s, t, o, c) {
|
||||||
|
t=s
|
||||||
|
o=gsub(/\{/, "{", t)
|
||||||
|
c=gsub(/\}/, "}", t)
|
||||||
|
return o-c
|
||||||
|
}
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
in_loc=0
|
||||||
|
depth=0
|
||||||
|
loc_expr=""
|
||||||
|
proxy=""
|
||||||
|
insecure_tls=0
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
raw=$0
|
||||||
|
line=clean_comments(raw)
|
||||||
|
|
||||||
|
if (!in_loc) {
|
||||||
|
if (line ~ /^[[:space:]]*location[[:space:]]+/ && line ~ /\{[[:space:]]*$/) {
|
||||||
|
tmp=line
|
||||||
|
sub(/^[[:space:]]*location[[:space:]]+/, "", tmp)
|
||||||
|
sub(/[[:space:]]*\{[[:space:]]*$/, "", tmp)
|
||||||
|
in_loc=1
|
||||||
|
depth=1
|
||||||
|
loc_expr=trim(tmp)
|
||||||
|
proxy=""
|
||||||
|
insecure_tls=0
|
||||||
|
next
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (line ~ /^[[:space:]]*proxy_pass[[:space:]]+/) {
|
||||||
|
tmp=line
|
||||||
|
sub(/^[[:space:]]*proxy_pass[[:space:]]+/, "", tmp)
|
||||||
|
sub(/[[:space:]]*;[[:space:]]*$/, "", tmp)
|
||||||
|
proxy=trim(tmp)
|
||||||
|
}
|
||||||
|
if (line ~ /^[[:space:]]*proxy_ssl_verify[[:space:]]+off[[:space:]]*;[[:space:]]*$/) {
|
||||||
|
insecure_tls=1
|
||||||
|
}
|
||||||
|
|
||||||
|
depth += brace_delta(line)
|
||||||
|
if (depth <= 0) {
|
||||||
|
if (proxy != "") {
|
||||||
|
print loc_expr "|" proxy "|" insecure_tls
|
||||||
|
}
|
||||||
|
in_loc=0
|
||||||
|
depth=0
|
||||||
|
loc_expr=""
|
||||||
|
proxy=""
|
||||||
|
insecure_tls=0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' "$block")"
|
||||||
|
|
||||||
|
if grep -nE '^[[:space:]]*(if|map|rewrite|try_files|fastcgi_pass|uwsgi_pass|scgi_pass|grpc_pass|auth_request)\b' "$block" >/dev/null; then
|
||||||
|
while IFS= read -r line; do
|
||||||
|
warn "${block_id}: unsupported directive -> ${line}"
|
||||||
|
unsupported_count=$((unsupported_count + 1))
|
||||||
|
done < <(grep -nE '^[[:space:]]*(if|map|rewrite|try_files|fastcgi_pass|uwsgi_pass|scgi_pass|grpc_pass|auth_request)\b' "$block")
|
||||||
|
fi
|
||||||
|
|
||||||
|
site_names=()
|
||||||
|
needs_tls=true
|
||||||
|
if [[ "$listen_80" == "true" && "$listen_443" == "false" ]]; then
|
||||||
|
needs_tls=false
|
||||||
|
fi
|
||||||
|
for name in "${names[@]}"; do
|
||||||
|
if [[ "$listen_443" == "true" && "$listen_80" == "false" ]]; then
|
||||||
|
site_names+=("https://${name}")
|
||||||
|
elif [[ "$listen_80" == "true" && "$listen_443" == "false" ]]; then
|
||||||
|
site_names+=("http://${name}")
|
||||||
|
else
|
||||||
|
site_names+=("$name")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
site_label="$(join_by_comma "${site_names[@]}")"
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "# Source block: ${block_id}"
|
||||||
|
echo "${site_label} {"
|
||||||
|
|
||||||
|
if [[ "$TLS_MODE" == "cloudflare" && "$needs_tls" == "true" ]]; then
|
||||||
|
echo " tls {"
|
||||||
|
echo " dns cloudflare {env.CF_API_TOKEN}"
|
||||||
|
echo " }"
|
||||||
|
echo
|
||||||
|
elif [[ "$TLS_MODE" == "existing" && "$needs_tls" == "true" ]]; then
|
||||||
|
if [[ -n "$ssl_cert" && -n "$ssl_key" ]]; then
|
||||||
|
echo " tls ${ssl_cert} ${ssl_key}"
|
||||||
|
echo
|
||||||
|
elif [[ "$listen_443" == "true" ]]; then
|
||||||
|
warn "${block_id}: listen 443 detected but ssl_certificate/ssl_certificate_key missing"
|
||||||
|
unsupported_count=$((unsupported_count + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
emitted_route=false
|
||||||
|
|
||||||
|
if [[ -n "$redirect_code" && -n "$redirect_target" && -z "$map_lines" ]]; then
|
||||||
|
if [[ "$redirect_code" == "301" ]]; then
|
||||||
|
echo " redir ${redirect_target} permanent"
|
||||||
|
elif [[ "$redirect_code" == "302" ]]; then
|
||||||
|
echo " redir ${redirect_target} temporary"
|
||||||
|
else
|
||||||
|
echo " redir ${redirect_target} ${redirect_code}"
|
||||||
|
fi
|
||||||
|
emitted_route=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$map_lines" ]]; then
|
||||||
|
while IFS='|' read -r loc_expr proxy_target insecure_tls; do
|
||||||
|
[[ -z "$loc_expr" || -z "$proxy_target" ]] && continue
|
||||||
|
resolved_proxy_target="$(resolve_proxy_target "$proxy_target" "$block_id")"
|
||||||
|
|
||||||
|
modifier="prefix"
|
||||||
|
path_expr="$loc_expr"
|
||||||
|
|
||||||
|
if [[ "$loc_expr" =~ ^([=~\^]+)[[:space:]]+(.+)$ ]]; then
|
||||||
|
modifier="${BASH_REMATCH[1]}"
|
||||||
|
path_expr="${BASH_REMATCH[2]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$modifier" in
|
||||||
|
"~"|"~*"|"^~")
|
||||||
|
warn "${block_id}: location modifier '${modifier}' for '${path_expr}' requires manual conversion"
|
||||||
|
unsupported_count=$((unsupported_count + 1))
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
"=")
|
||||||
|
if [[ "$path_expr" == /* ]]; then
|
||||||
|
echo " handle ${path_expr} {"
|
||||||
|
if [[ "$insecure_tls" == "1" && "$resolved_proxy_target" == https://* ]]; then
|
||||||
|
echo " reverse_proxy ${resolved_proxy_target} {"
|
||||||
|
echo " transport http {"
|
||||||
|
echo " tls_insecure_skip_verify"
|
||||||
|
echo " }"
|
||||||
|
echo " }"
|
||||||
|
else
|
||||||
|
echo " reverse_proxy ${resolved_proxy_target}"
|
||||||
|
fi
|
||||||
|
echo " }"
|
||||||
|
emitted_route=true
|
||||||
|
else
|
||||||
|
warn "${block_id}: exact location '${loc_expr}' is not a path; manual conversion required"
|
||||||
|
unsupported_count=$((unsupported_count + 1))
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ "$path_expr" == "/" ]]; then
|
||||||
|
if [[ "$insecure_tls" == "1" && "$resolved_proxy_target" == https://* ]]; then
|
||||||
|
echo " reverse_proxy ${resolved_proxy_target} {"
|
||||||
|
echo " transport http {"
|
||||||
|
echo " tls_insecure_skip_verify"
|
||||||
|
echo " }"
|
||||||
|
echo " }"
|
||||||
|
else
|
||||||
|
echo " reverse_proxy ${resolved_proxy_target}"
|
||||||
|
fi
|
||||||
|
emitted_route=true
|
||||||
|
elif [[ "$path_expr" == /* ]]; then
|
||||||
|
clean_path="${path_expr%/}"
|
||||||
|
[[ -z "$clean_path" ]] && clean_path="/"
|
||||||
|
echo " handle ${clean_path} {"
|
||||||
|
if [[ "$insecure_tls" == "1" && "$resolved_proxy_target" == https://* ]]; then
|
||||||
|
echo " reverse_proxy ${resolved_proxy_target} {"
|
||||||
|
echo " transport http {"
|
||||||
|
echo " tls_insecure_skip_verify"
|
||||||
|
echo " }"
|
||||||
|
echo " }"
|
||||||
|
else
|
||||||
|
echo " reverse_proxy ${resolved_proxy_target}"
|
||||||
|
fi
|
||||||
|
echo " }"
|
||||||
|
echo " handle_path ${clean_path}/* {"
|
||||||
|
if [[ "$insecure_tls" == "1" && "$resolved_proxy_target" == https://* ]]; then
|
||||||
|
echo " reverse_proxy ${resolved_proxy_target} {"
|
||||||
|
echo " transport http {"
|
||||||
|
echo " tls_insecure_skip_verify"
|
||||||
|
echo " }"
|
||||||
|
echo " }"
|
||||||
|
else
|
||||||
|
echo " reverse_proxy ${resolved_proxy_target}"
|
||||||
|
fi
|
||||||
|
echo " }"
|
||||||
|
emitted_route=true
|
||||||
|
else
|
||||||
|
warn "${block_id}: location '${loc_expr}' is not a simple path; manual conversion required"
|
||||||
|
unsupported_count=$((unsupported_count + 1))
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done <<< "$map_lines"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$emitted_route" == false ]]; then
|
||||||
|
echo " respond \"No automatic route generated for this server block\" 404"
|
||||||
|
warn "${block_id}: no proxy_pass/redirect converted; added placeholder response"
|
||||||
|
unsupported_count=$((unsupported_count + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "}"
|
||||||
|
echo
|
||||||
|
} >> "$OUTPUT_FILE"
|
||||||
|
|
||||||
|
converted_blocks=$((converted_blocks + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ! -s "$WARNINGS_FILE" ]]; then
|
||||||
|
echo "No warnings." > "$WARNINGS_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Generated Caddyfile: $OUTPUT_FILE"
|
||||||
|
log_info "Converted server blocks: $converted_blocks / $server_count"
|
||||||
|
log_info "Warnings report: $WARNINGS_FILE"
|
||||||
|
|
||||||
|
if [[ "$STRICT" == "true" && "$unsupported_count" -gt 0 ]]; then
|
||||||
|
log_error "Strict mode enabled: ${unsupported_count} warning(s) found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
92
setup/nginx-to-caddy/validate_caddy.sh
Executable file
92
setup/nginx-to-caddy/validate_caddy.sh
Executable file
@@ -0,0 +1,92 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
# shellcheck source=./lib.sh
|
||||||
|
source "$SCRIPT_DIR/lib.sh"
|
||||||
|
|
||||||
|
CONFIG_FILE="$SCRIPT_DIR/output/Caddyfile.generated"
|
||||||
|
FORMAT_FILE=true
|
||||||
|
USE_DOCKER=false
|
||||||
|
DO_ADAPT=true
|
||||||
|
DO_VALIDATE=true
|
||||||
|
CADDY_IMAGE="caddy:2"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<USAGE
|
||||||
|
Usage: $(basename "$0") [options]
|
||||||
|
|
||||||
|
Format/adapt/validate a generated Caddyfile.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--config=PATH Caddyfile to validate (default: setup/nginx-to-caddy/output/Caddyfile.generated)
|
||||||
|
--no-format Skip caddy fmt
|
||||||
|
--no-adapt Skip caddy adapt
|
||||||
|
--no-validate Skip caddy validate
|
||||||
|
--docker Use Docker image instead of local caddy binary
|
||||||
|
--image=NAME Docker image when --docker is used (default: caddy:2)
|
||||||
|
--help, -h Show help
|
||||||
|
USAGE
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--config=*) CONFIG_FILE="${arg#*=}" ;;
|
||||||
|
--no-format) FORMAT_FILE=false ;;
|
||||||
|
--no-adapt) DO_ADAPT=false ;;
|
||||||
|
--no-validate) DO_VALIDATE=false ;;
|
||||||
|
--docker) USE_DOCKER=true ;;
|
||||||
|
--image=*) CADDY_IMAGE="${arg#*=}" ;;
|
||||||
|
--help|-h) usage; exit 0 ;;
|
||||||
|
*) log_error "Unknown argument: $arg"; usage; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||||
|
log_error "Config file not found: $CONFIG_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$USE_DOCKER" == "true" ]]; then
|
||||||
|
require_cmd docker
|
||||||
|
|
||||||
|
if [[ "$FORMAT_FILE" == "true" ]]; then
|
||||||
|
log_info "Formatting Caddyfile with Docker..."
|
||||||
|
docker run --rm \
|
||||||
|
-v "$CONFIG_FILE:/etc/caddy/Caddyfile" \
|
||||||
|
"$CADDY_IMAGE" caddy fmt --overwrite /etc/caddy/Caddyfile
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$DO_ADAPT" == "true" ]]; then
|
||||||
|
log_info "Adapting Caddyfile (Docker)..."
|
||||||
|
docker run --rm \
|
||||||
|
-v "$CONFIG_FILE:/etc/caddy/Caddyfile:ro" \
|
||||||
|
"$CADDY_IMAGE" caddy adapt --config /etc/caddy/Caddyfile --adapter caddyfile >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$DO_VALIDATE" == "true" ]]; then
|
||||||
|
log_info "Validating Caddyfile (Docker)..."
|
||||||
|
docker run --rm \
|
||||||
|
-v "$CONFIG_FILE:/etc/caddy/Caddyfile:ro" \
|
||||||
|
"$CADDY_IMAGE" caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
require_cmd caddy
|
||||||
|
|
||||||
|
if [[ "$FORMAT_FILE" == "true" ]]; then
|
||||||
|
log_info "Formatting Caddyfile..."
|
||||||
|
caddy fmt --overwrite "$CONFIG_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$DO_ADAPT" == "true" ]]; then
|
||||||
|
log_info "Adapting Caddyfile..."
|
||||||
|
caddy adapt --config "$CONFIG_FILE" --adapter caddyfile >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$DO_VALIDATE" == "true" ]]; then
|
||||||
|
log_info "Validating Caddyfile..."
|
||||||
|
caddy validate --config "$CONFIG_FILE" --adapter caddyfile
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Validation checks complete"
|
||||||
Reference in New Issue
Block a user