Initial commit: 50 shell-script generators with web UI
- 8 categories / 50 generators covering middleware, databases, runtimes, systemd services, system tools, network, monitoring, security - VNC supports XFCE / GNOME / KDE Plasma / MATE / LXQt desktops - Node.js versions 18-26 (incl. current LTS 24 Krypton and current 26) - Live preview, copy / download / multi-script bundle in web UI - Distro-aware (Ubuntu/Debian/CentOS/RHEL/Rocky/Alma/Fedora) - All 50 generators pass bash -n syntax check - Zero pip dependencies (Flask stdlib only)
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
"""
|
||||
Network tools.
|
||||
- Wireguard VPN
|
||||
- OpenVPN
|
||||
- Tailscale client
|
||||
- DHCP server (isc-dhcp-server / kea)
|
||||
- DNS forwarder (unbound / dnsmasq)
|
||||
"""
|
||||
from . import register, Generator, Field
|
||||
from . import bash_header, quote, bool_str, yes
|
||||
|
||||
|
||||
# ========================== WireGuard ==========================
|
||||
class WireGuard(Generator):
|
||||
id = "wireguard"
|
||||
title = "WireGuard VPN"
|
||||
category = "network"
|
||||
icon = "🔒"
|
||||
tags = ["wireguard", "vpn", "wg"]
|
||||
description = "部署 WireGuard VPN server,可生成客户端配置。"
|
||||
fields = [
|
||||
Field("server_private_key", "Server Private Key", "password", default="",
|
||||
help="留空将自动生成 (使用 wg genkey)。"),
|
||||
Field("server_public_key", "Server Public Key", "text", default="",
|
||||
help="留空将自动从 private key 派生。"),
|
||||
Field("listen_port", "Listen 端口", "number", default="51820", min_=1, max_=65535),
|
||||
Field("interface", "网卡接口", "text", default="eth0"),
|
||||
Field("vpn_subnet", "VPN 子网 CIDR", "text", default="10.0.0.0/24"),
|
||||
Field("server_vpn_ip", "Server VPN IP", "text", default="10.0.0.1/24"),
|
||||
Field("peers", "客户端 Peer 列表", "textarea", default="client1\nclient2",
|
||||
help="每行一个 client name,会为每个生成密钥对。"),
|
||||
Field("peer_dns", "客户端 DNS", "text", default="1.1.1.1, 8.8.8.8"),
|
||||
]
|
||||
|
||||
def render(self, p):
|
||||
sk = p.get("server_private_key", "").strip()
|
||||
pk = p.get("server_public_key", "").strip()
|
||||
port = str(p.get("listen_port", "51820"))
|
||||
iface = p.get("interface", "eth0")
|
||||
subnet = p.get("vpn_subnet", "10.0.0.0/24")
|
||||
sip = p.get("server_vpn_ip", "10.0.0.1/24")
|
||||
peers = [p_.strip() for p_ in p.get("peers", "").splitlines() if p_.strip()]
|
||||
dns = p.get("peer_dns", "1.1.1.1, 8.8.8.8")
|
||||
out = [bash_header(self.title)]
|
||||
out.append('log "Installing WireGuard..."')
|
||||
out.append('$PKG_INSTALL wireguard qrencode')
|
||||
out.append('')
|
||||
# server keypair
|
||||
if not sk:
|
||||
out.append('wg genkey | tee /etc/wireguard/server.key | wg pubkey > /etc/wireguard/server.pub')
|
||||
else:
|
||||
out.append('mkdir -p /etc/wireguard && echo "' + sk + '" > /etc/wireguard/server.key')
|
||||
out.append('wg pubkey < /etc/wireguard/server.key > /etc/wireguard/server.pub')
|
||||
if pk:
|
||||
out.append('echo "' + pk + '" > /etc/wireguard/server.pub')
|
||||
out.append('chmod 600 /etc/wireguard/server.key')
|
||||
out.append('')
|
||||
# generate peer configs
|
||||
out.append('mkdir -p /etc/wireguard/clients')
|
||||
for i, name in enumerate(peers, start=2):
|
||||
ip = f'10.0.0.{i}/32'
|
||||
out.append('wg genkey | tee /etc/wireguard/clients/' + name + '.key | wg pubkey > /etc/wireguard/clients/' + name + '.pub')
|
||||
out.append('chmod 600 /etc/wireguard/clients/' + name + '.key')
|
||||
# server conf
|
||||
out.append('cat > /etc/wireguard/wg0.conf <<WG_EOF\n'
|
||||
'[Interface]\n'
|
||||
'Address = ' + sip + '\n'
|
||||
'ListenPort = ' + port + '\n'
|
||||
'PrivateKey = ' + (sk if sk else '$(cat /etc/wireguard/server.key)') + '\n'
|
||||
'PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o ' + iface + ' -j MASQUERADE\n'
|
||||
'PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o ' + iface + ' -j MASQUERADE\n\n')
|
||||
for i, name in enumerate(peers, start=2):
|
||||
out.append('[Peer]\n'
|
||||
f'# {name}\n'
|
||||
f'PublicKey = $(cat /etc/wireguard/clients/{name}.pub)\n'
|
||||
f'AllowedIPs = 10.0.0.{i}/32\n\n')
|
||||
out.append('WG_EOF')
|
||||
out.append('chmod 600 /etc/wireguard/wg0.conf')
|
||||
out.append('echo "net.ipv4.ip_forward=1" >> /etc/sysctl.d/99-wireguard.conf && sysctl -p /etc/sysctl.d/99-wireguard.conf')
|
||||
out.append('systemctl enable --now wg-quick@wg0')
|
||||
# build client confs
|
||||
for i, name in enumerate(peers, start=2):
|
||||
ip = f'10.0.0.{i}/32'
|
||||
out.append('SERVER_PUB=$(cat /etc/wireguard/server.pub)')
|
||||
out.append('CLIENT_PRIV=$(cat /etc/wireguard/clients/' + name + '.key)')
|
||||
out.append('cat > /etc/wireguard/clients/' + name + '.conf <<CLI_EOF\n'
|
||||
'[Interface]\n'
|
||||
'Address = ' + ip + '\n'
|
||||
'DNS = ' + dns + '\n'
|
||||
'PrivateKey = ${CLIENT_PRIV}\n\n'
|
||||
'[Peer]\n'
|
||||
'PublicKey = ${SERVER_PUB}\n'
|
||||
'Endpoint = YOUR_SERVER_IP:' + port + '\n'
|
||||
'AllowedIPs = 0.0.0.0/0, ::/0\n'
|
||||
'PersistentKeepalive = 25\n'
|
||||
'CLI_EOF')
|
||||
out.append('qrencode -t ansiutf8 < /etc/wireguard/clients/' + name + '.conf || true')
|
||||
return "\n".join(out) + "\n"
|
||||
|
||||
|
||||
# ========================== Tailscale ==========================
|
||||
class Tailscale(Generator):
|
||||
id = "tailscale"
|
||||
title = "Tailscale 客户端"
|
||||
category = "network"
|
||||
icon = "🦀"
|
||||
tags = ["tailscale", "vpn", "mesh"]
|
||||
description = "安装并启动 Tailscale,可指定自定义控制服务器。"
|
||||
fields = [
|
||||
Field("login_server", "控制服务器", "text", default="https://controlplane.tailscale.com",
|
||||
help="Headscale 自建时填入,如 https://hs.example.com。"),
|
||||
Field("accept_routes", "接受子网路由", "checkbox", default="no"),
|
||||
Field("advertise_exit", "作为 Exit Node", "checkbox", default="no"),
|
||||
Field("auth_key", "Auth Key (Headscale/preauth)", "password", default="",
|
||||
help="留空则用 sudo tailscale up 手动登录。"),
|
||||
Field("hostname", "Hostname", "text", default="$(hostname)"),
|
||||
]
|
||||
|
||||
def render(self, p):
|
||||
ls = p.get("login_server", "https://controlplane.tailscale.com")
|
||||
ar = bool_str(p.get("accept_routes"))
|
||||
ex = bool_str(p.get("advertise_exit"))
|
||||
ak = p.get("auth_key", "").strip()
|
||||
hn = p.get("hostname", "$(hostname)")
|
||||
out = [bash_header(self.title)]
|
||||
out.append('log "Installing Tailscale..."')
|
||||
out.append('curl -fsSL https://tailscale.com/install.sh | sh')
|
||||
out.append('systemctl enable --now tailscaled')
|
||||
out.append('sleep 2')
|
||||
flags = []
|
||||
if ls:
|
||||
flags.append('--login-server=' + ls)
|
||||
if ar:
|
||||
flags.append('--accept-routes')
|
||||
if ex:
|
||||
flags.append('--advertise-exit-node')
|
||||
if hn:
|
||||
flags.append('--hostname=' + hn)
|
||||
flag_str = " ".join(flags)
|
||||
if ak:
|
||||
out.append('tailscale up ' + flag_str + ' --authkey=' + ak)
|
||||
else:
|
||||
out.append('tailscale up ' + flag_str)
|
||||
out.append('log "Visit the URL printed above to authenticate."')
|
||||
out.append('tailscale status')
|
||||
return "\n".join(out) + "\n"
|
||||
|
||||
|
||||
# ========================== DNSMASQ ==========================
|
||||
class Dnsmasq(Generator):
|
||||
id = "dnsmasq"
|
||||
title = "Dnsmasq (DHCP + DNS 缓存)"
|
||||
category = "network"
|
||||
icon = "📡"
|
||||
tags = ["dns", "dhcp", "dnsmasq"]
|
||||
description = "轻量级 DNS 转发 + DHCP server。"
|
||||
fields = [
|
||||
Field("enable_dhcp", "启用 DHCP", "checkbox", default="yes"),
|
||||
Field("dhcp_range_start", "DHCP 起始 IP", "text", default="192.168.1.100"),
|
||||
Field("dhcp_range_end", "DHCP 结束 IP", "text", default="192.168.1.200"),
|
||||
Field("dhcp_lease", "租约时间", "text", default="12h"),
|
||||
Field("dhcp_gateway", "DHCP 网关", "text", default="192.168.1.1"),
|
||||
Field("dhcp_dns", "DHCP 推送的 DNS", "text", default="1.1.1.1, 8.8.8.8"),
|
||||
Field("upstream_dns", "上游 DNS", "textarea", default="1.1.1.1\n8.8.8.8"),
|
||||
Field("local_domain", "本地域名", "text", default="lan"),
|
||||
Field("listen_address", "Listen IP", "text", default="0.0.0.0"),
|
||||
]
|
||||
|
||||
def render(self, p):
|
||||
ed = bool_str(p.get("enable_dhcp", True))
|
||||
rstart = p.get("dhcp_range_start", "192.168.1.100")
|
||||
rend = p.get("dhcp_range_end", "192.168.1.200")
|
||||
lease = p.get("dhcp_lease", "12h")
|
||||
gw = p.get("dhcp_gateway", "192.168.1.1")
|
||||
dns = p.get("dhcp_dns", "1.1.1.1, 8.8.8.8")
|
||||
up = [u.strip() for u in p.get("upstream_dns", "").splitlines() if u.strip()]
|
||||
domain = p.get("local_domain", "lan")
|
||||
listen = p.get("listen_address", "0.0.0.0")
|
||||
out = [bash_header(self.title)]
|
||||
out.append('log "Installing dnsmasq..."')
|
||||
out.append('$PKG_INSTALL dnsmasq')
|
||||
out.append('cp /etc/dnsmasq.conf /etc/dnsmasq.conf.bak.$(date +%s) || true')
|
||||
cfg = []
|
||||
cfg.append('listen-address=' + listen)
|
||||
cfg.append('bind-interfaces')
|
||||
cfg.append('domain=' + domain)
|
||||
cfg.append('local=/' + domain + '/')
|
||||
cfg.append('cache-size=1000')
|
||||
for u in up:
|
||||
cfg.append('server=' + u)
|
||||
if ed:
|
||||
cfg.append('dhcp-range=' + rstart + ',' + rend + ',' + lease)
|
||||
cfg.append('dhcp-option=3,' + gw)
|
||||
cfg.append('dhcp-option=6,' + dns)
|
||||
out.append('cat > /etc/dnsmasq.conf <<CFG_EOF\n' + '\n'.join(cfg) + '\nCFG_EOF')
|
||||
out.append('systemctl enable --now dnsmasq')
|
||||
out.append('ss -ltnup | grep :53 || warn "port 53 not listening yet"')
|
||||
return "\n".join(out) + "\n"
|
||||
|
||||
|
||||
# ========================== OpenVPN ==========================
|
||||
class OpenVPN(Generator):
|
||||
id = "openvpn"
|
||||
title = "OpenVPN 简化部署",
|
||||
category = "network"
|
||||
icon = "🛡️"
|
||||
tags = ["openvpn", "vpn"]
|
||||
description = "安装 openvpn + easy-rsa,生成服务端 CA/密钥/客户端配置。"
|
||||
warnings = ["生产环境请先生成正式 PKI,本脚本为快速原型。"]
|
||||
fields = [
|
||||
Field("proto", "协议", "select", default="udp", options=["udp", "tcp"]),
|
||||
Field("port", "端口", "number", default="1194", min_=1, max_=65535),
|
||||
Field("subnet", "VPN 子网", "text", default="10.8.0.0/24"),
|
||||
Field("dns_servers", "推送的 DNS", "text", default="1.1.1.1, 8.8.8.8"),
|
||||
Field("cipher", "加密算法", "select", default="AES-256-GCM",
|
||||
options=["AES-256-GCM", "AES-256-CBC", "CHACHA20-POLY1305"]),
|
||||
Field("clients", "生成客户端数", "number", default="1", min_=1, max_=20),
|
||||
]
|
||||
|
||||
def render(self, p):
|
||||
proto = p.get("proto", "udp")
|
||||
port = str(p.get("port", "1194"))
|
||||
subnet = p.get("subnet", "10.8.0.0/24")
|
||||
dns = p.get("dns_servers", "1.1.1.1, 8.8.8.8")
|
||||
cipher = p.get("cipher", "AES-256-GCM")
|
||||
n = int(p.get("clients", "1"))
|
||||
out = [bash_header(self.title)]
|
||||
out.append('log "Installing openvpn + easy-rsa..."')
|
||||
out.append('$PKG_INSTALL openvpn easy-rsa')
|
||||
out.append('make-cadir /etc/openvpn/easy-rsa')
|
||||
out.append('cd /etc/openvpn/easy-rsa')
|
||||
out.append('./easyrsa init-pki')
|
||||
out.append('./easyrsa build-ca nopass')
|
||||
out.append('./easyrsa gen-req server nopass')
|
||||
out.append('./easyrsa sign-req server server <<< "yes"')
|
||||
out.append('./easyrsa gen-dh')
|
||||
out.append('openvpn --genkey secret /etc/openvpn/ta.key')
|
||||
out.append('cp pki/ca.crt pki/private/server.key pki/issued/server.crt pki/dh.pem /etc/openvpn/')
|
||||
out.append('cp /etc/openvpn/easy-rsa/ta.key /etc/openvpn/')
|
||||
out.append('')
|
||||
out.append('cat > /etc/openvpn/server.conf <<CFG_EOF\n'
|
||||
'port ' + port + '\n'
|
||||
'proto ' + proto + '\n'
|
||||
'dev tun\n'
|
||||
'ca ca.crt\ncert server.crt\nkey server.key\ndh dh.pem\n'
|
||||
'auth SHA256\ncipher ' + cipher + '\n'
|
||||
'tls-auth ta.key 0\n'
|
||||
'topology subnet\n'
|
||||
'server ' + subnet + '\n'
|
||||
'push "redirect-gateway def1 bypass-dhcp"\n'
|
||||
'push "dhcp-option DNS ' + dns.split(',')[0].strip() + '"\n'
|
||||
'keepalive 10 120\npersist-key\npersist-tun\nstatus openvpn-status.log\nverb 3\n'
|
||||
'CFG_EOF')
|
||||
out.append('systemctl enable --now openvpn@server')
|
||||
out.append('echo "net.ipv4.ip_forward=1" >> /etc/sysctl.d/99-openvpn.conf && sysctl -p')
|
||||
for i in range(1, n + 1):
|
||||
cn = f'client{i}'
|
||||
out.append('cd /etc/openvpn/easy-rsa && ./easyrsa gen-req ' + cn + ' nopass')
|
||||
out.append('cd /etc/openvpn/easy-rsa && ./easyrsa sign-req client ' + cn + ' <<< "yes"')
|
||||
out.append('cat > /etc/openvpn/' + cn + '.ovpn <<OVPN_EOF\n'
|
||||
'client\ndev tun\nproto ' + proto + '\n'
|
||||
'remote YOUR_SERVER_IP ' + port + '\n'
|
||||
'resolv-retry infinite\nnobind\n'
|
||||
'persist-key\npersist-tun\nremote-cert-tls server\nauth SHA256\ncipher ' + cipher + '\n'
|
||||
'verb 3\n<ca>\n$(cat /etc/openvpn/ca.crt)\n</ca>\n<cert>\n$(cat /etc/openvpn/easy-rsa/pki/issued/' + cn + '.crt)\n</cert>\n<key>\n$(cat /etc/openvpn/easy-rsa/pki/private/' + cn + '.key)\n</key>\n<tls-auth>\n$(cat /etc/openvpn/ta.key)\n</tls-auth>\nkey-direction 1\nOVPN_EOF')
|
||||
return "\n".join(out) + "\n"
|
||||
|
||||
|
||||
for _g in [WireGuard, Tailscale, Dnsmasq, OpenVPN]:
|
||||
register(_g())
|
||||
Reference in New Issue
Block a user