Merge pull request #297 from outis1one/claude/ionos-script-integration-x32ofw

Add Gatus auto-sync from Caddyfile, promote ensure_yq to lib/common.sh
This commit is contained in:
Outis
2026-08-11 00:36:21 -04:00
committed by GitHub
3 changed files with 185 additions and 13 deletions
+27
View File
@@ -244,6 +244,33 @@ ensure_caddy_network() {
&& log_info "Created Docker network ${_net} (needed by Caddy-fronted services)"
}
# Installs yq v4 (Go binary release, not the Python click-based yq some
# distros package under the same name) if not already present. Shared by
# any service that needs to patch YAML config robustly instead of
# hand-rolling sed/awk text surgery — originally lived only in
# services/onlyoffice.sh (FileBrowser config patching); promoted here once
# services/gatus.sh needed the same thing (endpoint sync from Caddyfile),
# so both use one implementation instead of two copies drifting apart.
ensure_yq() {
# Not just `command -v yq` — confirmed live, a box can already have
# `yq` on PATH that's actually kislyuk/yq (the Python jq-wrapper apt
# packages under the same name on Debian/Ubuntu), which silently
# errors on mikefarah's `e '.path' file` syntax every caller here
# uses ("argument files: can't open '.path'"). Check the version
# string actually identifies as mikefarah's before trusting it.
yq --version 2>/dev/null | grep -q mikefarah && return 0
log_info "Installing yq..."
local _arch; _arch=$(uname -m)
local _binary="yq_linux_amd64"
[[ "$_arch" == "aarch64" || "$_arch" == "arm64" ]] && _binary="yq_linux_arm64"
# /usr/local/bin precedes /usr/bin on Ubuntu's default PATH, so this
# correctly shadows a wrong /usr/bin/yq without needing to touch or
# remove it (something else on the box may depend on the real one).
curl -fsSL "https://github.com/mikefarah/yq/releases/latest/download/${_binary}" \
-o /usr/local/bin/yq && chmod +x /usr/local/bin/yq \
&& log_success "yq installed" || log_warning "yq install failed"
}
# Enables UFW if it isn't already active. Call this AFTER the caller has
# already added its own `ufw allow` rules for whatever it needs — this only
# flips UFW from inactive to active, it doesn't add rules for the calling
+154
View File
@@ -207,6 +207,135 @@ fi
register_service gatus utilities "Status & uptime monitoring page (Gatus)" 8086
# ── Caddyfile -> Gatus endpoint sync ────────────────────────────────────────
# Every auto-added endpoint is tagged group: "caddy-sync" — the sync script
# only ever adds or removes entries in that exact group, so anything you add
# by hand (the default external checks, a custom endpoint, different group
# name) is never touched regardless of what the Caddyfile looks like.
_gatus_write_sync_script() {
local gatus_dir="$1" caddy_file="$2"
cat > "$gatus_dir/sync-from-caddy.sh" << 'SYNC_SCRIPT'
#!/bin/bash
# Auto-generated by services/gatus.sh — do not edit by hand, re-run the
# installer to regenerate. Syncs Gatus endpoints with the current
# Caddyfile: adds a group: caddy-sync endpoint for every site block found,
# removes caddy-sync endpoints for sites no longer there. Never touches
# any other endpoint.
set -uo pipefail
CADDYFILE="$1"
GATUS_CONFIG="$2"
# Not just `command -v yq` — some distros package a completely different,
# incompatible yq (kislyuk's Python jq-wrapper) under the same name; check
# it actually identifies as mikefarah's before trusting its syntax.
yq --version 2>/dev/null | grep -q mikefarah || { echo "yq unavailable or incompatible — skipping sync"; exit 0; }
[ -f "$CADDYFILE" ] || { echo "No Caddyfile at $CADDYFILE — skipping sync"; exit 0; }
[ -f "$GATUS_CONFIG" ] || { echo "No Gatus config at $GATUS_CONFIG — skipping sync"; exit 0; }
# Site block headers at brace-depth 0, skipping the bare global-options
# block (no address before its "{") and parenthesized snippet definitions
# like (authelia) — same brace-tracking approach as the Caddy site-block
# removal in lib/common.sh (remove_service), for the same reason: a naive
# "next blank line" scan is exactly the class of bug that already bit this
# repo once on a live Caddy/Samba config.
CURRENT_DOMAINS="$(awk '
BEGIN { depth = 0 }
{
line = $0
opens = gsub(/\{/, "{", line)
closes = gsub(/\}/, "}", line)
if (depth == 0 && opens > 0) {
header = $0
sub(/[[:space:]]*\{.*$/, "", header)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", header)
if (header != "" && header !~ /^\(/) print header
depth += opens - closes
next
}
if (depth > 0) { depth += opens - closes; next }
}
' "$CADDYFILE")"
EXISTING_SYNCED="$(yq e '.endpoints[] | select(.group == "caddy-sync") | .name' "$GATUS_CONFIG" 2>/dev/null)"
ADDED=0
REMOVED=0
while IFS= read -r domain; do
[ -z "$domain" ] && continue
# Domains only — a defensive filter, not strictly needed since these
# come from our own Caddyfile, but cheap insurance against ever
# building a yq expression out of anything unexpected.
[[ "$domain" =~ ^[a-zA-Z0-9.-]+$ ]] || continue
if ! grep -qxF "$domain" <<< "$EXISTING_SYNCED"; then
yq e -i ".endpoints += [{\"name\": \"${domain}\", \"group\": \"caddy-sync\", \"url\": \"https://${domain}\", \"interval\": \"5m\", \"conditions\": [\"[STATUS] < 400\"]}]" "$GATUS_CONFIG" \
&& ADDED=$((ADDED + 1))
fi
done <<< "$CURRENT_DOMAINS"
while IFS= read -r name; do
[ -z "$name" ] && continue
if ! grep -qxF "$name" <<< "$CURRENT_DOMAINS"; then
yq e -i "del(.endpoints[] | select(.group == \"caddy-sync\" and .name == \"${name}\"))" "$GATUS_CONFIG" \
&& REMOVED=$((REMOVED + 1))
fi
done <<< "$EXISTING_SYNCED"
echo "Gatus sync: added $ADDED, removed $REMOVED endpoint(s) from $(basename "$CADDYFILE")"
SYNC_SCRIPT
chmod 700 "$gatus_dir/sync-from-caddy.sh"
chown "$ACTUAL_USER:$ACTUAL_USER" "$gatus_dir/sync-from-caddy.sh" 2>/dev/null || true
}
_gatus_setup_caddy_sync() {
local gatus_dir="$1"
local caddy_file="$DOCKER_DIR/caddy/Caddyfile"
[ -f "$caddy_file" ] || return 0
echo ""
local ENABLE_SYNC=""
prompt_yn "Automatically keep Gatus endpoints in sync with the Caddyfile (syncs now, then every 15 minutes)? (y/n):" "y" ENABLE_SYNC
[[ "$ENABLE_SYNC" =~ ^[Yy]$ ]] || return 0
declare -F ensure_yq >/dev/null 2>&1 && ensure_yq
command -v yq >/dev/null 2>&1 || { log_warning "yq unavailable — skipping Caddy sync."; return 0; }
_gatus_write_sync_script "$gatus_dir" "$caddy_file"
"$gatus_dir/sync-from-caddy.sh" "$caddy_file" "$gatus_dir/gatus_config/config.yaml"
if command -v systemctl >/dev/null 2>&1; then
cat > /etc/systemd/system/gatus-caddy-sync.service << UNIT
[Unit]
Description=Sync Gatus endpoints from Caddyfile
[Service]
Type=oneshot
ExecStart=${gatus_dir}/sync-from-caddy.sh ${caddy_file} ${gatus_dir}/gatus_config/config.yaml
UNIT
cat > /etc/systemd/system/gatus-caddy-sync.timer << 'UNIT'
[Unit]
Description=Periodic Gatus/Caddyfile sync
[Timer]
OnBootSec=5min
OnUnitActiveSec=15min
[Install]
WantedBy=timers.target
UNIT
systemctl daemon-reload
if systemctl enable --now gatus-caddy-sync.timer >/dev/null 2>&1; then
log_success "Sync scheduled every 15 minutes (systemd timer: gatus-caddy-sync)"
else
log_warning "Couldn't enable the sync timer — re-run $gatus_dir/sync-from-caddy.sh manually after Caddyfile changes."
fi
else
log_warning "No systemd — synced once, but won't auto-repeat. Re-run $gatus_dir/sync-from-caddy.sh manually after Caddyfile changes."
fi
}
install_gatus() {
require_docker || return 1
log_info "Installing Gatus..."
@@ -218,6 +347,7 @@ install_gatus() {
echo "[DRY-RUN] Would deploy twinproduction/gatus:latest"
echo "[DRY-RUN] Port 8086 published (auto-scanned for a free host port),"
echo "[DRY-RUN] config at gatus_config/config.yaml"
echo "[DRY-RUN] Would offer to auto-sync endpoints from the Caddyfile (adds/removes group: caddy-sync entries, every 15min via a systemd timer)"
return 0
fi
@@ -359,6 +489,28 @@ Key concepts:
Full docs: https://github.com/TwiN/gatus
## Caddyfile sync
If you enabled it during install, \`sync-from-caddy.sh\` runs every 15
minutes (systemd timer: \`gatus-caddy-sync\`) and keeps one endpoint per
Caddy site block in sync automatically — added when a new site appears,
removed when one goes away. Every auto-added endpoint is tagged
\`group: caddy-sync\`; the sync only ever touches entries in that group,
so anything you add or edit by hand (a different group, the default
external checks) is left alone no matter what.
Run it by hand any time instead of waiting for the timer:
\`\`\`bash
$GATUS_DIR/sync-from-caddy.sh $DOCKER_DIR/caddy/Caddyfile $GATUS_DIR/gatus_config/config.yaml
\`\`\`
Check/manage the timer:
\`\`\`bash
systemctl status gatus-caddy-sync.timer
systemctl list-timers gatus-caddy-sync.timer
sudo systemctl disable --now gatus-caddy-sync.timer # turn it off
\`\`\`
## Manage
\`\`\`bash
cd $GATUS_DIR
@@ -377,6 +529,8 @@ MD
|| log_warning "Failed to start — check: docker compose logs"
fi
_gatus_setup_caddy_sync "$GATUS_DIR"
echo " Access at: http://localhost:${WEB_PORT}"
echo " Config: $GATUS_DIR/gatus_config/config.yaml (hot-reloaded)"
echo ""
+4 -13
View File
@@ -191,17 +191,8 @@ fi
register_service onlyoffice utilities "Self-hosted OnlyOffice Document Server (Nextcloud/FileBrowser)" 8082
# ── Helper: install yq v4 if absent ──────────────────────────────────────────
_ensure_yq() {
command -v yq &>/dev/null && return 0
log_info "Installing yq (required for FileBrowser config patching)..."
local _arch; _arch=$(uname -m)
local _binary="yq_linux_amd64"
[[ "$_arch" == "aarch64" || "$_arch" == "arm64" ]] && _binary="yq_linux_arm64"
curl -fsSL "https://github.com/mikefarah/yq/releases/latest/download/${_binary}" \
-o /usr/local/bin/yq && chmod +x /usr/local/bin/yq \
&& log_success "yq installed" || log_warning "yq install failed — FileBrowser wiring skipped"
}
# yq install helper moved to lib/common.sh (ensure_yq) — shared with
# services/gatus.sh now that it needs the same thing.
# ── Helper: wire OnlyOffice into Nextcloud (idempotent) ───────────────────────
_wire_nextcloud() {
@@ -228,7 +219,7 @@ _wire_nextcloud() {
_wire_filebrowser() {
local _fbq_config="$DOCKER_DIR/filebrowser/config.yaml"
[[ -f "$_fbq_config" ]] || { log_info "FileBrowser config not found — skipping"; return 0; }
command -v yq &>/dev/null || { log_info "yq not found — skipping FileBrowser wiring"; return 0; }
yq --version 2>/dev/null | grep -q mikefarah || { log_info "yq unavailable or incompatible — skipping FileBrowser wiring"; return 0; }
log_info "Wiring OnlyOffice into FileBrowser Quantum..."
yq e '.officeServer = "http://onlyoffice:80/"' -i "$_fbq_config" \
&& log_success "FileBrowser officeServer set" || log_warning "Could not patch FileBrowser config"
@@ -237,7 +228,7 @@ _wire_filebrowser() {
install_onlyoffice() {
require_docker || return 1
_ensure_yq
declare -F ensure_yq >/dev/null 2>&1 && ensure_yq
log_info "Installing OnlyOffice Document Server..."
local DIR="$DOCKER_DIR/onlyoffice"