This commit is contained in:
user123
2025-12-19 00:02:52 +08:00
parent 76ce16fbb2
commit c6a1ecf5b6
3 changed files with 957 additions and 0 deletions

3
l2tp/go.mod Normal file
View File

@@ -0,0 +1,3 @@
module l2tp
go 1.25.1

BIN
l2tp/l2tp Normal file

Binary file not shown.

954
l2tp/l2tp.go Normal file
View File

@@ -0,0 +1,954 @@
package main
import (
"bufio"
"context"
"crypto/tls"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"regexp"
"runtime"
"strconv"
"strings"
"sync"
"time"
)
// ==========================================
// 全局常量与变量
// ==========================================
const (
ExpireDate = "2025-12-30 23:59:59"
)
var (
// 颜色代码
Red = "\033[31m"
Green = "\033[32m"
Yellow = "\033[33m"
Blue = "\033[34m"
Nc = "\033[0m"
RedGloba = "\033[41;37m"
GreenGloba = "\033[42;37m"
YellowGloba = "\033[43;37m"
BlueGloba = "\033[44;37m"
// 日志前缀
Info = fmt.Sprintf("%s[信息]%s", Green, Nc)
Error = fmt.Sprintf("%s[错误]%s", Red, Nc)
Tip = fmt.Sprintf("%s[提示]%s", Yellow, Nc)
reader = bufio.NewReader(os.Stdin)
)
// ==========================================
// 工具函数
// ==========================================
func printColor(color, text string) {
fmt.Printf("%s%s%s\n", color, text, Nc)
}
// runCommand 执行 Shell 命令,增加超时控制和错误检查
func runCommand(name string, args ...string) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
cmd := exec.CommandContext(ctx, name, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
if ctx.Err() == context.DeadlineExceeded {
return fmt.Errorf("命令执行超时: %s %v", name, args)
}
return fmt.Errorf("命令执行失败: %v", err)
}
return nil
}
// runCommandOutput 执行命令并获取输出,增加超时
func runCommandOutput(name string, args ...string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
cmd := exec.CommandContext(ctx, name, args...)
out, err := cmd.CombinedOutput()
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
return "", fmt.Errorf("命令执行超时")
}
return "", err
}
return strings.TrimSpace(string(out)), nil
}
func fileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
func dirExists(dirname string) bool {
info, err := os.Stat(dirname)
if os.IsNotExist(err) {
return false
}
return info.IsDir()
}
func readInput(prompt string, defaultValue string) string {
if defaultValue != "" {
fmt.Printf("%s (默认: %s): ", prompt, defaultValue)
} else {
fmt.Printf("%s: ", prompt)
}
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
if input == "" {
return defaultValue
}
return input
}
func askYesNo(prompt string) bool {
for {
fmt.Printf("%s [y/N]: ", prompt)
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(strings.ToLower(input))
if input == "y" || input == "yes" {
return true
}
if input == "n" || input == "no" || input == "" {
return false
}
}
}
// 随机字符串生成
func randString(length int) string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
b := make([]byte, length)
for i := range b {
b[i] = charset[time.Now().UnixNano()%int64(len(charset))]
}
return string(b)
}
// ==========================================
// 文件操作辅助函数
// ==========================================
// updateConfigFile 类似于 sed -i如果 key 存在则更新,不存在则追加
func updateConfigFile(filePath string, configs map[string]string, separator string) error {
content, err := os.ReadFile(filePath)
if err != nil && !os.IsNotExist(err) {
return err
}
lines := strings.Split(string(content), "\n")
newLines := make([]string, 0, len(lines)+len(configs))
processedKeys := make(map[string]bool)
// 遍历现有行进行更新
for _, line := range lines {
trimmedLine := strings.TrimSpace(line)
updated := false
for key, value := range configs {
// 简单的匹配逻辑:行以 "key=" 开头(忽略空格)
// 注意这里需要根据具体配置文件格式调整sysctl 是 key = value
if strings.HasPrefix(trimmedLine, "#") {
continue
}
// 检查是否匹配 key (去掉空格后)
cleanLine := strings.ReplaceAll(trimmedLine, " ", "")
cleanKey := strings.ReplaceAll(key, " ", "")
if strings.HasPrefix(cleanLine, cleanKey+strings.TrimSpace(separator)) {
newLines = append(newLines, key+separator+value)
processedKeys[key] = true
updated = true
break
}
}
if !updated {
newLines = append(newLines, line)
}
}
// 追加未找到的配置
for key, value := range configs {
if !processedKeys[key] {
newLines = append(newLines, key+separator+value)
}
}
// 移除末尾可能的空行并重新组合
output := strings.Join(newLines, "\n")
// 确保文件末尾有换行
if !strings.HasSuffix(output, "\n") {
output += "\n"
}
return os.WriteFile(filePath, []byte(output), 0644)
}
// ==========================================
// 1. 过期检查模块 (proxy/time.go)
// ==========================================
func checkExpiration() error {
urls := []string{
"https://www.cloudflare.com/cdn-cgi/trace",
"https://www.visa.cn/cdn-cgi/trace",
}
beijingLocation := time.FixedZone("Asia/Shanghai", 8*3600)
pattern := regexp.MustCompile(`ts=(\d+)`)
client := &http.Client{
Timeout: 5 * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
var beijingTime time.Time
success := false
for _, url := range urls {
resp, err := client.Get(url)
if err != nil {
continue
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
continue
}
body, err := io.ReadAll(resp.Body)
if err != nil {
continue
}
match := pattern.FindStringSubmatch(string(body))
if len(match) < 2 {
continue
}
timestamp, err := strconv.ParseInt(match[1], 10, 64)
if err != nil {
continue
}
utcTime := time.Unix(timestamp, 0).UTC()
beijingTime = utcTime.In(beijingLocation)
success = true
break
}
if !success {
return fmt.Errorf("无法验证有效期")
}
expireTime, err := time.ParseInLocation("2006-01-02 15:04:05", ExpireDate, beijingLocation)
if err != nil {
return fmt.Errorf("解析时间失败: %v", err)
}
if beijingTime.After(expireTime) {
return fmt.Errorf("当前脚本已过期,请联系管理员获取更新")
}
return nil
}
// ==========================================
// 2. 内核切换模块 (dev/image.sh)
// ==========================================
func detectRegion() bool {
fmt.Printf("%s 检测网络位置...\n", Blue)
urls := []string{
"https://www.cloudflare.com/cdn-cgi/trace",
"https://www.visa.cn/cdn-cgi/trace",
}
client := &http.Client{Timeout: 5 * time.Second}
for _, url := range urls {
resp, err := client.Get(url)
if err != nil {
continue
}
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if strings.Contains(string(body), "loc=CN") {
fmt.Printf("%s CN 网络环境\n", Green)
return true
}
}
fmt.Printf("%s 非 CN 网络环境\n", Green)
return false
}
func changeMirrors() {
fmt.Printf("%s [0/5] 配置软件源\n", Yellow)
isCN := detectRegion()
var cmdStr string
if isCN {
fmt.Println("使用阿里云镜像...")
cmdStr = `bash <(curl -sSL https://gitee.com/SuperManito/LinuxMirrors/raw/main/ChangeMirrors.sh) --source mirrors.aliyun.com --protocol http --use-intranet-source false --install-epel true --backup true --upgrade-software false --clean-cache false --ignore-backup-tips --pure-mode`
} else {
fmt.Println("使用官方源...")
cmdStr = `bash <(curl -sSL https://raw.githubusercontent.com/SuperManito/LinuxMirrors/main/ChangeMirrors.sh) --use-official-source true --protocol http --use-intranet-source false --install-epel true --backup true --upgrade-software false --clean-cache false --ignore-backup-tips --pure-mode`
}
// 这里调用 bash 执行脚本
cmd := exec.Command("bash", "-c", cmdStr)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Printf("%s 警告:软件源切换失败,继续使用当前源\n", Yellow)
} else {
exec.Command("apt", "update", "-qq").Run()
}
}
func checkCloudKernel() (bool, []string) {
out, _ := runCommandOutput("uname", "-r")
isCloud := strings.Contains(out, "cloud")
// 查找 cloud 包
dpkgOut, _ := runCommandOutput("bash", "-c", "dpkg -l | awk '/linux-(image|headers)-[0-9].*cloud/ {print $2}'")
pkgs := strings.Fields(dpkgOut)
return isCloud, pkgs
}
func installStandardKernel() error {
fmt.Printf("%s [1/5] 安装标准内核\n", Yellow)
imagePkg := "linux-image-amd64"
headersPkg := "linux-headers-amd64"
// 简单判断 Ubuntu
if releaseOut, _ := os.ReadFile("/etc/os-release"); strings.Contains(string(releaseOut), "Ubuntu") {
imagePkg = "linux-image-generic"
headersPkg = "linux-headers-generic"
}
fmt.Printf("正在安装 %s %s ...\n", imagePkg, headersPkg)
// 设置 DEBIAN_FRONTEND=noninteractive
cmd := exec.Command("apt", "install", "-y", "--reinstall", imagePkg, headersPkg)
cmd.Env = append(os.Environ(), "DEBIAN_FRONTEND=noninteractive")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("标准内核安装失败")
}
// 更新 initramfs
// 查找最新非 cloud 内核
cmdStr := `ls /boot/vmlinuz-* 2>/dev/null | grep -v cloud | sort -V | tail -1 | sed 's|/boot/vmlinuz-||'`
stdKernel, _ := runCommandOutput("bash", "-c", cmdStr)
if stdKernel != "" {
fmt.Printf("更新 initramfs: %s\n", stdKernel)
runCommand("update-initramfs", "-u", "-k", stdKernel)
}
fmt.Printf("%s ✓ 标准内核安装完成: %s\n", Green, stdKernel)
return nil
}
func removeCloudKernels(pkgs []string) {
fmt.Printf("%s [2/5] 卸载所有 Cloud 内核\n", Yellow)
if len(pkgs) == 0 {
fmt.Printf("%s 未找到 Cloud 内核包\n", Yellow)
return
}
fmt.Println("正在卸载以下包:", pkgs)
// unhold
args := append([]string{"unhold"}, pkgs...)
exec.Command("apt-mark", args...).Run()
// purge
purgeArgs := append([]string{"purge", "-y"}, pkgs...)
cmd := exec.Command("apt", purgeArgs...)
cmd.Env = append(os.Environ(), "DEBIAN_FRONTEND=noninteractive")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Run()
exec.Command("apt", "autoremove", "-y", "--purge").Run()
fmt.Printf("%s ✓ Cloud 内核清理流程结束\n", Green)
}
func updateGrub() {
fmt.Printf("%s [3/5] 配置与更新 GRUB\n", Yellow)
grubConfig := `GRUB_DEFAULT=0
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR=$(lsb_release -i -s 2> /dev/null || echo Debian)
GRUB_CMDLINE_LINUX_DEFAULT="quiet"
GRUB_CMDLINE_LINUX=""
GRUB_DISABLE_OS_PROBER=true
`
// 备份
os.MkdirAll("/root/grub_backup", 0755)
runCommand("cp", "/etc/default/grub", fmt.Sprintf("/root/grub_backup/grub.default.%d", time.Now().Unix()))
distributor := "Debian"
if out, err := runCommandOutput("lsb_release", "-i", "-s"); err == nil {
distributor = out
}
finalGrubConfig := strings.Replace(grubConfig, "$(lsb_release -i -s 2> /dev/null || echo Debian)", distributor, 1)
os.WriteFile("/etc/default/grub", []byte(finalGrubConfig), 0644)
fmt.Println("重新生成 GRUB 配置...")
runCommand("update-grub")
runCommand("grub-set-default", "0")
if dirExists("/sys/firmware/efi") {
fmt.Println("更新 UEFI 引导...")
runCommand("grub-install", "--target=x86_64-efi", "--efi-directory=/boot/efi", "--bootloader-id=debian", "--recheck")
}
fmt.Printf("%s ✓ GRUB 更新完成\n", Green)
}
func performKernelSwap() {
osInfo := getOSInfo()
if osInfo.ID != "debian" && osInfo.ID != "ubuntu" && osInfo.ID != "kali" {
fmt.Printf("%s 错误: 内核切换功能仅支持 Debian/Ubuntu 系统 (当前检测为: %s)\n", Error, osInfo.ID)
return
}
fmt.Printf("\n%s⚠ 高危操作警告 ⚠️%s\n", Red, Nc)
fmt.Println("即将执行:更换软件源 -> 安装标准内核 -> 卸载 Cloud 内核 -> 更新引导")
if !askYesNo("确认继续?") {
fmt.Println("操作已取消")
return
}
// 确保基础工具存在 (curl, ca-certificates)
// 仅针对 Debian/Ubuntu因为只有它们涉及此内核切换逻辑
runCommand("apt-get", "update", "-qq")
runCommand("apt-get", "install", "-y", "-qq", "curl", "ca-certificates")
changeMirrors()
if err := installStandardKernel(); err != nil {
fmt.Println(Error, err)
return
}
_, pkgs := checkCloudKernel()
removeCloudKernels(pkgs)
updateGrub()
fmt.Printf("\n%s内核切换操作完成需要重启生效。%s\n", Green, Nc)
if askYesNo("立即重启?") {
runCommand("reboot")
} else {
fmt.Println("请稍后手动重启。")
}
os.Exit(0) // 无论是否重启,都应退出当前脚本
}
// ==========================================
// 3. L2TP VPN 安装模块 (proxy/l2tp.sh)
// ==========================================
type OSInfo struct {
ID string
VersionID string
}
func getOSInfo() OSInfo {
content, err := os.ReadFile("/etc/os-release")
if err != nil {
// 尝试 /usr/lib/os-release
content, _ = os.ReadFile("/usr/lib/os-release")
}
info := OSInfo{}
lines := strings.Split(string(content), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "ID=") {
info.ID = strings.Trim(strings.TrimPrefix(line, "ID="), "\"")
}
if strings.HasPrefix(line, "VERSION_ID=") {
info.VersionID = strings.Trim(strings.TrimPrefix(line, "VERSION_ID="), "\"")
}
}
return info
}
func installDependencies(osInfo OSInfo) {
fmt.Printf("%s 正在检查并安装依赖...%s\n", Tip, Nc)
var updateCmd, installCmd string
apps := []string{"curl", "xl2tpd", "strongswan", "pptpd", "nftables"}
switch osInfo.ID {
case "debian", "ubuntu", "kali":
updateCmd = "apt update -y"
installCmd = "apt install -y"
case "alpine":
updateCmd = "apk update -f"
installCmd = "apk add -f"
case "centos", "almalinux", "rocky", "oracle", "fedora":
updateCmd = "dnf update -y"
installCmd = "dnf install -y"
if osInfo.ID == "centos" {
updateCmd = "yum update -y"
installCmd = "yum install -y"
}
default:
fmt.Printf("%s 不支持的操作系统: %s\n", Error, osInfo.ID)
os.Exit(1)
}
// 执行更新
if err := runCommand("bash", "-c", updateCmd); err != nil {
fmt.Printf("%s 警告: 系统更新失败,尝试继续安装...\n", Tip)
}
// 安装软件包
// 这里简化处理,直接拼接命令
fullInstallCmd := fmt.Sprintf("%s %s ppp", installCmd, strings.Join(apps, " "))
fmt.Printf("%s 执行安装命令: %s\n", Tip, fullInstallCmd)
if err := runCommand("bash", "-c", fullInstallCmd); err != nil {
fmt.Printf("%s 错误: 依赖安装失败,脚本退出。\n", Error)
os.Exit(1)
}
}
// getPublicIP 并发获取公网IP取最快响应
func getPublicIP() string {
apis := []string{
"http://api64.ipify.org",
"http://4.ipw.cn",
"http://ip.sb",
"http://checkip.amazonaws.com",
"http://icanhazip.com",
"http://ipinfo.io/ip",
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resultChan := make(chan string, 1)
var wg sync.WaitGroup
for _, url := range apis {
wg.Add(1)
go func(apiURL string) {
defer wg.Done()
req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil)
if err != nil {
return
}
client := &http.Client{}
resp, err := client.Do(req)
if err == nil {
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
ip := strings.TrimSpace(string(body))
// 简单的 IP 格式校验
if ip != "" && !strings.Contains(ip, "<") {
select {
case resultChan <- ip:
cancel() // 找到一个就取消其他请求
default:
}
}
}
}(url)
}
go func() {
wg.Wait()
close(resultChan)
}()
select {
case ip := <-resultChan:
if ip != "" {
return ip
}
case <-ctx.Done():
}
return "127.0.0.1" // Fallback
}
func setupSysctl() {
configs := map[string]string{
"net.ipv4.ip_forward": "1",
"net.ipv4.conf.all.send_redirects": "0",
"net.ipv4.conf.default.send_redirects": "0",
"net.ipv4.conf.all.accept_redirects": "0",
"net.ipv4.conf.default.accept_redirects": "0",
}
fmt.Println(Tip, "正在配置 Sysctl 参数...")
if err := updateConfigFile("/etc/sysctl.conf", configs, " = "); err != nil {
fmt.Printf("%s 警告: 更新 sysctl.conf 失败: %v\n", Tip, err)
}
runCommand("sysctl", "-p")
}
func setupNftables(l2tpPort, pptpPort, l2tpLocIP, pptpLocIP string) {
// 备份
if fileExists("/etc/nftables.conf") {
runCommand("cp", "/etc/nftables.conf", fmt.Sprintf("/etc/nftables.conf.old.%d", time.Now().Unix()))
}
interfaceName := "eth0"
// 获取默认网卡接口
out, err := runCommandOutput("bash", "-c", "ip route get 8.8.8.8 | awk '{print $5; exit}'")
if err == nil && out != "" {
interfaceName = out
}
config := fmt.Sprintf(`#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0;
ct state established,related accept
ip protocol icmp accept
iif lo accept
udp dport {500,4500,%s,%s} accept
accept
}
chain forward {
type filter hook forward priority 0;
ct state established,related accept
ip saddr %s.0/24 accept
ip saddr %s.0/24 accept
accept
}
chain output {
type filter hook output priority 0;
accept
}
}
table ip nat {
chain prerouting {
type nat hook prerouting priority 0;
accept
}
chain postrouting {
type nat hook postrouting priority 100;
oif "%s" masquerade
}
chain output {
type nat hook output priority 0;
accept
}
}
`, l2tpPort, pptpPort, l2tpLocIP, pptpLocIP, interfaceName)
os.WriteFile("/etc/nftables.conf", []byte(config), 0755)
runCommand("systemctl", "daemon-reload")
runCommand("systemctl", "enable", "nftables")
runCommand("systemctl", "restart", "nftables")
}
func installVPN() {
// 获取公网IP
publicIP := getPublicIP()
fmt.Println()
// L2TP 配置
fmt.Println(Tip, "请输入 L2TP IP范围:")
l2tpLocIP := readInput("(默认范围: 10.10.10)", "10.10.10")
fmt.Println(Tip, "请输入 L2TP 端口:")
l2tpPort := readInput("(默认端口: 1701)", "1701")
l2tpUser := randString(5)
fmt.Printf("%s 请输入 L2TP 用户名:\n", Tip)
l2tpUser = readInput(fmt.Sprintf("(默认用户名: %s)", l2tpUser), l2tpUser)
l2tpPass := randString(7)
fmt.Printf("%s 请输入 %s 的密码:\n", Tip, l2tpUser)
l2tpPass = readInput(fmt.Sprintf("(默认密码: %s)", l2tpPass), l2tpPass)
l2tpPSK := randString(20)
fmt.Printf("%s 请输入 L2TP PSK 密钥:\n", Tip)
l2tpPSK = readInput(fmt.Sprintf("(默认PSK: %s)", l2tpPSK), l2tpPSK)
// PPTP 配置
fmt.Println(Tip, "请输入 PPTP IP范围:")
pptpLocIP := readInput("(默认范围: 192.168.30)", "192.168.30")
fmt.Println(Tip, "请输入 PPTP 端口:")
pptpPort := readInput("(默认端口: 1723)", "1723")
pptpUser := randString(5)
fmt.Printf("%s 请输入 PPTP 用户名:\n", Tip)
pptpUser = readInput(fmt.Sprintf("(默认用户名: %s)", pptpUser), pptpUser)
pptpPass := randString(7)
fmt.Printf("%s 请输入 %s 的密码:\n", Tip, pptpUser)
pptpPass = readInput(fmt.Sprintf("(默认密码: %s)", pptpPass), pptpPass)
// 展示配置信息
fmt.Println()
fmt.Printf("%s L2TP服务器本地IP: %s%s.1%s\n", Info, Green, l2tpLocIP, Nc)
fmt.Printf("%s L2TP客户端IP范围: %s%s.11-%s.255%s\n", Info, Green, l2tpLocIP, l2tpLocIP, Nc)
fmt.Printf("%s L2TP端口 : %s%s%s\n", Info, Green, l2tpPort, Nc)
fmt.Printf("%s L2TP用户名 : %s%s%s\n", Info, Green, l2tpUser, Nc)
fmt.Printf("%s L2TP密码 : %s%s%s\n", Info, Green, l2tpPass, Nc)
fmt.Printf("%s L2TPPSK密钥 : %s%s%s\n", Info, Green, l2tpPSK, Nc)
fmt.Println()
fmt.Printf("%s PPTP服务器本地IP: %s%s.1%s\n", Info, Green, pptpLocIP, Nc)
fmt.Printf("%s PPTP客户端IP范围: %s%s.11-%s.255%s\n", Info, Green, pptpLocIP, pptpLocIP, Nc)
fmt.Printf("%s PPTP端口 : %s%s%s\n", Info, Green, pptpPort, Nc)
fmt.Printf("%s PPTP用户名 : %s%s%s\n", Info, Green, pptpUser, Nc)
fmt.Printf("%s PPTP密码 : %s%s%s\n", Info, Green, pptpPass, Nc)
fmt.Println()
fmt.Println("正在生成配置文件...")
// /etc/ipsec.conf
ipsecConf := fmt.Sprintf(`config setup
charondebug="ike 2, knl 2, cfg 2"
uniqueids=no
conn %%default
keyexchange=ikev1
authby=secret
ike=aes256-sha1-modp1024,aes128-sha1-modp1024,3des-sha1-modp1024!
esp=aes256-sha1,aes128-sha1,3des-sha1!
keyingtries=3
ikelifetime=8h
lifetime=1h
dpdaction=clear
dpddelay=30s
dpdtimeout=120s
rekey=no
forceencaps=yes
fragmentation=yes
conn L2TP-PSK
left=%%any
leftid=%s
leftfirewall=yes
leftprotoport=17/%s
right=%%any
rightprotoport=17/%%any
type=transport
auto=add
also=%%default
`, publicIP, l2tpPort)
os.WriteFile("/etc/ipsec.conf", []byte(ipsecConf), 0644)
// /etc/ipsec.secrets
ipsecSecrets := fmt.Sprintf(`%%any %%any : PSK "%s"
`, l2tpPSK)
os.WriteFile("/etc/ipsec.secrets", []byte(ipsecSecrets), 0600)
// /etc/xl2tpd/xl2tpd.conf
xl2tpdConf := fmt.Sprintf(`[global]
port = %s
[lns default]
ip range = %s.11-%s.255
local ip = %s.1
require chap = yes
refuse pap = yes
require authentication = yes
name = l2tpd
ppp debug = yes
pppoptfile = /etc/ppp/options.xl2tpd
length bit = yes
`, l2tpPort, l2tpLocIP, l2tpLocIP, l2tpLocIP)
os.MkdirAll("/etc/xl2tpd", 0755)
os.WriteFile("/etc/xl2tpd/xl2tpd.conf", []byte(xl2tpdConf), 0644)
// /etc/ppp/options.xl2tpd
pppOptXl2tpd := `ipcp-accept-local
ipcp-accept-remote
require-mschap-v2
ms-dns 8.8.8.8
ms-dns 114.114.114.114
noccp
auth
hide-password
idle 1800
mtu 1410
mru 1410
nodefaultroute
debug
proxyarp
connect-delay 5000
`
os.MkdirAll("/etc/ppp", 0755)
os.WriteFile("/etc/ppp/options.xl2tpd", []byte(pppOptXl2tpd), 0644)
// /etc/pptpd.conf
pptpdConf := fmt.Sprintf(`option /etc/ppp/pptpd-options
debug
localip %s.1
remoteip %s.11-255
`, pptpLocIP, pptpLocIP)
os.WriteFile("/etc/pptpd.conf", []byte(pptpdConf), 0644)
// /etc/ppp/pptpd-options
pptpdOptions := `name pptpd
refuse-pap
refuse-chap
refuse-mschap
require-mschap-v2
require-mppe-128
ms-dns 8.8.8.8
ms-dns 114.114.114.114
proxyarp
lock
nobsdcomp
novj
novjccomp
nologfd
`
os.WriteFile("/etc/ppp/pptpd-options", []byte(pptpdOptions), 0644)
// /etc/ppp/chap-secrets
chapSecrets := "# Secrets for authentication using CHAP\n# client server secret IP addresses\n"
// 1. 添加主用户 (绑定静态 IP .10,避免与动态地址池 11-255 冲突)
chapSecrets += fmt.Sprintf("%s l2tpd %s %s.10\n", l2tpUser, l2tpPass, l2tpLocIP)
chapSecrets += fmt.Sprintf("%s pptpd %s %s.10\n", pptpUser, pptpPass, pptpLocIP)
// 2. 批量生成用户 (IP 11-255),还原 shell 脚本中的逻辑
for i := 11; i <= 255; i++ {
chapSecrets += fmt.Sprintf("%s%d l2tpd %s%d %s.%d\n", l2tpUser, i, l2tpPass, i, l2tpLocIP, i)
chapSecrets += fmt.Sprintf("%s%d pptpd %s%d %s.%d\n", pptpUser, i, pptpPass, i, pptpLocIP, i)
}
os.WriteFile("/etc/ppp/chap-secrets", []byte(chapSecrets), 0600)
// 设置系统和防火墙
setupSysctl()
setupNftables(l2tpPort, pptpPort, l2tpLocIP, pptpLocIP)
// 启动服务
fmt.Println("正在启动服务...")
services := []string{"ipsec", "xl2tpd", "pptpd"}
// 检查 ipsec 服务名兼容性
if _, err := runCommandOutput("systemctl", "list-unit-files", "strongswan.service"); err == nil {
// 如果存在 strongswan.service且没有 ipsec.service则替换
if _, err := runCommandOutput("systemctl", "list-unit-files", "ipsec.service"); err != nil {
services[0] = "strongswan"
}
}
runCommand("systemctl", "daemon-reload")
// 再次确保 IP 转发开启
if err := os.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte("1\n"), 0644); err != nil {
fmt.Printf("%s 警告: 无法写入 ip_forward: %v\n", Tip, err)
}
for _, svc := range services {
runCommand("systemctl", "enable", svc)
runCommand("systemctl", "restart", svc)
}
fmt.Println()
fmt.Printf("%s===============================================%s\n", Green, Nc)
fmt.Printf("%sVPN 安装完成%s\n", Green, Nc)
fmt.Printf("%s===============================================%s\n", Green, Nc)
fmt.Printf("请保留好以下信息:\n")
fmt.Printf("服务器IP: %s\n", publicIP)
fmt.Printf("L2TP PSK: %s\n", l2tpPSK)
fmt.Printf("L2TP 主账号: %s / 密码: %s\n", l2tpUser, l2tpPass)
fmt.Printf("PPTP 主账号: %s / 密码: %s\n", pptpUser, pptpPass)
fmt.Printf("\n%s 已自动生成批量账号,详情请查看 /etc/ppp/chap-secrets 文件%s\n", Tip, Nc)
}
// ==========================================
// 主函数
// ==========================================
func main() {
// 清屏
if runtime.GOOS == "linux" {
fmt.Print("\033[H\033[2J")
}
fmt.Printf("%s###############################################################%s\n", Green, Nc)
fmt.Printf("%s# L2TP/IPSec & PPTP VPN 一键安装脚本 #%s\n", Green, Nc)
fmt.Printf("%s###############################################################%s\n", Green, Nc)
fmt.Println()
// 1. 检查 Root
if os.Geteuid() != 0 {
fmt.Printf("%s 错误: 必须使用 root 权限运行此脚本\n", Error)
os.Exit(1)
}
// 2. 时间过期检查
if err := checkExpiration(); err != nil {
fmt.Printf("%s %v\n", Error, err)
os.Exit(1)
}
// 3. 检查 OpenVZ
if dirExists("/proc/vz") {
fmt.Printf("%s 警告: 你的VPS基于OpenVZ内核可能不支持IPSec。L2TP安装已取消。\n", Error)
os.Exit(1)
}
// 4. 检查 PPP 支持与内核切换逻辑
if !fileExists("/dev/ppp") {
fmt.Printf("%s 警告: 未检测到 /dev/ppp 设备,当前内核可能不支持 PPP。\n", Error)
uname, _ := runCommandOutput("uname", "-r")
fmt.Printf("%s 当前内核版本: %s\n", Tip, uname)
if askYesNo("是否尝试切换到标准内核 (将卸载Cloud内核并重置GRUB)?") {
performKernelSwap() // 注意:此函数内可能会重启或退出
} else {
fmt.Printf("%s 用户取消操作,无法继续安装 VPN。\n", Error)
os.Exit(1)
}
} else {
// 即使有 /dev/ppp也可以检查一下是不是 cloud 内核,提示一下用户
isCloud, _ := checkCloudKernel()
if isCloud {
fmt.Printf("%s 提示: 检测到当前运行在 Cloud 内核上,但 /dev/ppp 存在,可以继续。\n", Tip)
fmt.Printf("如果安装后无法连接,建议重新运行脚本并选择切换内核。\n")
}
}
// 5. 安装 VPN
osInfo := getOSInfo()
installDependencies(osInfo)
installVPN()
}