Hermes 1614e37386 feat: structured upstream DNS / allow-recursion editor in Web UI
新增配置管理页面 '上游 DNS 转发' 卡片,让管理员通过两个 textarea
分别维护 forwarders 和 allow-recursion,不需要手写 BIND 语法:

  - 每行一个 IP / CIDR
  - 支持 # 和 // 注释
  - 留空 = 清空该字段(forwarders 留空表示走根提示;
    allow-recursion 留空表示只有 localhost/localnets 能递归)

实现要点:

1. _parse_options_lists() 从 options 文件文本里抽出 forwarders /
   allow-recursion 列表(用大括号平衡匹配,不依赖单行格式)。
2. _replace_or_append_option() 在 options block 里精准替换已有 directive,
   自动跟随原缩进;找不到则按 4 空格缩进追加到 'options {' 之后;
   options block 本身不存在则新建一个。
3. _ensure_options_file() 自动创建缺失的 options 文件(带合理的最小
   默认值),避免 'No such file' 错误。
4. config_upstream_save() 写文件前用 named-checkconf 验证语法,
   失败则不写入;成功则备份 .bak + 写文件 + rndc reload。

附带的修复:config_view 进入页面时也调用 _ensure_options_file(),
解决了 'Web 端第一次配置 options 文件不存在' 的问题。

模板 templates/config.html 新增 '上游 DNS 转发' 卡片,放在 named.conf
主配置卡片和 named.conf.options 卡片之间,placeholder 给出常用示例
(223.5.5.5 / 114.114.114.114 / RFC1918 三段)。

回归测试(dev 环境):
- _parse_options_lists: 正确从样例 options 抽出 3 forwarders + 2 recursion
- _replace_or_append_option: 替换后缩进正确,保留其它 directives
- round-trip:表单输入 -> 写文件 -> 再解析 -> 一致
2026-07-24 17:58:56 +08:00

DNS Web Manager

基于 BIND9 的 DNS 服务器 Web 管理界面,使用 Flask + SQLite 构建。

功能概览

