Files
Hermes 16448d1666 feat: setup-bind.sh one-shot initialization script
新增 scripts/setup-bind.sh,自动探测 Debian/Ubuntu vs RHEL/CentOS
布局(不靠 OS 名 if-elif,靠 /etc/bind vs /etc/named.conf 文件存在性),
执行 5 步:
  1. 装 bind 包(如未装)
  2. 建 zone 目录、设 bind:named 所有权 + 2770
  3. 建 named.conf.local 空文件、640
  4. 确保 named.conf 包含 named.conf.local(如未包含则追加)
  5. systemctl restart named

幂等:每步都先检查当前状态,已配置项跳过、避免破坏现有内容。

支持 --dry-run 模式打印将执行的命令而不真做,便于先审核。

README '安装 Web 应用' 步骤增加 dry-run + 真跑的两行示例。

测试:dev (Debian 12) --dry-run 通过,正确检测 Debian 布局、跳过
已配置步骤。
2026-07-24 17:47:45 +08:00

183 lines
6.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# setup-bind.sh — initialize BIND for the DNS Web Manager
#
# Detects BIND layout (Debian/Ubuntu vs RHEL/CentOS/Rocky) by checking which
# files exist, then ensures:
# - bind package installed
# - zone directory exists with correct ownership
# - named.conf.local is included from named.conf
# - /etc/named.conf.local (or /etc/bind/named.conf.local) exists & writable
#
# Usage:
# sudo bash scripts/setup-bind.sh # actually run all steps
# sudo bash scripts/setup-bind.sh --dry-run # print what would run, don't execute
#
# Idempotent: safe to run multiple times. Existing configs are preserved;
# named.conf.local is only created if missing.
set -euo pipefail
DRY_RUN=0
if [[ "${1:-}" == "--dry-run" ]]; then
DRY_RUN=1
fi
# ─── helpers ────────────────────────────────────────────────────────────────
log() { printf '\033[1;34m[setup-bind]\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[setup-bind]\033[0m %s\n' "$*" >&2; }
err() { printf '\033[1;31m[setup-bind]\033[0m %s\n' "$*" >&2; }
run() {
if [[ $DRY_RUN -eq 1 ]]; then
printf ' [DRY-RUN] %s\n' "$*"
else
"$@"
fi
}
must_be_root() {
if [[ $EUID -ne 0 ]]; then
err "Must run as root (use sudo)"
exit 1
fi
}
# ─── detect BIND layout ─────────────────────────────────────────────────────
# Logic mirrors app.py's auto-detection: prefer RHEL paths when both exist.
detect_layout() {
if [[ -d /etc/bind ]]; then
# Debian/Ubuntu bind9 — /etc/bind exists
BIND_USER="bind"
BIND_GROUP="bind"
ZONES_DIR="/etc/bind/zones"
NAMED_CONF="/etc/bind/named.conf"
NAMED_CONF_LOCAL="/etc/bind/named.conf.local"
PKG_MANAGER="apt-get"
PKG_INSTALL=(apt-get install -y bind9 bind9utils dnsutils)
SERVICE_NAME="named"
elif [[ -f /etc/named.conf ]]; then
# RHEL/CentOS/Rocky bind — /etc/named.conf exists, /etc/bind usually doesn't
BIND_USER="named"
BIND_GROUP="named"
ZONES_DIR="/var/named"
NAMED_CONF="/etc/named.conf"
NAMED_CONF_LOCAL="/etc/named.conf.local"
PKG_MANAGER="yum"
PKG_INSTALL=(yum install -y bind bind-utils)
SERVICE_NAME="named"
else
# No BIND config found at all — assume Debian (most common for fresh installs)
# and let the package install create the structure.
BIND_USER="bind"
BIND_GROUP="bind"
ZONES_DIR="/etc/bind/zones"
NAMED_CONF="/etc/bind/named.conf"
NAMED_CONF_LOCAL="/etc/bind/named.conf.local"
PKG_MANAGER="apt-get"
PKG_INSTALL=(apt-get install -y bind9 bind9utils dnsutils)
SERVICE_NAME="named"
warn "No BIND config detected; defaulting to Debian/Ubuntu layout"
fi
log "Detected layout:"
log " bind user/group : $BIND_USER:$BIND_GROUP"
log " zones dir : $ZONES_DIR"
log " named.conf : $NAMED_CONF"
log " named.conf.local: $NAMED_CONF_LOCAL"
log " pkg manager : $PKG_MANAGER"
}
# ─── steps ──────────────────────────────────────────────────────────────────
install_bind() {
log "Step 1: ensure BIND package is installed"
if command -v named >/dev/null 2>&1; then
log " named already installed: $(named -v 2>&1 | head -1)"
return
fi
warn " named not found; installing via $PKG_MANAGER"
run "${PKG_INSTALL[@]}"
}
ensure_zones_dir() {
log "Step 2: ensure zones directory exists with correct ownership"
if [[ ! -d "$ZONES_DIR" ]]; then
log " $ZONES_DIR does not exist; creating"
run mkdir -p "$ZONES_DIR"
else
log " $ZONES_DIR already exists"
fi
# group-writable + setgid so future files inherit the group
run chown "$BIND_USER:$BIND_GROUP" "$ZONES_DIR"
run chmod 2770 "$ZONES_DIR"
}
ensure_named_conf_local() {
log "Step 3: ensure named.conf.local exists and is writable by web"
if [[ ! -f "$NAMED_CONF_LOCAL" ]]; then
log " $NAMED_CONF_LOCAL does not exist; creating empty file"
run touch "$NAMED_CONF_LOCAL"
run chown "root:$BIND_GROUP" "$NAMED_CONF_LOCAL"
run chmod 640 "$NAMED_CONF_LOCAL"
else
log " $NAMED_CONF_LOCAL already exists; preserving"
# Don't change ownership if file already has content — it may be managed
# by someone else. Just make sure web process can write to it.
run chmod 644 "$NAMED_CONF_LOCAL"
fi
}
ensure_include_in_named_conf() {
log "Step 4: ensure named.conf includes named.conf.local"
if [[ ! -f "$NAMED_CONF" ]]; then
warn " $NAMED_CONF not found; skipping include check (bind not configured?)"
return
fi
if grep -qF "$NAMED_CONF_LOCAL" "$NAMED_CONF"; then
log " $NAMED_CONF already includes $NAMED_CONF_LOCAL"
return
fi
warn " $NAMED_CONF does NOT include $NAMED_CONF_LOCAL — appending"
if [[ $DRY_RUN -eq 1 ]]; then
printf ' [DRY-RUN] echo "include \\"%s\\";" >> %s\n' "$NAMED_CONF_LOCAL" "$NAMED_CONF"
else
printf '\ninclude "%s";\n' "$NAMED_CONF_LOCAL" >> "$NAMED_CONF"
fi
}
restart_named() {
log "Step 5: restart named so include takes effect"
if ! systemctl is-active --quiet "$SERVICE_NAME"; then
warn " $SERVICE_NAME not running; skipping restart"
return
fi
if [[ $DRY_RUN -eq 1 ]]; then
printf ' [DRY-RUN] systemctl restart %s\n' "$SERVICE_NAME"
else
run systemctl restart "$SERVICE_NAME"
sleep 1
if systemctl is-active --quiet "$SERVICE_NAME"; then
log " $SERVICE_NAME is active"
else
err " $SERVICE_NAME failed to start; check 'journalctl -xe -u $SERVICE_NAME'"
fi
fi
}
# ─── main ───────────────────────────────────────────────────────────────────
main() {
must_be_root
detect_layout
if [[ $DRY_RUN -eq 1 ]]; then
log "DRY-RUN mode: no changes will be made"
fi
install_bind
ensure_zones_dir
ensure_named_conf_local
ensure_include_in_named_conf
restart_named
log "Done. Verify with:"
log " named-checkconf"
log " rndc status | grep 'number of zones'"
}
main "$@"