From 16448d1666a273ed7694c44316a195e5844840cc Mon Sep 17 00:00:00 2001 From: Hermes Date: Fri, 24 Jul 2026 17:47:45 +0800 Subject: [PATCH] feat: setup-bind.sh one-shot initialization script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 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 布局、跳过 已配置步骤。 --- README.md | 11 +++ scripts/setup-bind.sh | 183 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100755 scripts/setup-bind.sh diff --git a/README.md b/README.md index 4cf315e..454e78b 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,17 @@ systemctl restart named git clone ssh://git@git.cnbugs.com:10022/AI-Agent/dns-service.git cd dns-service +# 一键初始化 BIND(自动识别 Debian/Ubuntu vs RHEL/CentOS): +# - 装 bind 包 +# - 建 zone 目录、设权限 +# - 建 named.conf.local、确保被主配置 include +# - 重启 named +# 默认是干跑模式,先看清楚会做什么: +sudo bash scripts/setup-bind.sh --dry-run + +# 确认无误后真跑: +sudo bash scripts/setup-bind.sh + # 安装 Python 依赖 pip install -r requirements.txt diff --git a/scripts/setup-bind.sh b/scripts/setup-bind.sh new file mode 100755 index 0000000..3070c97 --- /dev/null +++ b/scripts/setup-bind.sh @@ -0,0 +1,183 @@ +#!/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 "$@" \ No newline at end of file