功能 说明
仪表盘 BIND 服务状态、域名/记录统计、服务控制(启动/停止/重启/重载)
域名管理 创建/删除 Zone(支持 master/slave)、查看 Zone 详情、编辑原始 zone 文件
记录管理 添加/删除 DNS 记录(A、AAAA、CNAME、MX、TXT、NS、PTR、SRV、CAA
DNS 查询 在线 dig 测试,支持所有记录类型
配置管理 在线编辑 named.conf.optionsnamed.conf.local,带语法校验
操作日志 全部操作的审计日志,支持分页
用户认证 登录/登出/修改密码

截图

仪表盘

  • BIND 服务状态卡片(运行状态、开机自启)
  • 域名数量、记录总数统计
  • 服务控制按钮(启动/停止/重启/重载)
  • 域名概览表格
  • 最近操作日志

域名管理

  • 域名列表(域名、类型、Zone 文件、记录数)
  • 创建域名表单(域名、类型、NS 服务器、管理员邮箱)
  • Zone 详情页:SOA 信息、记录列表、添加记录表单、原始文件编辑

DNS 查询

  • 输入域名 + 记录类型 + DNS 服务器,执行 dig 查询
  • 显示完整 dig 输出

技术栈

  • DNS 服务器: BIND 9.18+named
  • Web 框架: Flask 3.0
  • 数据库: SQLite(用户认证 + 审计日志)
  • 前端: Jinja2 模板 + 原生 CSS(无前端框架依赖)
  • WSGI 服务器: Gunicorn

目录结构

dns-service/
├── app.py                  # Flask 应用主文件(路由、模型、BIND 操作)
├── wsgi.py                 # Gunicorn 入口
├── init_db.py              # 数据库初始化脚本
├── requirements.txt        # Python 依赖
├── README.md
├── templates/              # Jinja2 模板
│   ├── base.html           # 布局模板(导航栏 + 页脚)
│   ├── login.html          # 登录页
│   ├── dashboard.html      # 仪表盘
│   ├── zones.html          # 域名列表
│   ├── zone_form.html      # 创建域名表单
│   ├── zone_detail.html    # Zone 详情 + 记录管理
│   ├── zone_raw.html       # 原始 Zone 文件编辑
│   ├── query.html          # DNS 查询测试
│   ├── config.html         # BIND 配置管理
│   ├── logs.html           # 操作日志
│   ├── change_password.html
│   └── error.html          # 错误页
├── static/
│   └── style.css           # 全部样式
└── instance/               # SQLite 数据库(运行时生成)
    └── dns_web.db

BIND 配置文件

文件 用途 默认路径(Debian/Ubuntu
named.conf 主配置 /etc/bind/named.conf
named.conf.options 全局选项(监听端口、转发器、查询权限) /etc/bind/named.conf.options
named.conf.local Zone 声明(由 Web UI 自动管理) /etc/bind/named.conf.local
zone files DNS 记录文件 /etc/bind/zones/db.*

路径自动探测:服务启动时按常见路径顺序探测实际存在的目录/文件,覆盖 Debian/Ubuntu、RHEL/CentOS/Rocky、AlmaLinux、FreeBSD 等发行版默认布局。如需强制指定,可用环境变量覆盖(见"路径优先级")。

安装部署

1. 安装 BIND9

apt-get update
apt-get install -y bind9 bind9utils dnsutils

2. 配置 BIND

不同发行版默认路径、默认 include 的文件都不同,下面分别给出全新机器的初始化步骤。如果机器上已经有 BIND 在跑,只看对应小节的"追加 include"步骤即可。

核心要点Web UI 写入 zone 声明的文件(named.conf.local必须被 named 主配置 include,否则 zone 文件存在但 named 不会加载。

Debian / Ubuntubind9

默认路径:/etc/bind/named.conf/etc/bind/named.conf.options/etc/bind/named.conf.local/etc/bind/zones/

apt-get update
apt-get install -y bind9 bind9utils dnsutils

# 创建 zone 文件目录(项目代码默认就写这里)
mkdir -p /etc/bind/zones
chown bind:bind /etc/bind/zones

# named.conf.options(监听 53 端口)
cat > /etc/bind/named.conf.options << 'EOF'
options {
    directory "/var/cache/bind";
    listen-on port 53 { any; };
    listen-on-v6 { none; };
    forwarders { 8.8.8.8; 8.8.4.4; };
    allow-query { any; };
    allow-recursion { 127.0.0.0/8; 10.168.1.0/24; };
    dnssec-validation auto;
    auth-nxdomain no;
};
EOF

# named.confbind9 包默认就是这套,确认一下即可)
grep -q '/etc/bind/named.conf.local' /etc/bind/named.conf || \
  echo 'include "/etc/bind/named.conf.local";' >> /etc/bind/named.conf

# named.conf.local(空,由 Web UI 自动填充;首次部署建一个空文件)
touch /etc/bind/named.conf.local
chown bind:bind /etc/bind/named.conf.local

systemctl enable named
systemctl restart named

RHEL / CentOS 7 / Rocky Linux / AlmaLinux

默认路径:/etc/named.conf/var/named//etc/named.conf.local(默认不创建)、/etc/rndc.key

yum install -y bind bind-utils

# /var/named 默认就有;如果没建过,手动建
mkdir -p /var/named
chown root:named /var/named
chmod 2770 /var/named

# 关键:RHEL 的 /etc/named.conf 默认不 include /etc/named.conf.local
# 必须手动追加,否则 Web UI 写的 zone 声明不会生效
grep -q '/etc/named.conf.local' /etc/named.conf || \
  echo 'include "/etc/named.conf.local";' >> /etc/named.conf

# 空白入口文件,让 named 重启时不报"include 文件不存在"
touch /etc/named.conf.local
chown root:named /etc/named.conf.local
chmod 640 /etc/named.conf.local

systemctl enable named
systemctl restart named

3. 安装 Web 应用

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

# 创建数据库目录
mkdir -p instance

# 初始化数据库(创建默认 admin/admin 账号)
python3 init_db.py

# 启动(开发模式)
python3 app.py

4. 生产部署(Gunicorn + Systemd

# 创建 systemd 服务
cat > /etc/systemd/system/dns-web.service << 'EOF'
[Unit]
Description=DNS Web Manager
After=network.target named.service

[Service]
Type=notify
User=root
WorkingDirectory=/root/dns-service
ExecStart=/usr/local/bin/gunicorn --workers 2 --bind 0.0.0.0:5300 wsgi:app
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable dns-web
systemctl start dns-web

5. 路径优先级

服务启动时按下面的顺序确定每个 BIND 路径:

  1. 环境变量(最高优先级,用于强制指定非默认路径)
  2. 自动探测(按常见路径顺序,找到第一个存在的目录/文件)
  3. 内置默认值(都没探测到时使用,主要为 Debian/Ubuntu

自动探测覆盖以下发行版默认布局:

变量 探测顺序
BIND_CONF_DIR /etc/bind/etc/etc/named
BIND_ZONES_DIR /etc/bind/zones/var/named/var/named/data
BIND_CONF_LOCAL /etc/named.conf.local/etc/bind/named.conf.local
BIND_CONF_OPTIONS /etc/named.conf/etc/bind/named.conf.options
BIND_RNDC_KEY /etc/bind/rndc.key/etc/rndc.key/var/named/key
BIND_SERVICE 固定 named

BIND_CONF_LOCAL / BIND_CONF_OPTIONS 探测顺序说明RHEL/CentOS 的 /etc/named.conf 默认 include 的是 /etc/named.conf.localDebian/Ubuntu 默认 include 的是 /etc/bind/named.conf.local。代码里优先探测 RHEL 路径——这样可以避免"两个文件都存在但 named 只读其中一个"的歧义场景(典型踩坑:zone 文件写出来了,但 named 不加载)。

大多数情况下无需任何配置——Debian 上自然走 /etc/bind/*CentOS/RHEL 上自然走 /var/named + /etc/named.conf*

只有以下两种场景需要用环境变量强制覆盖:

  • BIND 装在非标准路径(比如容器里 mount 到 /opt/bind
  • 同机多实例 / 测试场景,需要指向特定路径

覆盖示例(systemd):

cat > /etc/systemd/system/dns-web.service << 'EOF'
[Unit]
Description=DNS Web Manager
After=network.target named.service

[Service]
Type=notify
User=root
WorkingDirectory=/root/dns-service
Environment=BIND_ZONES_DIR=/opt/bind/zones
ExecStart=/usr/local/bin/gunicorn --workers 2 --bind 0.0.0.0:5300 wsgi:app
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl restart dns-web

注意:覆盖 BIND_CONF_LOCAL 后,必须让 named 主配置 include 这个文件。CentOS 7 的 /etc/named.conf 默认不会 include,需要按上面"配置 BIND / RHEL"一节里的命令追加 include "/etc/named.conf.local";

使用说明

登录

  • 访问 http://<服务器IP>:5300
  • 默认账号: admin / 密码: admin
  • 首次登录后请修改密码

创建域名

  1. 进入「域名管理」→「+ 添加域名」
  2. 填写域名(如 example.com)、选择类型(master/slave
  3. 可选填写 NS 服务器和管理员邮箱(留空则自动生成)
  4. 点击「创建域名」

添加 DNS 记录

  1. 进入域名详情页
  2. 点击「+ 添加记录」
  3. 填写记录名称、类型、TTL、数据
  4. 点击「添加」

记录类型说明:

类型 数据格式 示例
A IPv4 地址 10.168.1.100
AAAA IPv6 地址 2001:db8::1
CNAME 别名(FQDN,以 . 结尾) www.example.com.
MX 优先级 + 邮件服务器(FQDN 10 mail.example.com.
TXT 文本内容 "v=spf1 ~all"
NS NS 服务器(FQDN ns1.example.com.
PTR 反向解析目标(FQDN host.example.com.
SRV 优先级 权重 端口 目标 10 5 5060 sip.example.com.

DNS 查询测试

  1. 进入「DNS 查询」页面
  2. 输入域名和记录类型
  3. DNS 服务器留空默认查询本地 BIND127.0.0.1:53
  4. 点击「查询」查看结果

配置管理

  1. 进入「配置管理」页面
  2. 可在线编辑 named.conf.options(监听端口、转发器等)
  3. 可在线编辑 named.conf.localZone 声明)
  4. 保存时自动执行 named-checkconf 校验,通过后自动 rndc reload

服务控制

在「仪表盘」页面可控制 BIND 服务:

  • 启动 - systemctl start named
  • 停止 - systemctl stop named
  • 重启 - systemctl restart named
  • 重载配置 - rndc reload(不中断服务,热加载 zone 变更)

注意事项

  1. 权限要求: Web 应用需要 root 权限运行(用于操作 BIND 配置文件和 systemctl 命令)
  2. 端口冲突: 如果 53 端口被其他服务占用(如 dnsmasq),需先停掉对应服务
  3. Zone 文件权限: zone 文件需要 bind:bind 所有权,Web UI 自动设置
  4. Serial 自动递增: 每次添加/删除记录时,SOA Serial 自动递增(YYYYMMDDNN 格式)
  5. 配置备份: 编辑配置文件时自动创建 .bak 备份

API 接口

方法 路径 说明
GET /api/status 获取 BIND 服务状态(需登录)

License

MIT

S
Description
No description provided
Readme 230 KiB
2026-07-24 18:20:19 +08:00
Languages
Python 51.6%
HTML 30.6%
CSS 11.5%
Shell 6.3%