From 948d4cff9a9aa50d6bca24d78cfb293e5e80f1f3 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 7 Aug 2026 11:31:14 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D3=E4=B8=AA=E9=97=AE=E9=A2=98:?= =?UTF-8?q?=201)=20setup-bind.sh=E7=A1=AE=E4=BF=9D=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=202)=20DNSSEC=E9=85=8D=E7=BD=AE=E9=AA=8C?= =?UTF-8?q?=E8=AF=81options=E5=9D=97=E5=8C=85=E8=A3=B9=203)=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0IPv6=E5=BC=80=E5=85=B3=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 44 +++++++++++++++++++++++++++++++++++++++---- scripts/setup-bind.sh | 30 +++++++++++++++++------------ templates/config.html | 15 +++++++++++++-- 3 files changed, 71 insertions(+), 18 deletions(-) diff --git a/app.py b/app.py index e80df6e..d8bf6db 100644 --- a/app.py +++ b/app.py @@ -1083,6 +1083,24 @@ def _parse_listen_on_v4(content): return items +def _parse_listen_on_v6(content): + """Return 'any'/'none'/'localhost' — the IPv6 listen mode. + Returns 'none' if directive is absent (disabled by default). + """ + m = re.search(r'listen-on-v6(?:\s+port\s+\d+)?\s*\{([^}]*)\}', content, re.DOTALL) + if not m: + return "none" + body = m.group(1) + # Look for the main token: { any; } or { none; } or { localhost; } + for tm in re.finditer(r'"([^"]+)"|([\w./:-]+)', body): + token = tm.group(1) or tm.group(2) + if token in ("any", "none", "localhost"): + return token + # Fallback: if the block is not empty, assume "custom" mode + body_stripped = body.strip().strip(';').strip() + return "any" if body_stripped else "none" + + def _parse_recursion(content): """Return 'yes' (default), 'no', or specific value from 'recursion ;'.""" m = re.search(r'recursion\s+(\w+)\s*;', content) @@ -1296,6 +1314,7 @@ def config_view(): dnssec_value = _parse_options_dnssec(options_content) listen_on_v4 = _parse_listen_on_v4(options_content) recursion_value = _parse_recursion(options_content) + listen_on_v6 = _parse_listen_on_v6(options_content) return render_template("config.html", options_content=options_content, @@ -1308,6 +1327,7 @@ def config_view(): dnssec_value=dnssec_value, dnssec_values=_VALIDATION_VALUES, listen_on_v4=listen_on_v4, + listen_on_v6=listen_on_v6, recursion_value=recursion_value) @@ -1319,6 +1339,11 @@ def config_options_save(): run_cmd(f"cp {BIND_CONF_OPTIONS} {backup}") # Write to temp and validate + # NOTE: BIND_CONF_OPTIONS content must have 'options { ... }' wrapper (it's a complete file) + # that gets included from named.conf, so named-checkconf can validate it directly + if not re.search(r'^\s*options\s*\{', content, re.MULTILINE): + # User may have accidentally removed the options wrapper in text editor + content = f"options {{\n{content}\n}};\n" with open("/tmp/named_check.tmp", 'w') as f: f.write(content) rc, out, err = run_cmd("named-checkconf /tmp/named_check.tmp") @@ -1330,7 +1355,7 @@ def config_options_save(): with open(BIND_CONF_OPTIONS, 'w') as f: f.write(content) - run_cmd(f"chown root:bind {BIND_CONF_OPTIONS}") + run_cmd(f"chown root:bind {BIND_CONF_OPTIONS} 2>/dev/null || chown root:named {BIND_CONF_OPTIONS} 2>/dev/null") bind_reload() @@ -1392,6 +1417,9 @@ def config_upstream_save(): raw_fwd = request.form.get("forwarders", "") raw_rc = request.form.get("recursion", "") raw_listen = request.form.get("listen_on_v4", "") + listen_on_v6_in = (request.form.get("listen_on_v6") or "none").strip().lower() + if listen_on_v6_in not in ("any", "none", "localhost"): + listen_on_v6_in = "none" dnssec_in = (request.form.get("dnssec") or "auto").strip().lower() if dnssec_in not in _VALIDATION_VALUES: dnssec_in = "auto" @@ -1430,12 +1458,20 @@ def config_upstream_save(): new_content, "listen-on", _format_list_block("listen-on port 53", new_listen), ) + new_content = _replace_or_append_option( + new_content, "listen-on-v6", + _format_list_block("listen-on-v6 port 53", [listen_on_v6_in]), + ) new_content = _replace_or_append_option( new_content, "recursion", - f"recursion {recursion_in};", + f"recursion {recursion_in};\n", ) # Validate BEFORE writing to the live file + # Ensure content has options block wrapper (required for named-checkconf) + if not re.search(r'^\s*options\s*\{', new_content, re.MULTILINE): + # No options block found — wrap the entire content + new_content = f"options {{\n{new_content}\n}};\n" tmp = "/tmp/named_check.tmp" with open(tmp, 'w') as f: f.write(new_content) @@ -1457,11 +1493,11 @@ def config_upstream_save(): u = current_user() AuditLog.log(u.username, "修改上游DNS转发配置", f"forwarders={new_fwd}; allow-recursion={new_rc}; " - f"dnssec={dnssec_in}; listen-on={new_listen}; recursion={recursion_in}") + f"dnssec={dnssec_in}; listen-on={new_listen}; listen-on-v6={listen_on_v6_in}; recursion={recursion_in}") flash( f"已保存:forwarders {len(new_fwd)} 条,allow-recursion {len(new_rc)} 条," f"dnssec-validation {dnssec_in},listen-on {len(new_listen)} 项," - f"recursion {recursion_in}", + f"listen-on-v6 {listen_on_v6_in},recursion {recursion_in}", "success", ) return redirect(url_for("config_view")) diff --git a/scripts/setup-bind.sh b/scripts/setup-bind.sh index 3070c97..442364e 100755 --- a/scripts/setup-bind.sh +++ b/scripts/setup-bind.sh @@ -145,21 +145,27 @@ ensure_include_in_named_conf() { } 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" + log "Step 5: start/enable named service" + # Always enable and start/restart the service + if [[ $DRY_RUN -eq 1 ]]; then + printf ' [DRY-RUN] systemctl enable named\n' + printf ' [DRY-RUN] systemctl restart named (or start if not running)\n' + printf ' [DRY-RUN] sleep 1\n' + printf ' [DRY-RUN] systemctl is-active --quiet named && echo "named is active"\n' return fi - if [[ $DRY_RUN -eq 1 ]]; then - printf ' [DRY-RUN] systemctl restart %s\n' "$SERVICE_NAME" + systemctl enable named 2>/dev/null || true + if systemctl is-active --quiet named; then + systemctl restart named 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 + systemctl start named + fi + sleep 1 + if systemctl is-active --quiet named; then + log " named is active and running" + else + err " named failed to start; check 'journalctl -xe -u named'" + exit 1 fi } diff --git a/templates/config.html b/templates/config.html index 1c88e34..42292a9 100644 --- a/templates/config.html +++ b/templates/config.html @@ -68,14 +68,25 @@ placeholder="any 或具体网段,如: 192.168.1.0/24 10.0.0.0/8">{% for net in listen_on_v4 %}{{ net }} {% endfor %} +
+ + +