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.
|
3. Script does not change Cloudflare DNS records automatically.
|
||||||
- DNS updates are intentional/manual to keep blast radius controlled.
|
- 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.
|
4. Do not set public Cloudflare proxied records to private `192.168.x.x` addresses.
|
||||||
5. Canary updates are enclosed between markers:
|
5. Canary upsert behavior is domain-aware:
|
||||||
- `# BEGIN_PHASE7_5_CANARY`
|
- if site block for the canary domain does not exist, it is added
|
||||||
- `# END_PHASE7_5_CANARY`
|
- 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"
|
GITEA_ENTRY="${GITEA_DOMAIN}|http://${UNRAID_GITEA_IP}:3000|false||false"
|
||||||
CADDY_COMPOSE_DIR="${UNRAID_COMPOSE_DIR}/caddy"
|
CADDY_COMPOSE_DIR="${UNRAID_COMPOSE_DIR}/caddy"
|
||||||
CANARY_BEGIN_MARKER="# BEGIN_PHASE7_5_CANARY"
|
|
||||||
CANARY_END_MARKER="# END_PHASE7_5_CANARY"
|
|
||||||
|
|
||||||
SELECTED_HOST_MAP=()
|
SELECTED_HOST_MAP=()
|
||||||
if [[ "$MODE" == "canary" ]]; then
|
if [[ "$MODE" == "canary" ]]; then
|
||||||
@@ -212,6 +210,174 @@ emit_site_block_standalone() {
|
|||||||
} >> "$outfile"
|
} >> "$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() {
|
build_caddyfile() {
|
||||||
local outfile="$1"
|
local outfile="$1"
|
||||||
local entry host upstream streaming body_limit skip_verify
|
local entry host upstream streaming body_limit skip_verify
|
||||||
@@ -260,29 +426,6 @@ build_caddyfile() {
|
|||||||
done
|
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
|
if ! validate_backend_tls_policy; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -340,15 +483,20 @@ if ssh_exec UNRAID "test -f '${CADDY_DATA_PATH}/Caddyfile'" 2>/dev/null; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$MODE" == "canary" && "$HAS_EXISTING_CADDYFILE" == "true" ]]; then
|
if [[ "$MODE" == "canary" && "$HAS_EXISTING_CADDYFILE" == "true" ]]; then
|
||||||
TMP_EXISTING=$(mktemp)
|
TMP_WORK=$(mktemp)
|
||||||
TMP_CLEAN=$(mktemp)
|
TMP_NEXT=$(mktemp)
|
||||||
TMP_FRAGMENT=$(mktemp)
|
cp /dev/null "$TMP_NEXT"
|
||||||
ssh_exec UNRAID "cat '${CADDY_DATA_PATH}/Caddyfile'" > "$TMP_EXISTING"
|
ssh_exec UNRAID "cat '${CADDY_DATA_PATH}/Caddyfile'" > "$TMP_WORK"
|
||||||
sed "/^${CANARY_BEGIN_MARKER}\$/,/^${CANARY_END_MARKER}\$/d" "$TMP_EXISTING" > "$TMP_CLEAN"
|
|
||||||
build_canary_fragment "$TMP_FRAGMENT"
|
for entry in "${CANARY_HOST_MAP[@]}"; do
|
||||||
cat "$TMP_CLEAN" "$TMP_FRAGMENT" > "$TMP_CADDYFILE"
|
upsert_site_block_by_host "$TMP_WORK" "$entry" "$TMP_NEXT"
|
||||||
rm -f "$TMP_EXISTING" "$TMP_CLEAN" "$TMP_FRAGMENT"
|
mv "$TMP_NEXT" "$TMP_WORK"
|
||||||
log_info "Canary mode: preserved existing routes and updated canary block only"
|
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
|
else
|
||||||
build_caddyfile "$TMP_CADDYFILE"
|
build_caddyfile "$TMP_CADDYFILE"
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user