mirror of
https://github.com/dredx/prole.git
synced 2026-09-27 04:44:30 +00:00
feat(scripts): introduce DNS sync script for Samba AD and hosts management
- Added `init-prole-dns.sh` to sync host records to Samba AD DNS. - Included a fallback `hosts.txt` for static entries when Postgres is unavailable. - Script supports operations such as start, stop, sync, status, and flush. - Integrated cron-based hourly sync functionality.
This commit is contained in:
parent
b735a1df13
commit
4c0c593b34
15
etc/hosts.txt
Normal file
15
etc/hosts.txt
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
myrddin.prole.org 10.0.0.3
|
||||||
|
raspberry.prole.org 10.0.0.4
|
||||||
|
pi.prole.org 10.0.0.5
|
||||||
|
synology.prole.org 10.0.0.203
|
||||||
|
morgoth.prole.org 10.0.0.204
|
||||||
|
zinfandel.prole.org 10.0.0.205
|
||||||
|
aventage.prole.org 10.0.0.206
|
||||||
|
retropie.prole.org 10.0.0.207
|
||||||
|
fairyland.prole.org 10.0.0.208
|
||||||
|
k8s.prole.org zinfandel.prole.org
|
||||||
|
mc.prole.org 73.15.20.166
|
||||||
|
morana.prole.org 10.0.0.66
|
||||||
|
ollama.prole.org 73.15.20.166
|
||||||
|
svc.prole.org 73.15.20.166
|
||||||
|
www.prole.org ghs.googlehosted.com
|
||||||
532
prole-dns/init-prole-dns.sh
Normal file
532
prole-dns/init-prole-dns.sh
Normal file
@ -0,0 +1,532 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# init-prole-dns.sh
|
||||||
|
# Sync prole.org host records into Samba AD DNS using samba-tool.
|
||||||
|
#
|
||||||
|
# Host source:
|
||||||
|
# 1) Postgres (preferred when available):
|
||||||
|
# SELECT host_name, ip_address FROM prole.hosts;
|
||||||
|
# 2) Fallback file:
|
||||||
|
# /etc/prole-dns-sync/hosts.txt (one hostname per line; FQDN or short)
|
||||||
|
#
|
||||||
|
# Commands:
|
||||||
|
# start - run sync now + install hourly cron job
|
||||||
|
# stop - remove cron job
|
||||||
|
# status - show last run time, last exit, and journal messages
|
||||||
|
# sync - run one sync pass
|
||||||
|
# flush - clear local state (forces "unknown" status); does NOT delete DNS records
|
||||||
|
# list|show|details - show configured hostnames and detected DC info
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# -db=HOST:PORT/DBNAME default: localhost:5432/prole-db
|
||||||
|
#
|
||||||
|
# Environment overrides (optional):
|
||||||
|
# AD_DNS_ZONE default: prole.org
|
||||||
|
# AD_DNS_SERVER default: auto-detect (hostname -f)
|
||||||
|
# AD_DNS_SERVER_IP default: auto-detect via getent hosts
|
||||||
|
# PUBLIC_DNS_SERVER default: first NS of prole.org from system resolv OR "ns1.name.com"
|
||||||
|
# DRY_RUN if set to 1, prints actions without changing AD DNS
|
||||||
|
|
||||||
|
PROG="init_prole_dns"
|
||||||
|
STATE_DIR="/var/lib/${PROG}"
|
||||||
|
STATE_FILE="${STATE_DIR}/state.env"
|
||||||
|
CRON_FILE="/etc/cron.d/${PROG}"
|
||||||
|
CONF_DIR="/etc/prole-dns-sync"
|
||||||
|
HOSTS_FILE="${CONF_DIR}/hosts.txt"
|
||||||
|
LOG_TAG="${PROG}"
|
||||||
|
|
||||||
|
AD_DNS_ZONE="${AD_DNS_ZONE:-prole.org}"
|
||||||
|
DRY_RUN="${DRY_RUN:-0}"
|
||||||
|
|
||||||
|
DEFAULT_DB_ENDPOINT="localhost:5432/prole-db"
|
||||||
|
DB_ENDPOINT="$DEFAULT_DB_ENDPOINT"
|
||||||
|
|
||||||
|
mkdir -p "$STATE_DIR" "$CONF_DIR"
|
||||||
|
chmod 700 "$STATE_DIR"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
local msg="$*"
|
||||||
|
logger -t "$LOG_TAG" -- "$msg" 2>/dev/null || true
|
||||||
|
echo "$msg"
|
||||||
|
}
|
||||||
|
|
||||||
|
die() {
|
||||||
|
log "ERROR: $*"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
need_root() {
|
||||||
|
[[ "${EUID:-$(id -u)}" -eq 0 ]] || die "Must run as root."
|
||||||
|
}
|
||||||
|
|
||||||
|
have_cmd() {
|
||||||
|
command -v "$1" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_dc_fqdn() {
|
||||||
|
local fqdn
|
||||||
|
fqdn="$(hostname -f 2>/dev/null || true)"
|
||||||
|
[[ -n "$fqdn" ]] || die "Unable to detect DC FQDN (hostname -f failed)."
|
||||||
|
echo "$fqdn"
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_dc_ip() {
|
||||||
|
local dc_fqdn="$1"
|
||||||
|
local ip
|
||||||
|
ip="$(getent ahostsv4 "$dc_fqdn" 2>/dev/null | awk '{print $1; exit}' || true)"
|
||||||
|
[[ -n "$ip" ]] || die "Unable to detect DC IP for $dc_fqdn (getent ahostsv4 failed)."
|
||||||
|
echo "$ip"
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_public_dns_server() {
|
||||||
|
local ns=""
|
||||||
|
if have_cmd dig; then
|
||||||
|
ns="$(dig +short NS "${AD_DNS_ZONE}" 2>/dev/null | head -n1 | sed 's/\.$//' || true)"
|
||||||
|
fi
|
||||||
|
[[ -n "$ns" ]] || ns="ns1.name.com"
|
||||||
|
echo "$ns"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse DB endpoint of form host:port/dbname
|
||||||
|
parse_db_endpoint() {
|
||||||
|
local ep="$1"
|
||||||
|
local hostport="${ep%%/*}"
|
||||||
|
local dbname="${ep#*/}"
|
||||||
|
local host="${hostport%%:*}"
|
||||||
|
local port="${hostport#*:}"
|
||||||
|
|
||||||
|
if [[ "$hostport" == "$host" ]]; then
|
||||||
|
# no port specified
|
||||||
|
port="5432"
|
||||||
|
fi
|
||||||
|
[[ -n "$host" && -n "$port" && -n "$dbname" ]] || return 1
|
||||||
|
|
||||||
|
echo "$host|$port|$dbname"
|
||||||
|
}
|
||||||
|
|
||||||
|
psql_can_connect() {
|
||||||
|
have_cmd psql || return 1
|
||||||
|
local parsed
|
||||||
|
parsed="$(parse_db_endpoint "$DB_ENDPOINT")" || return 1
|
||||||
|
local host="${parsed%%|*}"; parsed="${parsed#*|}"
|
||||||
|
local port="${parsed%%|*}"; local db="${parsed##*|}"
|
||||||
|
|
||||||
|
# Allow interactive password prompting if needed:
|
||||||
|
# We DO NOT add -w (never prompt). We add a short statement timeout.
|
||||||
|
# If auth requires a password and none is cached, this command will prompt.
|
||||||
|
psql -h "$host" -p "$port" -d "$db" -v ON_ERROR_STOP=1 -At \
|
||||||
|
-c "SET statement_timeout='5s'; SELECT 1;" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Functions to check if a string is an IP address
|
||||||
|
is_ipv4() {
|
||||||
|
[[ $1 =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
is_ipv6() {
|
||||||
|
[[ $1 == *:* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
is_ip() {
|
||||||
|
is_ipv4 "$1" || is_ipv6 "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
read_hosts_file() {
|
||||||
|
[[ -f "$HOSTS_FILE" ]] || die "Missing $HOSTS_FILE and DB not available. Create it with: hostname [TAB/SPACE] ip_or_target"
|
||||||
|
grep -vE '^\s*($|#)' "$HOSTS_FILE" | awk '{print $1 "|" $2}' || true
|
||||||
|
}
|
||||||
|
|
||||||
|
read_hosts_db() {
|
||||||
|
local parsed
|
||||||
|
parsed="$(parse_db_endpoint "$DB_ENDPOINT")" || die "Invalid -db endpoint '$DB_ENDPOINT' (expected host:port/dbname)"
|
||||||
|
local host="${parsed%%|*}"; parsed="${parsed#*|}"
|
||||||
|
local port="${parsed%%|*}"; local db="${parsed##*|}"
|
||||||
|
|
||||||
|
# This will prompt for password if required by libpq/pg_hba.
|
||||||
|
# Output format: host_name|ip_address
|
||||||
|
psql -h "$host" -p "$port" -d "$db" -v ON_ERROR_STOP=1 -At -F '|' \
|
||||||
|
-c "SELECT host_name, ip_address FROM prole.hosts ORDER BY host_name;"
|
||||||
|
}
|
||||||
|
|
||||||
|
normalize_host() {
|
||||||
|
local raw="$1"
|
||||||
|
raw="${raw%%#*}"
|
||||||
|
# Trim whitespace
|
||||||
|
raw="$(echo "$raw" | xargs)"
|
||||||
|
[[ -n "$raw" ]] || return 1
|
||||||
|
|
||||||
|
local fqdn="$raw"
|
||||||
|
# If it's a short name, append the zone
|
||||||
|
if [[ "$raw" != *.* ]]; then
|
||||||
|
fqdn="${raw}.${AD_DNS_ZONE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate it's within our zone
|
||||||
|
if [[ "$fqdn" != *".${AD_DNS_ZONE}" && "$fqdn" != "${AD_DNS_ZONE}" ]]; then
|
||||||
|
# We might be reading a line that has two columns, but normalize_host is called with only the first.
|
||||||
|
# If the user provided a FQDN outside the zone, it's an error for an A/CNAME record name in this script's context.
|
||||||
|
die "Host '$raw' is not within zone '${AD_DNS_ZONE}' (got fqdn '$fqdn')."
|
||||||
|
fi
|
||||||
|
|
||||||
|
local rel="${fqdn%.$AD_DNS_ZONE}"
|
||||||
|
rel="${rel%.}"
|
||||||
|
|
||||||
|
echo "$rel|$fqdn"
|
||||||
|
}
|
||||||
|
|
||||||
|
dig_records() {
|
||||||
|
local dns_server="$1"
|
||||||
|
local fqdn="$2"
|
||||||
|
local a_records aaaa_records
|
||||||
|
|
||||||
|
a_records="$(dig +short @"$dns_server" A "$fqdn" 2>/dev/null | tr '\n' ' ' | xargs || true)"
|
||||||
|
aaaa_records="$(dig +short @"$dns_server" AAAA "$fqdn" 2>/dev/null | tr '\n' ' ' | xargs || true)"
|
||||||
|
echo "$a_records|$aaaa_records"
|
||||||
|
}
|
||||||
|
|
||||||
|
samba_query_rr() {
|
||||||
|
local dc="$1" zone="$2" name="$3" rrtype="$4"
|
||||||
|
samba-tool dns query "$dc" "$zone" "$name" "$rrtype" 2>/dev/null \
|
||||||
|
| awk '
|
||||||
|
BEGIN{inrec=0}
|
||||||
|
/Records:/ {inrec=1; next}
|
||||||
|
inrec && NF>=1 { print $0 }
|
||||||
|
' \
|
||||||
|
| tr '\n' ' ' | xargs || true
|
||||||
|
}
|
||||||
|
|
||||||
|
samba_add_rr() {
|
||||||
|
local dc="$1" zone="$2" name="$3" rrtype="$4" value="$5"
|
||||||
|
if [[ "$DRY_RUN" == "1" ]]; then
|
||||||
|
log "[DRY_RUN] samba-tool dns add $dc $zone $name $rrtype $value"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
samba-tool dns add "$dc" "$zone" "$name" "$rrtype" "$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
samba_update_rr() {
|
||||||
|
local dc="$1" zone="$2" name="$3" rrtype="$4" old="$5" new="$6"
|
||||||
|
if [[ "$DRY_RUN" == "1" ]]; then
|
||||||
|
log "[DRY_RUN] samba-tool dns update $dc $zone $name $rrtype $old $new"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
samba-tool dns update "$dc" "$zone" "$name" "$rrtype" "$old" "$new"
|
||||||
|
}
|
||||||
|
|
||||||
|
sync_rr_set() {
|
||||||
|
local dc="$1" zone="$2" name_rel="$3" fqdn="$4" rrtype="$5" desired_values="$6"
|
||||||
|
[[ -n "$desired_values" ]] || return 0
|
||||||
|
|
||||||
|
local existing
|
||||||
|
existing="$(samba_query_rr "$dc" "$zone" "$name_rel" "$rrtype")"
|
||||||
|
|
||||||
|
for val in $desired_values; do
|
||||||
|
if [[ " $existing " == *" $val "* ]]; then
|
||||||
|
log "OK: $fqdn $rrtype already has $val"
|
||||||
|
else
|
||||||
|
local first_old
|
||||||
|
first_old="$(echo "$existing" | awk '{print $1}' || true)"
|
||||||
|
if [[ -n "$first_old" ]]; then
|
||||||
|
log "UPDATE: $fqdn $rrtype $first_old -> $val"
|
||||||
|
samba_update_rr "$dc" "$zone" "$name_rel" "$rrtype" "$first_old" "$val" || samba_add_rr "$dc" "$zone" "$name_rel" "$rrtype" "$val"
|
||||||
|
else
|
||||||
|
log "ADD: $fqdn $rrtype $val"
|
||||||
|
samba_add_rr "$dc" "$zone" "$name_rel" "$rrtype" "$val"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
write_state() {
|
||||||
|
local key="$1" val="$2"
|
||||||
|
if [[ -f "$STATE_FILE" ]] && grep -qE "^${key}=" "$STATE_FILE"; then
|
||||||
|
sed -i "s|^${key}=.*|${key}=${val}|g" "$STATE_FILE"
|
||||||
|
else
|
||||||
|
echo "${key}=${val}" >> "$STATE_FILE"
|
||||||
|
fi
|
||||||
|
chmod 600 "$STATE_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
load_state() {
|
||||||
|
[[ -f "$STATE_FILE" ]] && source "$STATE_FILE" || true
|
||||||
|
}
|
||||||
|
|
||||||
|
install_cron() {
|
||||||
|
local script_path
|
||||||
|
script_path="$(readlink -f "$0")"
|
||||||
|
cat > "$CRON_FILE" <<EOF
|
||||||
|
# Managed by $PROG. Runs hourly.
|
||||||
|
SHELL=/bin/bash
|
||||||
|
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
0 * * * * root $script_path -db=$DB_ENDPOINT sync >/dev/null 2>&1
|
||||||
|
EOF
|
||||||
|
chmod 644 "$CRON_FILE"
|
||||||
|
log "Installed hourly cron job at $CRON_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_cron() {
|
||||||
|
if [[ -f "$CRON_FILE" ]]; then
|
||||||
|
rm -f "$CRON_FILE"
|
||||||
|
log "Removed cron job $CRON_FILE"
|
||||||
|
else
|
||||||
|
log "Cron job not present ($CRON_FILE)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
choose_host_source() {
|
||||||
|
# Echo "db" if we can connect; otherwise "file"
|
||||||
|
if have_cmd psql; then
|
||||||
|
# We want to allow interactive prompting; connectivity test here should not block forever.
|
||||||
|
# We'll do a *non-interactive* quick check first: if it fails, fall back to file.
|
||||||
|
# (If you want forced DB prompting, run: sudo PSQL_FORCE_PROMPT=1 ... sync)
|
||||||
|
if psql_can_connect; then
|
||||||
|
echo "db"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "file"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_list() {
|
||||||
|
local dc_fqdn dc_ip public_dns source
|
||||||
|
dc_fqdn="${AD_DNS_SERVER:-$(detect_dc_fqdn)}"
|
||||||
|
dc_ip="${AD_DNS_SERVER_IP:-$(detect_dc_ip "$dc_fqdn")}"
|
||||||
|
public_dns="${PUBLIC_DNS_SERVER:-$(detect_public_dns_server)}"
|
||||||
|
source="$(choose_host_source)"
|
||||||
|
|
||||||
|
echo "AD zone : $AD_DNS_ZONE"
|
||||||
|
echo "AD DC (server) : $dc_fqdn"
|
||||||
|
echo "AD DC IP : $dc_ip"
|
||||||
|
echo "Public resolver: $public_dns"
|
||||||
|
echo "DB endpoint : $DB_ENDPOINT"
|
||||||
|
echo "Hosts file : $HOSTS_FILE"
|
||||||
|
echo "Host source : $source"
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "Hostnames:"
|
||||||
|
if [[ "$source" == "db" ]]; then
|
||||||
|
read_hosts_db | while IFS='|' read -r host ip; do
|
||||||
|
[[ -n "${host// }" ]] || continue
|
||||||
|
local norm rel fqdn
|
||||||
|
norm="$(normalize_host "$host")" || continue
|
||||||
|
rel="${norm%%|*}"
|
||||||
|
fqdn="${norm##*|}"
|
||||||
|
if [[ -n "${ip// }" ]]; then
|
||||||
|
if is_ip "$ip"; then
|
||||||
|
echo " - $fqdn (name='$rel', ip=$ip)"
|
||||||
|
else
|
||||||
|
echo " - $fqdn (name='$rel', cname=$ip)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " - $fqdn (name='$rel', ip=<lookup>)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
read_hosts_file | while IFS='|' read -r host ip; do
|
||||||
|
[[ -n "${host// }" ]] || continue
|
||||||
|
local norm rel fqdn
|
||||||
|
norm="$(normalize_host "$host")" || continue
|
||||||
|
rel="${norm%%|*}"
|
||||||
|
fqdn="${norm##*|}"
|
||||||
|
if [[ -n "${ip// }" ]]; then
|
||||||
|
if is_ip "$ip"; then
|
||||||
|
echo " - $fqdn (name='$rel', ip=$ip)"
|
||||||
|
else
|
||||||
|
echo " - $fqdn (name='$rel', cname=$ip)"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " - $fqdn (name='$rel', ip=<lookup>)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_start() {
|
||||||
|
need_root
|
||||||
|
cmd_sync
|
||||||
|
install_cron
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_stop() {
|
||||||
|
need_root
|
||||||
|
remove_cron
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_flush() {
|
||||||
|
need_root
|
||||||
|
rm -f "$STATE_FILE"
|
||||||
|
log "Cleared local state ($STATE_FILE). DNS records were NOT changed."
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_status() {
|
||||||
|
load_state
|
||||||
|
echo "State file: $STATE_FILE"
|
||||||
|
echo " last_run_epoch : ${LAST_RUN_EPOCH:-<unknown>}"
|
||||||
|
if [[ -n "${LAST_RUN_EPOCH:-}" ]]; then
|
||||||
|
date -d "@${LAST_RUN_EPOCH}" +" last_run_time : %F %T %Z"
|
||||||
|
else
|
||||||
|
echo " last_run_time : <unknown>"
|
||||||
|
fi
|
||||||
|
echo " last_exit : ${LAST_EXIT:-<unknown>}"
|
||||||
|
echo " last_source : ${LAST_SOURCE:-<unknown>}"
|
||||||
|
echo " last_public_ns : ${LAST_PUBLIC_DNS:-<unknown>}"
|
||||||
|
echo " last_dc : ${LAST_DC:-<unknown>}"
|
||||||
|
echo " db_endpoint : ${DB_ENDPOINT}"
|
||||||
|
echo
|
||||||
|
echo "Recent logs:"
|
||||||
|
echo "----------------------------------------"
|
||||||
|
journalctl -t "$LOG_TAG" -n 50 --no-pager 2>/dev/null || \
|
||||||
|
echo "(journalctl tag '$LOG_TAG' not available; try: sudo journalctl -n 50 | grep $LOG_TAG)"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_sync() {
|
||||||
|
need_root
|
||||||
|
have_cmd samba-tool || die "samba-tool not found (run this on the Samba AD DC)."
|
||||||
|
have_cmd dig || die "dig not found. Install bind9-dnsutils or dnsutils."
|
||||||
|
|
||||||
|
local dc_fqdn dc_ip public_dns source
|
||||||
|
dc_fqdn="${AD_DNS_SERVER:-$(detect_dc_fqdn)}"
|
||||||
|
dc_ip="${AD_DNS_SERVER_IP:-$(detect_dc_ip "$dc_fqdn")}"
|
||||||
|
public_dns="${PUBLIC_DNS_SERVER:-$(detect_public_dns_server)}"
|
||||||
|
source="$(choose_host_source)"
|
||||||
|
|
||||||
|
write_state "LAST_RUN_EPOCH" "$(date +%s)"
|
||||||
|
write_state "LAST_PUBLIC_DNS" "$public_dns"
|
||||||
|
write_state "LAST_DC" "$dc_fqdn"
|
||||||
|
write_state "LAST_SOURCE" "$source"
|
||||||
|
|
||||||
|
log "Starting sync: zone=${AD_DNS_ZONE}, dc=${dc_fqdn} (${dc_ip}), public_dns=${public_dns}, source=${source}, db=${DB_ENDPOINT}, dry_run=${DRY_RUN}"
|
||||||
|
|
||||||
|
local exit_code=0
|
||||||
|
|
||||||
|
# Helper to process a single host entry
|
||||||
|
process_host() {
|
||||||
|
local host="$1" ip="$2"
|
||||||
|
[[ -n "${host// }" ]] || return 0
|
||||||
|
local norm rel fqdn
|
||||||
|
norm="$(normalize_host "$host")" || return 0
|
||||||
|
rel="${norm%%|*}"
|
||||||
|
fqdn="${norm##*|}"
|
||||||
|
|
||||||
|
# Skip zone apex
|
||||||
|
if [[ "$fqdn" == "$AD_DNS_ZONE" ]]; then
|
||||||
|
log "INFO: Skipping zone apex $fqdn"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local a_vals="" aaaa_vals="" cname_vals=""
|
||||||
|
if [[ -n "${ip// }" ]]; then
|
||||||
|
if is_ip "$ip"; then
|
||||||
|
# If a single IP is provided, classify as v4/v6
|
||||||
|
if is_ipv6 "$ip"; then
|
||||||
|
aaaa_vals="$ip"
|
||||||
|
else
|
||||||
|
a_vals="$ip"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# It's a hostname, use CNAME
|
||||||
|
cname_vals="$ip"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# No IP in entry: resolve from public DNS
|
||||||
|
local recs
|
||||||
|
recs="$(dig_records "$public_dns" "$fqdn")"
|
||||||
|
a_vals="${recs%%|*}"
|
||||||
|
aaaa_vals="${recs##*|}"
|
||||||
|
if [[ -z "$a_vals" && -z "$aaaa_vals" ]]; then
|
||||||
|
log "WARN: No A/AAAA in public DNS for $fqdn (via $public_dns); skipping"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
local ret=0
|
||||||
|
if [[ -n "$a_vals" ]]; then
|
||||||
|
if ! sync_rr_set "$dc_fqdn" "$AD_DNS_ZONE" "$rel" "$fqdn" "A" "$a_vals"; then ret=1; fi
|
||||||
|
fi
|
||||||
|
if [[ -n "$aaaa_vals" ]]; then
|
||||||
|
if ! sync_rr_set "$dc_fqdn" "$AD_DNS_ZONE" "$rel" "$fqdn" "AAAA" "$aaaa_vals"; then ret=1; fi
|
||||||
|
fi
|
||||||
|
if [[ -n "$cname_vals" ]]; then
|
||||||
|
# Samba-tool CNAME values should ideally be FQDN with trailing dot for safety,
|
||||||
|
# but let's see how it handles it. The issue says "samba-tool dns add .. cname"
|
||||||
|
if ! sync_rr_set "$dc_fqdn" "$AD_DNS_ZONE" "$rel" "$fqdn" "CNAME" "$cname_vals"; then ret=1; fi
|
||||||
|
fi
|
||||||
|
return $ret
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "$source" == "db" ]]; then
|
||||||
|
while IFS='|' read -r host ip; do
|
||||||
|
if ! process_host "$host" "$ip"; then exit_code=1; fi
|
||||||
|
done < <(read_hosts_db)
|
||||||
|
else
|
||||||
|
while IFS='|' read -r host ip; do
|
||||||
|
if ! process_host "$host" "$ip"; then exit_code=1; fi
|
||||||
|
done < <(read_hosts_file)
|
||||||
|
fi
|
||||||
|
|
||||||
|
write_state "LAST_EXIT" "$exit_code"
|
||||||
|
if [[ "$exit_code" -eq 0 ]]; then
|
||||||
|
log "Sync complete: success"
|
||||||
|
else
|
||||||
|
log "Sync complete: errors occurred"
|
||||||
|
fi
|
||||||
|
return "$exit_code"
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $0 [-db=host:port/dbname] <command>
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
start Run sync now and install hourly cron job
|
||||||
|
stop Remove hourly cron job
|
||||||
|
status Show last run time, exit status, and recent logs
|
||||||
|
sync Run one sync pass now
|
||||||
|
flush Clear local state file (does not change DNS records)
|
||||||
|
list Show host list + DC info (alias: show, details)
|
||||||
|
show Alias for list
|
||||||
|
details Alias for list
|
||||||
|
|
||||||
|
DB option:
|
||||||
|
-db=HOST:PORT/DBNAME default: ${DEFAULT_DB_ENDPOINT}
|
||||||
|
|
||||||
|
Host list sources:
|
||||||
|
1) If psql exists and DB is reachable:
|
||||||
|
SELECT host_name, ip_address FROM prole.hosts;
|
||||||
|
(psql may prompt for password interactively)
|
||||||
|
2) Else: $HOSTS_FILE
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
sudo $0 sync
|
||||||
|
sudo $0 -db=localhost:5432/prole-db sync
|
||||||
|
sudo DRY_RUN=1 $0 sync
|
||||||
|
sudo $0 start
|
||||||
|
sudo $0 status
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
# Parse -db=... and keep remaining args
|
||||||
|
local args=()
|
||||||
|
for a in "$@"; do
|
||||||
|
case "$a" in
|
||||||
|
-db=*) DB_ENDPOINT="${a#-db=}" ;;
|
||||||
|
*) args+=("$a") ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
local cmd="${args[0]:-}"
|
||||||
|
case "$cmd" in
|
||||||
|
start) cmd_start ;;
|
||||||
|
stop) cmd_stop ;;
|
||||||
|
status) cmd_status ;;
|
||||||
|
sync) cmd_sync ;;
|
||||||
|
flush) cmd_flush ;;
|
||||||
|
list|show|details) cmd_list ;;
|
||||||
|
""|-h|--help|help) usage ;;
|
||||||
|
*) die "Unknown command: $cmd (try --help)" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Loading…
Reference in New Issue
Block a user