feat: enhance canary mode to support domain-aware upsert behavior for site blocks
This commit is contained in:
7
TODO.md
7
TODO.md
@@ -112,6 +112,7 @@ Phase 7.5 is done only when all are true:
|
||||
3. Script does not change Cloudflare DNS records automatically.
|
||||
- DNS updates are intentional/manual to keep blast radius controlled.
|
||||
4. Do not set public Cloudflare proxied records to private `192.168.x.x` addresses.
|
||||
5. Canary updates are enclosed between markers:
|
||||
- `# BEGIN_PHASE7_5_CANARY`
|
||||
- `# END_PHASE7_5_CANARY`
|
||||
5. Canary upsert behavior is domain-aware:
|
||||
- if site block for the canary domain does not exist, it is added
|
||||
- if site block exists, it is replaced in-place
|
||||
- previous block content is printed in logs before replacement
|
||||
|
||||
@@ -104,8 +104,6 @@ CANARY_HOST_MAP=(
|
||||
|
||||
GITEA_ENTRY="${GITEA_DOMAIN}|http://${UNRAID_GITEA_IP}:3000|false||false"
|
||||
CADDY_COMPOSE_DIR="${UNRAID_COMPOSE_DIR}/caddy"
|
||||
CANARY_BEGIN_MARKER="# BEGIN_PHASE7_5_CANARY"
|
||||
CANARY_END_MARKER="# END_PHASE7_5_CANARY"
|
||||
|
||||
SELECTED_HOST_MAP=()
|
||||
if [[ "$MODE" == "canary" ]]; then
|
||||
@@ -212,6 +210,174 @@ emit_site_block_standalone() {
|
||||
} >> "$outfile"
|
||||
}
|
||||
|
||||
caddy_block_extract_for_host() {
|
||||
local infile="$1" host="$2" outfile="$3"
|
||||
awk -v host="$host" '
|
||||
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
|
||||
}
|
||||
function has_host(labels, i, n, parts, token) {
|
||||
labels = trim(labels)
|
||||
gsub(/[[:space:]]+/, "", labels)
|
||||
n = split(labels, parts, ",")
|
||||
for (i = 1; i <= n; i++) {
|
||||
token = parts[i]
|
||||
if (token == host) {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
BEGIN {
|
||||
depth = 0
|
||||
in_target = 0
|
||||
target_depth = 0
|
||||
found = 0
|
||||
}
|
||||
|
||||
{
|
||||
line = $0
|
||||
|
||||
if (!in_target) {
|
||||
if (depth == 0) {
|
||||
pos = index(line, "{")
|
||||
if (pos > 0) {
|
||||
labels = substr(line, 1, pos - 1)
|
||||
if (trim(labels) != "" && labels !~ /^[[:space:]]*\(/ && has_host(labels)) {
|
||||
in_target = 1
|
||||
target_depth = brace_delta(line)
|
||||
found = 1
|
||||
print line
|
||||
next
|
||||
}
|
||||
}
|
||||
}
|
||||
depth += brace_delta(line)
|
||||
} else {
|
||||
target_depth += brace_delta(line)
|
||||
print line
|
||||
if (target_depth <= 0) {
|
||||
in_target = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
END {
|
||||
if (!found) {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
' "$infile" > "$outfile"
|
||||
}
|
||||
|
||||
caddy_block_remove_for_host() {
|
||||
local infile="$1" host="$2" outfile="$3"
|
||||
awk -v host="$host" '
|
||||
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
|
||||
}
|
||||
function has_host(labels, i, n, parts, token) {
|
||||
labels = trim(labels)
|
||||
gsub(/[[:space:]]+/, "", labels)
|
||||
n = split(labels, parts, ",")
|
||||
for (i = 1; i <= n; i++) {
|
||||
token = parts[i]
|
||||
if (token == host) {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
BEGIN {
|
||||
depth = 0
|
||||
in_target = 0
|
||||
target_depth = 0
|
||||
removed = 0
|
||||
}
|
||||
|
||||
{
|
||||
line = $0
|
||||
|
||||
if (in_target) {
|
||||
target_depth += brace_delta(line)
|
||||
if (target_depth <= 0) {
|
||||
in_target = 0
|
||||
}
|
||||
next
|
||||
}
|
||||
|
||||
if (depth == 0) {
|
||||
pos = index(line, "{")
|
||||
if (pos > 0) {
|
||||
labels = substr(line, 1, pos - 1)
|
||||
if (trim(labels) != "" && labels !~ /^[[:space:]]*\(/ && has_host(labels)) {
|
||||
in_target = 1
|
||||
target_depth = brace_delta(line)
|
||||
removed = 1
|
||||
next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print line
|
||||
depth += brace_delta(line)
|
||||
}
|
||||
|
||||
END {
|
||||
if (!removed) {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
' "$infile" > "$outfile"
|
||||
}
|
||||
|
||||
upsert_site_block_by_host() {
|
||||
local infile="$1" entry="$2" outfile="$3"
|
||||
local host upstream streaming body_limit skip_verify
|
||||
IFS='|' read -r host upstream streaming body_limit skip_verify <<< "$entry"
|
||||
|
||||
local tmp_new_block tmp_old_block tmp_without_old tmp_combined
|
||||
tmp_new_block=$(mktemp)
|
||||
tmp_old_block=$(mktemp)
|
||||
tmp_without_old=$(mktemp)
|
||||
tmp_combined=$(mktemp)
|
||||
|
||||
: > "$tmp_new_block"
|
||||
emit_site_block_standalone "$tmp_new_block" "$host" "$upstream" "$streaming" "$body_limit" "$skip_verify"
|
||||
|
||||
if caddy_block_extract_for_host "$infile" "$host" "$tmp_old_block"; then
|
||||
log_info "Domain '${host}' already exists; replacing existing site block"
|
||||
log_info "Previous block for '${host}':"
|
||||
sed 's/^/ | /' "$tmp_old_block" >&2
|
||||
caddy_block_remove_for_host "$infile" "$host" "$tmp_without_old"
|
||||
cat "$tmp_without_old" "$tmp_new_block" > "$tmp_combined"
|
||||
else
|
||||
log_info "Domain '${host}' not present; adding new site block"
|
||||
cat "$infile" "$tmp_new_block" > "$tmp_combined"
|
||||
fi
|
||||
|
||||
mv "$tmp_combined" "$outfile"
|
||||
rm -f "$tmp_new_block" "$tmp_old_block" "$tmp_without_old"
|
||||
}
|
||||
|
||||
build_caddyfile() {
|
||||
local outfile="$1"
|
||||
local entry host upstream streaming body_limit skip_verify
|
||||
@@ -260,29 +426,6 @@ build_caddyfile() {
|
||||
done
|
||||
}
|
||||
|
||||
build_canary_fragment() {
|
||||
local outfile="$1"
|
||||
local entry host upstream streaming body_limit skip_verify
|
||||
: > "$outfile"
|
||||
{
|
||||
echo
|
||||
echo "${CANARY_BEGIN_MARKER}"
|
||||
echo "# Managed by phase7_5_nginx_to_caddy.sh (canary additive mode)"
|
||||
echo "# Existing routes above are preserved."
|
||||
echo
|
||||
} >> "$outfile"
|
||||
|
||||
for entry in "${CANARY_HOST_MAP[@]}"; do
|
||||
IFS='|' read -r host upstream streaming body_limit skip_verify <<< "$entry"
|
||||
emit_site_block_standalone "$outfile" "$host" "$upstream" "$streaming" "$body_limit" "$skip_verify"
|
||||
done
|
||||
|
||||
{
|
||||
echo "${CANARY_END_MARKER}"
|
||||
echo
|
||||
} >> "$outfile"
|
||||
}
|
||||
|
||||
if ! validate_backend_tls_policy; then
|
||||
exit 1
|
||||
fi
|
||||
@@ -340,15 +483,20 @@ if ssh_exec UNRAID "test -f '${CADDY_DATA_PATH}/Caddyfile'" 2>/dev/null; then
|
||||
fi
|
||||
|
||||
if [[ "$MODE" == "canary" && "$HAS_EXISTING_CADDYFILE" == "true" ]]; then
|
||||
TMP_EXISTING=$(mktemp)
|
||||
TMP_CLEAN=$(mktemp)
|
||||
TMP_FRAGMENT=$(mktemp)
|
||||
ssh_exec UNRAID "cat '${CADDY_DATA_PATH}/Caddyfile'" > "$TMP_EXISTING"
|
||||
sed "/^${CANARY_BEGIN_MARKER}\$/,/^${CANARY_END_MARKER}\$/d" "$TMP_EXISTING" > "$TMP_CLEAN"
|
||||
build_canary_fragment "$TMP_FRAGMENT"
|
||||
cat "$TMP_CLEAN" "$TMP_FRAGMENT" > "$TMP_CADDYFILE"
|
||||
rm -f "$TMP_EXISTING" "$TMP_CLEAN" "$TMP_FRAGMENT"
|
||||
log_info "Canary mode: preserved existing routes and updated canary block only"
|
||||
TMP_WORK=$(mktemp)
|
||||
TMP_NEXT=$(mktemp)
|
||||
cp /dev/null "$TMP_NEXT"
|
||||
ssh_exec UNRAID "cat '${CADDY_DATA_PATH}/Caddyfile'" > "$TMP_WORK"
|
||||
|
||||
for entry in "${CANARY_HOST_MAP[@]}"; do
|
||||
upsert_site_block_by_host "$TMP_WORK" "$entry" "$TMP_NEXT"
|
||||
mv "$TMP_NEXT" "$TMP_WORK"
|
||||
TMP_NEXT=$(mktemp)
|
||||
done
|
||||
|
||||
cp "$TMP_WORK" "$TMP_CADDYFILE"
|
||||
rm -f "$TMP_WORK" "$TMP_NEXT"
|
||||
log_info "Canary mode: existing routes preserved; canary domains upserted"
|
||||
else
|
||||
build_caddyfile "$TMP_CADDYFILE"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user