Initial YakPanel commit

This commit is contained in:
Niranjan
2026-04-07 02:04:22 +05:30
commit 2826d3e7f3
5359 changed files with 1390724 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2014-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: yakpanel
# -------------------------------------------------------------------
# ------------------------------
# xxx app
# ------------------------------

450
class_v2/ssl_dnsV2/api.py Normal file
View File

@@ -0,0 +1,450 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2014-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: yakpanel
# -------------------------------------------------------------------
# ------------------------------
# aaDNS api
# ------------------------------
import json
import sys
import threading
if not "class/" in sys.path:
sys.path.insert(0, "class/")
if not "class_v2/" in sys.path:
sys.path.insert(0, "class_v2/")
from public.exceptions import HintException
from public.validate import Param
from ssl_domainModelV2.model import DnsDomainProvider
from ssl_domainModelV2.service import DomainValid
from .dns_manager import DnsManager, MailManager
from .helper import *
class DnsApiObject:
def __init__(self):
pass
def install_bind(self, get):
log = f"{public.get_panel_path()}/logs/install_bind.log"
if os.path.exists(log):
public.ExecShell(f"rm -f {log}")
install_script = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "bind_script.sh"
)
public.ExecShell("chmod +x {}".format(install_script))
public.ExecShell("nohup bash {} install >>{} 2>&1 &".format(install_script, log))
return public.success_v2(public.lang("Installing..."))
def install_pdns(self, get):
try:
get.validate([
Param("act").String("in", ["install", "uninstall"]).Require(),
Param("clean").Integer(),
], [public.validate.trim_filter(), ])
if not hasattr(get, "clean"):
get.clean = 1
else:
get.clean = int(get.clean)
if get.act == "install" and os.path.exists("/www/server/panel/plugin/syssafe/config.json"):
cf = public.readFile("/www/server/panel/plugin/syssafe/config.json")
if cf:
jcf = None
try:
import json
jcf = json.loads(cf)
except:
pass
if jcf and jcf.get("open") is True:
raise HintException(public.lang(
"[System hardening] is enabled, please disable it first before installing aaDNS."
))
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.fail_v2(str(ex))
log = f"{public.get_panel_path()}/logs/install_pdns.log"
public.writeFile(log, f"Starting {get.act}...\n")
install_script = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "pdns_script.sh"
)
public.ExecShell("chmod +x {}".format(install_script))
public.ExecShell(f"nohup bash {install_script} {get.act} {get.clean} >>{log} 2>&1 &")
if get.act == "install":
public.set_module_logs(
"sys_domain", "Install_YakPanelDns", 1
)
return public.success_v2(public.lang("Success!"))
def get_status(self, get):
config_obj = aaDnsConfig()
service_name = config_obj.service_path.get("service_name")
if not service_name:
public.success_v2({
"service": None,
"status": False
})
a, e = public.ExecShell(f"ps -ef | grep '{service_name}' | grep -v grep")
if e:
raise HintException(f"Failed to get aaDNS service status: {e} please try again.")
return public.success_v2({
"service": config_obj.install_service,
"status": True if a else False
})
def change_status(self, get):
try:
get.validate([
Param("service_name").String("in", ["bind", "pdns"]),
Param("status").String("in", [
"start", "stop", "restart", "reload"
]).Require(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.fail_v2(str(ex))
if not hasattr(get, "service_name"):
service_name = "pdns"
else:
service_name = get.service_name
DnsManager().change_service_status(service_name, get.status)
return public.success_v2(public.lang(f"Successfully {get.status} aaDNS service."))
@staticmethod
def check_base_params(func):
def wrapper(self, get):
check_domain = ["domain", "ns1domain", "ns2domain", "soa"]
soa_params = ["nameserver", "admin_mail"]
for key in check_domain + soa_params:
if hasattr(get, key) and not DomainValid.is_valid_domain(getattr(get, key)):
raise HintException("invalid {}: {}".format(key, getattr(get, key)))
# ip
check_ip = ["ip", "domain_ip"]
for key2 in check_ip:
if hasattr(get, key2):
if not DomainValid.is_ip4(getattr(get, key2)) and not DomainValid.is_ip6(getattr(get, key2)):
raise HintException("invalid ip address: {}".format(getattr(get, key2)))
# ttl, priority
check_int_type = ["ttl", "priority"]
soa_int_params = ["serial", "refresh", "retry", "expire", "minimum"]
for key3 in check_int_type + soa_int_params:
if hasattr(get, key3):
try:
int(getattr(get, key3))
except:
raise HintException("{} must be an digit.".format(key3))
return func(self, get)
return wrapper
@staticmethod
def init_provider(func):
def wrapper(self, get):
provider = DnsDomainProvider.objects.filter(name="YakPanelDns").first()
if not provider:
raise HintException(public.lang(
"YakPanelDns provider not found. Please install aaDNS first."
))
return func(self, get, provider)
return wrapper
@check_base_params
def add_zone(self, get):
"""添加zone信息"""
try:
get.validate([
Param("domain").String().Require(),
Param("ns1domain").String(),
Param("ns2domain").String(),
Param("soa").String(),
Param("domain_ip").String(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.fail_v2(str(ex))
manager = DnsManager()
domain = get.domain.rstrip(".") # 去点
soa = "ns1.{}.".format(domain)
ip = "127.0.0.1"
ns1 = "ns1.{}.".format(domain)
ns2 = "ns2.{}.".format(domain)
if hasattr(get, "ns1") and get.ns1:
ns1 = get.ns1
if hasattr(get, "ns2") and get.ns2:
ns2 = get.ns2
if hasattr(get, "soa") and get.soa:
soa = get.soa
if hasattr(get, "domain_ip") and get.domain_ip:
ip = get.domain_ip
# 确保FQDN格式
for k in [ns1, ns2, soa]:
if not k.endswith("."):
k += "."
res = manager.add_zone(domain, ns1, ns2, soa, ip)
if isinstance(res, str):
return public.success_v2(res)
return public.success_v2(public.lang("Successfully added zone."))
@check_base_params
def del_zone(self, get):
try:
get.validate([
Param("domain").String().Require(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.fail_v2(str(ex))
res = DnsManager().delete_zone(get.domain)
if isinstance(res, str):
return public.success_v2(res)
return public.success_v2(public.lang("Successfully deleted zone."))
@check_base_params
def get_zones(self, get):
"""获取所有已添加的域名列表"""
try:
get.validate([
Param("domain").String(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.fail_v2(str(ex))
domains = DnsParser().get_zones(get.domain)
return public.success_v2(domains)
@check_base_params
def get_nameserver(self, get):
return public.success_v2(DnsManager().get_default_nameserver())
@check_base_params
def set_nameserver(self, get):
try:
get.validate([
Param("ns1domain").String().Require(),
Param("ns2domain").String().Require(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.fail_v2(str(ex))
manager = DnsManager()
manager.set_default_nameserver(get.ns1domain, get.ns2domain)
return public.success_v2(public.lang("Successfully set nameserver."))
@check_base_params
def get_soa(self, get):
try:
get.validate([
Param("domain").String().Require(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.fail_v2(str(ex))
soa = DnsManager().get_soa(get.domain)
return public.success_v2(soa)
@check_base_params
def set_soa(self, get):
try:
get.validate([
Param("domain").String().Require(),
Param("nameserver").String().Require(),
Param("admin_mail").String().Require(),
Param("serial").Integer().Require(),
Param("refresh").Integer("between", [1200, 43200]).Require(),
Param("retry").Integer("between", [120, 7200]).Require(),
Param("expire").Integer("between", [1209600, 2419200]).Require(),
Param("minimum").Integer("between", [180, 86400]).Require(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.fail_v2(str(ex))
for k, v in get.__dict__.items():
if k in ["domain", "nameserver", "admin_mail"]:
if v.endswith("."):
setattr(get, k, v.rstrip("."))
DnsManager().set_soa(**get.__dict__)
return public.success_v2(public.lang("Successfully set SOA record."))
def get_logger(self, get):
try:
get.validate([
Param("p").Integer(),
Param("limit").Integer(),
Param("search").String(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.fail_v2(str(ex))
if not hasattr(get, "p"):
get.p = 1
if not hasattr(get, "limit"):
get.limit = 20
if hasattr(get, "search") and get.search:
search = str(get.search)
else:
search = None
return public.success_v2(
DnsManager().get_logger(int(get.p), int(get.limit), search)
)
def clear_logger(self, get):
if DnsManager().clear_logger():
return public.success_v2(public.lang("Successfully cleared logs."))
return public.fail_v2(public.lang("Failed to clear logs."))
@init_provider
def add_dmarc(self, get, provider: DnsDomainProvider):
try:
get.validate([
Param("policy").String("in", ["none", "quarantine", "reject"]).Require(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.fail_v2(str(ex))
mail_manager = MailManager()
fails = []
for d in provider.domains:
try:
setattr(mail_manager, "domain", d)
mail_manager.add_dmarc(policy=get.policy, provider=provider)
except Exception as ex:
public.print_log("error info: {}".format(ex))
fails.append(f"domain: {d} error: {ex}")
continue
if fails:
return public.fail_v2(", ".join(fails))
return public.success_v2(public.lang("Successfully added DMARC record."))
@init_provider
def add_dkim_spf(self, get, provider: DnsDomainProvider):
fails = []
for d in provider.domains:
try:
MailManager(d).add_spf(provider)
MailManager(d).add_dkim(provider)
except Exception as ex:
public.print_log("error info: {}".format(ex))
fails.append(f"domain: {d} error: {ex}")
continue
if fails:
return public.fail_v2(", ".join(fails))
return public.success_v2(public.lang("Successfully added DKIM/SPF records."))
@init_provider
def dns_checker(self, get, provider: DnsDomainProvider):
try:
get.validate([
Param("act").String("in", ["start", "status"]).Require(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.fail_v2(str(ex))
status = 1 if os.path.exists(DNS_AUTH_LOCK) else 0
if get.act == "status":
if status:
msg = public.lang("DNS Checker is Running. Please Wait.")
else:
msg = public.lang("DNS Checker is Suspend.")
elif get.act == "start":
if status:
msg = public.lang("DNS Checker is Already Running. Please Wait.")
else:
task = threading.Thread(
target=DnsManager().builtin_dns_checker, args=(provider,)
)
task.start()
status = 1
msg = public.lang("DNS Checker Run Successfully.")
else:
raise HintException(public.lang("Invalid action."))
return public.success_v2({
"checker_status": status,
"msg": msg
})
@check_base_params
@init_provider
def fix_zone(self, get, provider: DnsDomainProvider):
manager = DnsManager()
try:
for i in provider.domains:
manager.fix_zone(i)
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.fail_v2(str(ex))
return public.success_v2(public.lang("Successfully fixed all zones."))
@check_base_params
@init_provider
def set_ttl_batch(self, get, provider: DnsDomainProvider):
try:
get.validate([
Param("ttl").String().Require(),
Param("domains").String().Require(),
Param("record_type").String().Require(),
], [
public.validate.trim_filter(),
])
get.domains = json.loads(get.domains)
if not get.record_type:
raise HintException(public.lang("Record type is required."))
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.fail_v2(str(ex))
fails = []
manager = DnsManager()
for d in get.domains:
if d not in provider.domains:
fails.append(f"domain: {d} error: not found in provider domains.")
continue
try:
if not manager.domian_record_type_ttl_batch_set(
domain=d, record_type=get.record_type, ttl=get.ttl
):
fails.append(f"domain: {d} error: failed to set ttl.")
except Exception as ex:
public.print_log("error info: {}".format(ex))
fails.append(f"domain: {d} error: {ex}")
continue
if fails:
return public.fail_v2(", ".join(fails))
return public.success_v2(public.lang("Successfully set TTL for all domains."))

View File

@@ -0,0 +1,170 @@
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
Install_Bind()
{
if command -v yum >/dev/null 2>&1; then
# CentOS/RHEL
echo "Detected yum, installing BIND..."
yum install bind bind-chroot -y
# chroot 环境配置
mkdir -p /var/named/chroot/etc
mkdir -p /var/named/chroot/var/named/data
mkdir -p /var/named/chroot/var/named/dynamic
if [ ! -f /var/named/chroot/etc/named.rfc1912.zones ];then
if [ -d '/usr/share/doc/bind/sample/var/named/' ];then
cp -R /usr/share/doc/bind/sample/var/named/* /var/named/chroot/var/named/
else
cp -R /usr/share/doc/bind-*/sample/var/named/* /var/named/chroot/var/named/
fi
# 复制所有配置文件
echo "Configuring BIND for the first time..."
cp -p /etc/named.* /var/named/chroot/etc/
touch /var/named/chroot/var/named/data/cache_dump.db
touch /var/named/chroot/var/named/data/named_stats.txt
touch /var/named/chroot/var/named/data/named_mem_stats.txt
touch /var/named/chroot/var/named/data/named.run
touch /var/named/chroot/var/named/dynamic/managed-keys.bind
# 修改默认值
NAMED_CONF_PATH="/var/named/chroot/etc/named.conf"
sed -i 's/listen-on port 53 .*/listen-on port 53 { any; };/' "$NAMED_CONF_PATH"
sed -i 's/allow-query .*/allow-query { any; };/' "$NAMED_CONF_PATH"
sed -i 's/recursion yes;/recursion no;/' "$NAMED_CONF_PATH"
fi
# 设置权限
chown -R named:named /var/named/chroot
SERVICE_NAME="named"
if systemctl list-unit-files | grep -q "named-chroot.service"; then
SERVICE_NAME="named-chroot"
fi
systemctl disable pdns >/dev/null 2>&1
systemctl stop pdns >/dev/null 2>&1
systemctl restart "$SERVICE_NAME"
systemctl enable "$SERVICE_NAME"
echo "bind" > /www/server/panel/class_v2/ssl_dnsV2/aadns.pl
echo "BIND installed and configured for CentOS/RHEL."
echo "Installed Success!"
elif command -v apt-get >/dev/null 2>&1; then
# Debian/Ubuntu
echo "Detected apt, installing BIND9..."
apt-get update
apt-get install bind9 bind9utils -y
systemctl stop bind9
# 创建用户组
if ! getent group named >/dev/null; then groupadd named; fi
if ! id -u named >/dev/null 2>&1; then useradd -g named -s /sbin/nologin -d /var/named named; fi
# chroot 环境配置
CHROOT_DIR="/var/named/chroot"
mkdir -p "$CHROOT_DIR/etc"
mkdir -p "$CHROOT_DIR/dev"
mkdir -p "$CHROOT_DIR/var/named/data"
mkdir -p "$CHROOT_DIR/var/named/dynamic"
mkdir -p "$CHROOT_DIR/var/cache/bind"
mkdir -p "$CHROOT_DIR/var/run/named"
# 移动配置文件并创建符号链接
if [ -d /etc/bind ] && [ ! -L /etc/bind ]; then
mv /etc/bind "$CHROOT_DIR/etc/"
ln -s "$CHROOT_DIR/etc/bind" /etc/bind
fi
# 以chroot模式启动
if [ -f /etc/default/named ]; then
# -u: user, -t: chroot directory
sed -i "s/OPTIONS=.*/OPTIONS=\"-u named -t \/var\/named\/chroot\"/" /etc/default/named
fi
# chroot所需的设备文件
if [ ! -c "$CHROOT_DIR/dev/null" ]; then mknod "$CHROOT_DIR/dev/null" c 1 3; fi
if [ ! -c "$CHROOT_DIR/dev/random" ]; then mknod "$CHROOT_DIR/dev/random" c 1 8; fi
chmod 666 "$CHROOT_DIR/dev/null" "$CHROOT_DIR/dev/random"
echo "\$AddUnixListenSocket $CHROOT_DIR/dev/log" > /etc/rsyslog.d/bind-chroot.conf
systemctl restart rsyslog
# 首次配置时修改配置
NAMED_CONF_OPTIONS_PATH="$CHROOT_DIR/etc/bind/named.conf.options"
if ! grep -q "listen-on { any; };" "$NAMED_CONF_OPTIONS_PATH"; then
echo "Configuring BIND options for the first time..."
# sed -i '/^\s*listen-on\s/c\listen-on { any; };' "$NAMED_CONF_OPTIONS_PATH"
# sed -i '/^\s*listen-on-v6\s/c\listen-on-v6 { none; };' "$NAMED_CONF_OPTIONS_PATH"
# sed -i '/^\s*allow-query\s/c\allow-query { any; };' "$NAMED_CONF_OPTIONS_PATH"
# sed -i '/^\s*recursion\s/c\recursion no;' "$NAMED_CONF_OPTIONS_PATH"
cat > "$NAMED_CONF_OPTIONS_PATH" <<EOF
options {
directory "/var/cache/bind";
listen-on { any; };
listen-on-v6 { none; };
allow-query { any; };
recursion no;
pid-file "/var/run/named/named.pid";
dnssec-validation auto;
auth-nxdomain no; # conform to RFC1035
};
EOF
fi
# 确保权限
chown -R named:named "$CHROOT_DIR"
# 处理 AppArmor
if [ -d /etc/apparmor.d/ ]; then
if [ ! -L /etc/apparmor.d/disable/usr.sbin.named ]; then
echo "Disabling AppArmor for named to ensure chroot works correctly."
ln -sf /etc/apparmor.d/usr.sbin.named /etc/apparmor.d/disable/
apparmor_parser -R /etc/apparmor.d/usr.sbin.named
fi
fi
systemctl disable pdns >/dev/null 2>&1
systemctl stop pdns >/dev/null 2>&1
systemctl daemon-reload
systemctl restart bind9
systemctl enable bind9
echo "bind" > /www/server/panel/class_v2/ssl_dnsV2/aadns.pl
echo "BIND9 with chroot installed and configured for Debian/Ubuntu."
echo "Installed Success!"
else
echo "Error: Neither yum nor apt-get found. Cannot install BIND."
exit 1
fi
}
Install_aaDns()
{
Install_Bind
}
Uninstall_aaDns()
{
if command -v yum >/dev/null 2>&1; then
SERVICE_NAME="named"
if systemctl list-unit-files | grep -q "named-chroot.service"; then
SERVICE_NAME="named-chroot"
fi
systemctl stop "$SERVICE_NAME"
systemctl disable "$SERVICE_NAME"
echo "BIND uninstalled from CentOS/RHEL."
elif command -v apt-get >/dev/null 2>&1; then
systemctl stop bind9
systemctl disable bind9
echo "BIND9 uninstalled from Debian/Ubuntu."
else
echo "Error: Neither yum nor apt-get found. Cannot uninstall BIND."
exit 1
fi
}
if [ "${1}" == "install" ];then
Install_aaDns
fi

137
class_v2/ssl_dnsV2/conf.py Normal file
View File

@@ -0,0 +1,137 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2014-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: yakpanel
# -------------------------------------------------------------------
# aaDNS config
# ------------------------------
import json
import os
import re
import sys
if not "class_v2" in sys.path:
sys.path.append("class_v2")
__all__ = [
"aaDnsConfig",
"zone_pattern",
"file_pattern",
"record_pattern",
"ZONES_DIR",
"ZONES",
"APP_DIR",
"SERVICE_INSTALL_NAME",
"PUBLIC_SERVER",
"DNS_AUTH_LOCK",
"aaDNS_CONF",
]
zone_pattern = re.compile(r'zone\s+"[^"]+"\s*(?:IN)?\s*\{[\s\S]*?};', re.MULTILINE)
file_pattern = re.compile(r'file\s+"([^"]+)"')
record_pattern = re.compile(r'^(\S+)\s+(?:(\d+)\s+)?(?:(IN)\s+)?(\S+)\s+(.*)$')
ZONES_DIR = "/var/named/chroot/var/named/"
ZONES = "/var/named/chroot/etc/named.rfc1912.zones"
APP_DIR = os.path.dirname(os.path.abspath(__file__))
SERVICE_INSTALL_NAME = f"{APP_DIR}/aadns.pl"
aaDNS_CONF = os.path.join(APP_DIR, "aaDns_conf.json")
DNS_AUTH_LOCK = f"{APP_DIR}/dns_auth.pl"
PUBLIC_SERVER = [
("Google", ["8.8.8.8"]),
("Cloudflare", ["1.1.1.1"]),
("Quad9", ["9.9.9.9"]),
("OpenDNS", ["208.67.222.222"]),
("DNS.Watch", ["84.200.69.80"]),
("Comodo Secure DNS", ["8.26.56.26"]),
("AdGuard DNS", ["94.140.14.14"]),
("CleanBrowsing", ["185.228.168.9"]),
("Neustar DNS", ["207.177.68.4"]),
("Freenom World", ["83.145.86.7"]),
]
class aaDnsConfig:
if os.path.exists("/etc/redhat-release"):
os_type = "redhat"
package = "yum"
else:
os_type = "ubuntu"
package = "apt"
def __init__(self):
self.install_service = None
self.ns_server = None
self.bind_service_name = "named"
self.pnds_service_name = "pdns"
self._init_env()
def _init_env(self):
# RHEL/CentOS检查 bind-chroot 服务具体名称
if os.path.exists("/usr/lib/systemd/system/named-chroot.service"):
self.bind_service_name = "named-chroot"
if os.path.exists(SERVICE_INSTALL_NAME):
with open(SERVICE_INSTALL_NAME, "r") as f:
self.install_service = f.read().strip()
if self.install_service not in ["bind", "pdns"]:
try:
os.remove(SERVICE_INSTALL_NAME)
except:
pass
self.install_service = None
if os.path.exists(aaDNS_CONF):
try:
with open(aaDNS_CONF, "r") as f:
content = f.read().strip()
self.ns_server = json.loads(content) if content else None
except:
pass
@property
def pdns_paths(self):
if self.os_type == "ubuntu": # debian
return {
"config": "/etc/powerdns/pdns.conf",
"zones": ZONES,
"zone_dir": ZONES_DIR,
"service_name": self.pnds_service_name,
"package_name": "pdns-server",
}
else: # redhat, centos
return {
"config": "/etc/pdns/pdns.conf",
"zones": ZONES,
"zone_dir": ZONES_DIR,
"service_name": self.pnds_service_name,
"package_name": "bind-chroot" if self.os_type == "redhat" else "bind9",
"main": "/var/named/chroot/etc/named.conf",
}
@property
def bind_paths(self):
return {
"config": "/var/named/chroot/etc/named.conf",
"zones": "/var/named/chroot/etc/named.conf.local",
"zone_dir": ZONES_DIR,
"service_name": self.bind_service_name,
"package_name": "bind-chroot" if self.os_type == "redhat" else "bind9",
"main": "/var/named/chroot/etc/named.conf",
}
@property
def service_path(self):
if self.install_service == "bind":
return self.bind_paths
elif self.install_service == "pdns":
return self.pdns_paths
else:
return {}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,260 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2014-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: yakpanel
# -------------------------------------------------------------------
import os
import re
from typing import Optional
import public
from .conf import *
class DnsParser:
def __init__(self):
self.config = aaDnsConfig()
# ======================= bind ===============================
def _parser_bind_config(self, conf: str) -> dict:
"""解析bind主配置"""
if not conf:
return {}
conf = re.sub(r'//.*', '', conf)
conf = re.sub(r'#.*', '', conf)
conf = re.sub(r'/\*[\s\S]*?\*/', '', conf)
# 匹配 directory, listen-on, listen-on-v6, allow-query
pattern = re.compile(
r'\s*(directory|listen-on|listen-on-v6|allow-query)\s+(\{[\s\S]*?}|"[^"]*"|[^;]+?)\s*;',
re.MULTILINE
)
matches = pattern.findall(conf)
main_config = dict()
for key, value in matches:
# 清理值,去除多余的空格和引号
cleaned_value = value.strip()
if cleaned_value.startswith('"') and cleaned_value.endswith('"'):
cleaned_value = cleaned_value[1:-1]
elif cleaned_value.startswith('{') and cleaned_value.endswith('}'):
# 对于块值,进一步清理内部内容
cleaned_value = cleaned_value[1:-1].strip()
cleaned_value = re.sub(r'\s+', ' ', cleaned_value)
# 如果键已存在,则将值附加到列表中
if key in main_config:
if isinstance(main_config[key], list):
main_config[key].append(cleaned_value)
else:
main_config[key] = [main_config[key], cleaned_value]
else:
main_config[key] = cleaned_value
return main_config
# ======================= pdns ================================
def _parser_pdns_config(self, conf: str):
"""解析pdns主配置"""
if not conf:
return {}
main_config = {}
for line in conf.splitlines():
line = line.strip()
if line and not line.startswith("#"):
parts = line.split("=", 1)
if len(parts) == 2:
key = parts[0].strip()
value = parts[1].strip()
main_config[key] = value
return main_config
# ======================= public method =======================
@staticmethod
def ttl_parse(value: str) -> Optional[int]:
try:
return int(value.split()[1])
except (ValueError, IndexError):
return None
@staticmethod
def soa_parse(value: str) -> dict:
try:
value = re.sub(r';.*', '', value)
soa_parts = value.split()
if len(soa_parts) >= 7:
parsed_value = {
"nameserver": soa_parts[0],
"admin_mail": soa_parts[1],
"serial": int(soa_parts[2]),
"refresh": int(soa_parts[3]),
"retry": int(soa_parts[4]),
"expire": int(soa_parts[5]),
"minimum": int(soa_parts[6])
}
return parsed_value
except (ValueError, IndexError):
return {}
return {}
@staticmethod
def handle_multiline_soa(lines: list, i: int, current_line: str) -> tuple[str, int]:
"""处理多行SOA记录"""
soa_lines = [current_line.replace("(", " ")]
while i < len(lines):
next_line = lines[i].strip()
i += 1
if not next_line:
continue
next_line = next_line.split(';', 1)[0].strip() # 移除注释
if not next_line:
continue
soa_lines.append(next_line)
if ")" in next_line:
break
line = " ".join(soa_lines).replace(")", " ")
return line, i
def _parse_record(self, line: str, default_ttl: Optional[int]) -> Optional[dict]:
"""解析单条DNS记录"""
match = record_pattern.match(line)
if not match:
return None
name, ttl, r_class, r_type, value = match.groups()
if r_type.upper() == "TXT":
value = value.strip()
if not (value.startswith('"') and value.endswith('"')):
# 对于没有引号的 TXT 记录或其它记录,移除注释
value = value.split(';', 1)[0].strip()
else:
# 对于非 TXT 记录,保持原有的注释移除逻辑
value = value.split(';', 1)[0].strip()
record = {
"name": name,
"ttl": int(ttl) if ttl is not None else default_ttl,
"class": r_class or "IN",
"type": r_type,
"value": value,
}
if r_type.upper() == "SOA": # 解析特殊字段
record.update(self.soa_parse(value))
elif r_type.upper() == "MX":
try:
priority, mx_value = value.split(None, 1)
record["priority"] = int(priority)
record["value"] = mx_value
except (ValueError, IndexError):
pass
elif r_type.upper() == "SRV":
try:
priority, weight, port, target = value.split(None, 3)
record["priority"] = int(priority)
record["weight"] = int(weight)
record["port"] = int(port)
record["value"] = target
except (ValueError, IndexError):
pass
return record
def parser_zone_record(self, zone_file: str, witSOA: bool = False):
"""解析zone记录"""
zone_content = public.readFile(zone_file) or ""
if not zone_content:
return
default_ttl = None
lines = zone_content.splitlines()
i = 0
while i < len(lines):
line = lines[i].strip()
i += 1
if not line or line.startswith(";"):
continue
if line.startswith("$TTL"):
default_ttl = self.ttl_parse(line)
continue
try:
if "SOA" in line and line.rstrip().endswith("("):
line, i = self.handle_multiline_soa(lines, i, line)
except Exception as e:
public.print_log("Error handling multiline SOA: {}".format(e))
continue
record = self._parse_record(line, default_ttl)
if not record:
continue
if not witSOA and record.get("type") == "SOA":
continue
elif witSOA and record.get("type") == "SOA":
yield record
return
yield record
def get_config(self, service_name: str = None) -> dict:
""""获取服务的所有配置, 默认获取当前安装的服务配置"""
config = {}
service = service_name or self.config.install_service
if service == "bind":
nick_name = "bind"
paths = self.config.bind_paths
parser = self._parser_bind_config
elif service == "pdns":
nick_name = "pdns"
paths = self.config.pdns_paths
parser = self._parser_pdns_config
else:
return {}
conf_path = paths["config"]
if not os.path.exists(conf_path):
return {}
config["service_name"] = nick_name
config["config"] = parser(public.readFile(conf_path))
return config
def get_zones(self, domain: str = None) -> list:
"""获取域名列表"""
path = self.config.pdns_paths["zones"]
if not os.path.exists(path):
return []
zones_content = public.readFile(path) or ""
zone_matches = zone_pattern.findall(zones_content)
domains = []
for match in zone_matches:
zone_declaration = match.strip()
domain_match = re.search(r'zone\s+"([^"]+)"', zone_declaration)
if domain_match:
if domain and domain != domain_match.group(1):
continue
domains.append(domain_match.group(1))
return domains
def get_zones_records(self, domain: str = None, witSOA: bool = False) -> list:
"""获取domain zones信息记录列表"""
for root, dirs, files in os.walk(self.config.pdns_paths["zone_dir"]):
files.sort()
for file in files:
try:
if file.startswith("db.") or file.endswith(".zone"):
temp_domain = re.sub(r'^(db\.|zone\.)', '', str(file))
temp_domain = re.sub(r'\.(db|zone)$', '', temp_domain)
if temp_domain != domain:
continue
zone_file_path = os.path.join(root, file)
res = list(
self.parser_zone_record(
zone_file=str(zone_file_path), witSOA=witSOA
)
)
return res
except Exception as e:
public.print_log("Error parsing zone file {}: {}".format(file, e))
continue
return []

View File

@@ -0,0 +1,42 @@
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2014-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: yakpanel
# -------------------------------------------------------------------
# ------------------------------
# dns app
# ------------------------------
from public.aaModel import *
class DnsResolve(aaModel):
id = IntField(primary_key=True)
domain = StrField(default="", ps="domain")
ns_resolve = IntField(default=0, ps="NS")
a_resolve = IntField(default=0, ps="A")
tips = StrField(default="", ps="tips")
create_time = DateTimeStrField(auto_now_add=True, ps="创建时间")
update_time = DateTimeStrField(auto_now=True, ps="更新时间")
@classmethod
def update_or_create(cls, domain: str, **kwargs) -> "DnsResolve":
obj = cls.objects.filter(domain=domain).first()
if obj:
for k, v in kwargs.items():
setattr(obj, k, v)
obj.save()
return obj
else:
res = cls(domain=domain, **kwargs).save()
return res
class _Meta:
table_name = "dns_domain_resolve"
index = [
"domain"
]

View File

@@ -0,0 +1,170 @@
#!/bin/bash
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
public_file=/www/server/panel/install/public.sh
if [ ! -f $public_file ];then
wget -O $public_file https://node.yakpanel.com/install/public.sh -T 30;
fi
. $public_file
download_Url=$NODE_URL
check_and_disable_resolved()
{
if systemctl is-active --quiet systemd-resolved; then
echo "Found active systemd-resolved. Stopping and disabling it..."
systemctl stop systemd-resolved
systemctl disable systemd-resolved
echo "systemd-resolved has been stopped and disabled."
elif systemctl list-units --type=service --all | grep -q 'systemd-resolved.service'; then
echo "Found inactive systemd-resolved. Disabling it..."
systemctl disable systemd-resolved
echo "systemd-resolved has been disabled."
else
echo "systemd-resolved service not found, no action needed."
fi
# 删软链
if [ -L /etc/resolv.conf ]; then
echo "Removing symlink /etc/resolv.conf"
rm -f /etc/resolv.conf
echo "Set namerser 8.8.8.8 to /etc/resolv.conf"
echo "nameserver 8.8.8.8" > /etc/resolv.conf
fi
# 确保上游dns
if ! grep -q "nameserver 8.8.8.8" /etc/resolv.conf 2>/dev/null; then
echo "Adding 8.8.8.8 to /etc/resolv.conf"
echo "nameserver 8.8.8.8" >> /etc/resolv.conf
fi
}
Install_Powerdns_Redhat()
{
yum install pdns -y
groupadd named
useradd -g named -s /sbin/nologin named
mv /etc/pdns/pdns.conf /etc/pdns/pdns.conf_bt
wget -O /etc/pdns/pdns.conf $download_Url/install/plugin/dns_manager/pdns.conf -T 30
chmod 644 /etc/pdns/pdns.conf
mkdir -p /var/named/chroot/etc
mkdir -p /var/named/chroot/var/named
chmod 755 /var/named
chmod 755 /var/named/chroot
chmod 755 /var/named/chroot/etc
chmod 755 /var/named/chroot/var
chmod 755 /var/named/chroot/var/named
chmod -R 644 /var/named/chroot/etc/*
chmod -R 644 /var/named/chroot/var/named/*
if [ ! -f "/var/named/chroot/etc/named.rfc1912.zones" ];then
touch /var/named/chroot/etc/named.rfc1912.zones
fi
bind_conf=$(grep 'file "/var/' /var/named/chroot/etc/named.rfc1912.zones)
if [ "$bind_conf" == "" ];then
sed -i 's/file\s*\"/file \"\/var\/named\/chroot\/var\/named\//g' /var/named/chroot/etc/named.rfc1912.zones
fi
check_and_disable_resolved
systemctl stop named-chroot
systemctl disable named-chroot
systemctl enable pdns
systemctl restart pdns
echo "Configured for Rehat/Centos."
}
Install_Powerdns_Ubuntu()
{
curl https://repo.powerdns.com/FD380FBB-pub.asc | sudo apt-key add -
apt-get update -y
apt-get install pdns-server -y
groupadd named
useradd -g named -s /sbin/nologin named
mv /etc/powerdns/pdns.conf /etc/powerdns/pdns.conf_bt
wget -O /etc/powerdns/pdns.conf $download_Url/install/plugin/dns_manager/pdns.conf -T 30
chmod 644 /etc/powerdns/pdns.conf
mkdir -p /var/named/chroot/etc
mkdir -p /var/named/chroot/var/named
if [ ! -f "/var/named/chroot/etc/named.rfc1912.zones" ];then
touch /var/named/chroot/etc/named.rfc1912.zones
fi
check_and_disable_resolved
systemctl enable pdns
systemctl restart pdns
echo "Configured for Debian/Ubuntu."
}
Install_Powerdns()
{
if [ -f '/usr/bin/yum' ];then
Install_Powerdns_Redhat
else
Install_Powerdns_Ubuntu
fi
echo -n "pdns" > /www/server/panel/class_v2/ssl_dnsV2/aadns.pl
}
Install_DnsManager()
{
echo "Installing..."
Install_Powerdns
/usr/bin/btpip install dnspython
grep "English" /www/server/panel/config/config.json
sleep 3
echo "YakPanelDns Service Success!"
}
update_DnsManager()
{
if [ ! -f "/usr/sbin/pdns_server" ];then
echo "Installing ..."
Install_Powerdns
fi
grep "English" /www/server/panel/config/config.json
echo "The installation is complete"
}
Uninstall_DnsManager()
{
echo "Uninstalling..."
clean=${1}
if [ "$clean" == 1 ];then
echo "Cleaning up YakPanelDns config data..."
rm -f /var/named/chroot/etc/named.rfc1912.zones_bak
mv /var/named/chroot/etc/named.rfc1912.zones /var/named/chroot/etc/named.rfc1912.zones_bak
echo "Remove config: named.rfc1912.zones"
cp /var/named/chroot/var/named/*_aadef /var/named/chroot/var/named_bak/
cp /var/named/chroot/var/named/*zone /var/named/chroot/var/named_bak/
echo "Remove zone config files"
rm -rf /var/named/chroot/var/named
else
echo "Backing up YakPanelDns config data..."
cp /var/named/chroot/etc/named.rfc1912.zones /var/named/chroot/etc/named.rfc1912.zones_aabak
echo "Backup config: named.rfc1912.zones"
mkdir -p /var/named/chroot/var/named_bak/
echo "Backup zone config files"
cp /var/named/chroot/var/named/*_aadef /var/named/chroot/var/named_bak/
cp /var/named/chroot/var/named/*zone /var/named/chroot/var/named_bak/
fi
rm -f /www/server/panel/class_v2/ssl_dnsV2/aadns.pl
rm -f /www/server/panel/class_v2/ssl_dnsV2/aaDns_conf.json
/usr/bin/systemctl stop named-chroot
systemctl disable named-chroot
systemctl stop pdns
systemctl disable pdns
sleep 3
echo "YakPanelDns Service Success!"
}
if [ "${1}" == 'install' ];then
Install_DnsManager
elif [ "${1}" == 'update' ];then
update_DnsManager
elif [ "${1}" == 'uninstall' ];then
Uninstall_DnsManager ${2}
fi