Ubuntu 移除 Firefox Snap 并安装 Firefox 官方 .deb 版本

一、移除 Ubuntu 自带的 Firefox Snap 占位包

Ubuntu 默认提供的 Firefox 是 Snap 版本,并且 apt 安装的 firefox 只是一个“占位包”,会强制安装 Snap 版本。

首先清理系统中所有 Firefox:

  1. 移除 snap 包
1
sudo snap remove firefox
  1. 移除 Ubuntu 自带的 firefox 占位包
1
sudo apt purge firefox
  1. 确认系统中没有任何 firefox 包
1
dpkg -l | grep firefox

如果没有输出,表示已彻底移除。


二、添加 Mozilla 官方 APT 仓库

Mozilla 为 Ubuntu 用户提供了真正的原生 .deb 安装方式,通过官方 APT 仓库获得更新。

  1. 创建 keyrings 目录
1
sudo install -d -m 0755 /etc/apt/keyrings
  1. 下载 Mozilla 官方仓库签名 key(ASCII)
1
sudo wget -O /etc/apt/keyrings/packages.mozilla.org.asc https://packages.mozilla.org/apt/repo-signing-key.gpg
  1. 将 ASCII key 转换为 apt 可用的 binary key
1
2
sudo gpg --dearmor -o /etc/apt/keyrings/packages.mozilla.org.gpg /etc/apt/keyrings/packages.mozilla.org.asc
sudo chmod 644 /etc/apt/keyrings/packages.mozilla.org.gpg
  1. 添加 Mozilla 官方 APT 源
1
2
echo "deb [signed-by=/etc/apt/keyrings/packages.mozilla.org.gpg] https://packages.mozilla.org/apt mozilla main" \
| sudo tee /etc/apt/sources.list.d/mozilla.list

三、避免 Ubuntu 主仓库再次安装 Snap 占位包(APT Pinning)

为了阻止 Ubuntu 的 firefox Snap 占位包再次安装,需要给 APT 设置优先级。

  1. 创建优先级规则文件
1
sudo nano /etc/apt/preferences.d/mozilla-firefox
  1. 写入以下内容
1
2
3
4
5
6
7
8
9
10
11
Package: firefox
Pin: origin packages.mozilla.org
Pin-Priority: 700

Package: firefox
Pin: origin mirrors.aliyun.com
Pin-Priority: -1

Package: firefox
Pin: origin archive.ubuntu.com
Pin-Priority: -1

保存退出。

这样 apt 就会优先使用 Mozilla 仓库,并拒绝 Ubuntu 仓库的 Snap 占位包。


四、更新软件源

1
sudo apt update

如果此时没有看到 Firefox 相关错误,说明 Mozilla 仓库已经成功启用。


五、安装正式的 Firefox .deb 版本

1
sudo apt install firefox

此时安装的 Firefox 即为来自 Mozilla 官方 APT 仓库的原生 .deb 版本。


六、验证安装结果

  1. 检查 Firefox 的路径
1
which firefox

应该输出:

1
/usr/bin/firefox

如果输出的是 /snap/bin/firefox,则表示仍在使用 Snap,需要清理后重新安装。

  1. 再次确认 dpkg 列表
1
dpkg -l | grep firefox

应该能看到 Mozilla 提供的版本,而不是 “snap1” 结尾的 Ubuntu 占位包。

aria2c安装和配置

1. 安装 aria2

Ubuntu 官方仓库直接可安装:

1
2
sudo apt update
sudo apt install aria2

验证版本:

1
aria2c -v

2. 创建默认配置文件

aria2 支持使用配置文件自动加载参数,无需每次手动输入。

创建配置目录:

1
mkdir -p ~/.aria2

编辑配置文件:

1
nano ~/.aria2/aria2.conf

写入常用参数(示例推荐配置):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 基本下载配置
continue=true
file-allocation=none

# 多线程
split=16
max-connection-per-server=16
min-split-size=1M

# 重试与超时
max-tries=5
timeout=600

# 证书检查(可按需关闭)
check-certificate=false

# 下载目录(可选)
dir=/home/你的用户名/Downloads

这样以后只需执行:

1
aria2c http://example.com/file.zip

所有默认参数自动生效

vmware+ubuntu gui界面显示不全

  • 环境:
    • VMware 虚拟机中安装 Ubuntu(桌面版)
  • 现象:
    • 可以正常进入桌面
    • 有些软件的图形界面打不开 / 空白 / 不刷新 /控件显示不全

解决步骤:

  • 安装/确认 VMware Tools

    1
    2
    3
    sudo apt update
    sudo apt install -y open-vm-tools open-vm-tools-desktop
    sudo reboot

    如果没有解决,进行下一步

  • 关闭3D 加速 / 3D 渲染

    VMware 设置里关闭 加速3D图形

    1
    虚拟机->设置->硬件->显示->取消勾选3D图形

使用vs2022的CRT检测内存泄露

在main.cpp中添加如下内容,需要确保_CRTDBG_MAP_ALLOC宏放在最前面

1
2
3
4
5
6
7
8
9
#define _CRTDBG_MAP_ALLOC
#include <cstdlib>
#include <crtdbg.h>
#include <iostream>

#ifdef _DEBUG
#define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
#define new DBG_NEW
#endif

在main函数开始处添加

1
2
// 打开自动在程序退出时检测泄漏并输出报告
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

完整例子,使用debug运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define _CRTDBG_MAP_ALLOC
#include <cstdlib>
#include <crtdbg.h>
#include <iostream>

#ifdef _DEBUG
#define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
#define new DBG_NEW
#endif

int main()
{
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
int* p = new int[100]; // 故意制造泄漏
return 0;
}

输出示例:

image-20251019164600710

输出显示问题出在memoryleak.cpp文件第14行,泄露大小为400 bytes,如果需要在泄露处中断,可以添加

1
_CrtSetBreakAlloc(编号);

里面的编号就是图片中的160,程序运行到内存泄露处会中断程序

vcpkg 安装和使用

一、vcpkg 简介

vcpkg 是微软开发的 C++ 包管理器,用于简化 C++ 库的获取、编译和集成过程。它包含超过 2000 个开源库的精选注册表,这些库经过 vcpkg 的持续集成管道验证,确保可以协同工作。

二、安装和设置

1. 克隆 vcpkg 仓库

1
2
git clone https://github.com/microsoft/vcpkg.git
cd vcpkg

vcpkg 仓库包含:

  • 获取 vcpkg 可执行文件的脚本
  • 由 vcpkg 社区维护的精选开源库注册表
  • 构建和安装库所需的配方和元数据(不包含库源代码)

2. 运行引导脚本

1
2
3
4
5
# Windows (PowerShell)
.\bootstrap-vcpkg.bat

# Linux/macOS
./bootstrap-vcpkg.sh

引导脚本会:

  • 执行先决条件检查
  • 下载 vcpkg 可执行文件

3. 设置环境变量

1
2
3
4
5
6
7
# PowerShell
$env:VCPKG_ROOT="C:\path\to\vcpkg"
$env:PATH="$env:VCPKG_ROOT;$env:PATH"

# CMD
set "VCPKG_ROOT=C:\path\to\vcpkg"
set PATH=%VCPKG_ROOT%;%PATH%

作用:

  • VCPKG_ROOT:帮助 Visual Studio 定位 vcpkg 实例
  • 添加到 PATH:确保可以直接从命令行运行 vcpkg 命令
  • 执行:vcpkg integrate install

三、核心文件详解

1. vcpkg.json(清单文件)

这是项目的依赖清单文件,定义项目所需的包。

1
2
3
4
5
{
"dependencies": [
"fmt"
]
}

作用:

  • 声明项目依赖的库
  • vcpkg 读取此文件以了解需要安装哪些依赖项
  • 与 CMake 集成,为项目提供所需的依赖项
  • 应纳入版本控制
  • 运行vcpkg install会根据vcpkg.json安装依赖

创建方法:

1
2
vcpkg new --application        # 创建清单文件
vcpkg add port fmt # 添加依赖包

2. vcpkg-configuration.json(配置文件)

1
2
3
4
5
6
7
{
"default-registry": {
"kind": "git",
"baseline": "...",
"repository": "..."
}
}

作用:

  • 引入基线(baseline),对项目依赖项设置最低版本约束
  • 确保不同开发环境之间的版本一致性
  • 建议纳入版本控制
  • 通常由 vcpkg new 自动生成,一般不需手动修改

3. CMakePresets.json(CMake 预设文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
{
"version": 3,
"configurePresets": [
{
"name": "windows-base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/out/build/${presetName}",
"installDir": "${sourceDir}/out/install/${presetName}",
"cacheVariables": {
"CMAKE_C_COMPILER": "cl.exe",
"CMAKE_CXX_COMPILER": "cl.exe",
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" //添加CMAKE_TOOLCHAIN_FILE
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Windows"
}
},
{
"name": "x64-debug",
"displayName": "x64 Debug",
"inherits": "windows-base",
"architecture": {
"value": "x64",
"strategy": "external"
},
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "x64-release",
"displayName": "x64 Release",
"inherits": "x64-debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "linux-debug",
"displayName": "Linux Debug",
"generator": "Ninja",
"binaryDir": "${sourceDir}/out/build/${presetName}",
"installDir": "${sourceDir}/out/install/${presetName}",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"//添加CMAKE_TOOLCHAIN_FILE
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Linux"
},
"vendor": {
"microsoft.com/VisualStudioRemoteSettings/CMake/1.0": {
"sourceDir": "$env{HOME}/.vs/$ms{projectDirName}"
}
}
},
{
"name": "macos-debug",
"displayName": "macOS Debug",
"generator": "Ninja",
"binaryDir": "${sourceDir}/out/build/${presetName}",
"installDir": "${sourceDir}/out/install/${presetName}",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"//添加CMAKE_TOOLCHAIN_FILE
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Darwin"
},
"vendor": {
"microsoft.com/VisualStudioRemoteSettings/CMake/1.0": {
"sourceDir": "$env{HOME}/.vs/$ms{projectDirName}"
}
}
}
]
}

作用:

  • 配置 CMake 使用 vcpkg 的工具链文件
  • 设置 CMAKE_TOOLCHAIN_FILE 指向 vcpkg 的自定义工具链
  • 使 CMake 能够自动链接 vcpkg 安装的库
  • 定义构建预设(生成器、输出目录等)
  • 应纳入版本控制,团队共享

关键配置:

  • CMAKE_TOOLCHAIN_FILE:vcpkg 与 CMake 集成的核心
  • 路径:$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake

4. CMakeUserPresets.json(用户级预设文件 可选)

1
2
3
4
5
6
7
8
9
10
11
12
{
"version": 2,
"configurePresets": [
{
"name": "default",
"inherits": "vcpkg",
"environment": {
"VCPKG_ROOT": "<path to vcpkg>"
}
}
]
}

作用:

  • 设置用户特定的配置(如本地 vcpkg 路径)
  • 继承 CMakePresets.json 中的预设
  • 不应纳入版本控制(因为包含特定于用户的路径)
  • 一般不需要配置,只需要在环境变量中配置好VCPKG_ROOT+CMakePresets.json指定CMAKE_TOOLCHAIN_FILE即可

5. CMakeLists.txt(CMake 构建脚本)

1
2
3
4
5
6
7
8
9
cmake_minimum_required(VERSION 3.10)

project(HelloWorld)

find_package(fmt CONFIG REQUIRED)

add_executable(HelloWorld helloworld.cpp)

target_link_libraries(HelloWorld PRIVATE fmt::fmt)

各行作用:

  • cmake_minimum_required(VERSION 3.10):指定 CMake 最低版本要求
  • project(HelloWorld):设置项目名称
  • find_package(fmt CONFIG REQUIRED):查找 fmt 库的 CMake 配置文件
    • CONFIG:使用库的 CMake 配置模式
    • REQUIRED:未找到时生成错误
  • add_executable(HelloWorld helloworld.cpp):创建可执行目标
  • target_link_libraries(HelloWorld PRIVATE fmt::fmt):链接 fmt 库
    • PRIVATE:依赖仅用于构建,不传播到其他项目

四、工作流程

1. 初始化项目

1
2
vcpkg new --application           # 创建 vcpkg.json 和 vcpkg-configuration.json
vcpkg add port fmt # 添加依赖

2. 配置 CMake

  • 创建/修改 CMakePresets.json
  • 创建 CMakeUserPresets.json(设置本地路径)
  • 编写 CMakeLists.txt

3. 构建和运行

1
2
3
cmake --preset=default            # 配置项目
cmake --build build # 构建项目
./build/HelloWorld # 运行程序

在 Visual Studio 中:

  • 使用 “Build > Build All” 构建
  • 使用 “Debug > Start” 运行

五、vcpkg 工作原理

清单模式(Manifest Mode)

  • vcpkg 读取 vcpkg.json 自动安装依赖
  • 依赖安装到项目本地(而非全局)
  • 更适合现代 C++ 项目管理

CMake 集成

  1. vcpkg 提供自定义工具链文件
  2. CMake 通过工具链文件找到 vcpkg 安装的库
  3. find_package() 自动定位库的配置文件
  4. 自动配置包含路径和链接库

六、最佳实践

  1. 版本控制

    • ✅ 纳入:vcpkg.jsonvcpkg-configuration.jsonCMakePresets.json
    • ❌ 不纳入:CMakeUserPresets.jsonbuild/ 目录
  2. 环境变量

    • 设置 VCPKG_ROOT 环境变量(永久性设置更佳)
    • 添加到 PATH 以便命令行使用
  3. 依赖管理

    • 使用清单模式而非全局安装
    • 定期更新基线以获取安全更新
  4. 跨平台开发

    • vcpkg 支持 Windows、Linux、macOS
    • 使用相同的清单文件在不同平台工作

七、常用命令

1
2
3
4
5
6
vcpkg new --application           # 初始化项目
vcpkg add port <package> # 添加依赖
vcpkg remove port <package> # 移除依赖
vcpkg search <keyword> # 搜索可用包
vcpkg list # 列出已安装的包
vcpkg update # 更新 vcpkg 本身

八、使用优化

linux优化

保存为 setup_vcpkg_opt.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#!/bin/bash
set -e

# ========== 配置参数 ==========
CACHE_DIR="/root/.vcpkg-cache"
BASHRC="/root/.bashrc"

echo "? 配置 vcpkg 优化环境 (缓存 + Ninja + 并行 + apt)"

# ========== 1. 安装系统依赖(用 apt,避免 vcpkg 重编译基础库) ==========
echo "? 使用 apt 安装构建工具和常见依赖..."
sudo apt update -y
sudo apt install -y \
build-essential \
ninja-build \
cmake \
pkg-config \
git \
curl \
unzip \
zip \
tar \
libssl-dev \
libcurl4-openssl-dev \
zlib1g-dev \
libsqlite3-dev

# ========== 2. 配置 vcpkg 缓存 ==========
echo "? 配置缓存目录: $CACHE_DIR"
mkdir -p $CACHE_DIR
chmod -R 777 $CACHE_DIR

if ! grep -q "VCPKG_BINARY_SOURCES" $BASHRC; then
echo "export VCPKG_BINARY_SOURCES=\"clear;files,$CACHE_DIR,readwrite\"" >> $BASHRC
fi

# ========== 3. 启用 Ninja 构建 ==========
if ! grep -q "VCPKG_USE_NINJA" $BASHRC; then
echo "export VCPKG_USE_NINJA=1" >> $BASHRC
fi

# ========== 4. 启用并行编译 ==========
if ! grep -q "VCPKG_MAX_CONCURRENCY" $BASHRC; then
echo "export VCPKG_MAX_CONCURRENCY=\$(nproc)" >> $BASHRC
fi

# ========== 5. 立即生效 ==========
export VCPKG_BINARY_SOURCES="clear;files,$CACHE_DIR,readwrite"
export VCPKG_USE_NINJA=1
export VCPKG_MAX_CONCURRENCY=$(nproc)

# ========== 6. 提示结果 ==========
echo "✅ 已配置 vcpkg 缓存: $CACHE_DIR"
echo "✅ Ninja 已通过 apt 安装并启用"
echo "✅ VCPKG_MAX_CONCURRENCY=$(nproc) (CPU核心数)"
echo "? 所有配置已写入 $BASHRC,下次登录自动生效"
1
source ~/.bashrc

image-20251010154330419

1
2
3
4
5
export PATH=$VCPKG_ROOT:$PATH
export VCPKG_USE_NINJA=1
export VCPKG_MAX_CONCURRENCY=$(nproc)
export VCPKG_BINARY_SOURCES="clear;files,/root/.vcpkg-cache,readwrite"
export CMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake

添加CMAKE_PREFIX_PATH

1
2
mkdir -p ~/.config/cmake
nano ~/.config/cmake/init.cmake

windows优化

  1. 在环境变量中添加path
  2. 添加VCPKG_BINARY_SOURCES,内容为 clear;files,C:\Users\Jianzhe\tools\vcpkg\.vcpkg-cache,readwrite
  3. 添加VCPKG_MAX_CONCURRENCY,内容为核心数量,如16
  4. 添加VCPKG_USE_NINJA,内容为 1

域名邮箱搭建

一、前置条件:

  1. GMail邮箱账号
  2. cloudflare账号
  3. resend.com账号
  4. 域名

二、域名邮件接收

使用cloudflare的邮箱服务,将域名邮箱转发到你的gmail邮箱,添加对于的dns记录

image-20251001220334461

image-20251001220447757

配置Catch-all,选择发送到电子邮件,目标使用gmail邮箱image-20251001220619945

三、域名邮件发送

使用resend.com服务,免费发送邮件额度:3000/月,100/天

将域名添加到resend

image-20251001012036868

在cloudflare上面添加相应的dns记录,可以等了cloudflare快捷添加,待出现三个Verified就表示成功了

image-20251001012230381

四、GMail添加邮箱

进入Gmail设置 → “账号和导入” → “用这个地址发送邮件” → 点击”添加其他电子邮件地址”。取消勾选 视为别名,点击下一步

image-20251001012537971

添加SMTP配置

  • Host:smtp.resend.com
  • Port:465
  • User:resend
  • Password:你的resend key

点击添加账号后,去到cloudflare配置的目标邮箱点击确认认证即可,然后设置为默认发信地址即可

image-20251001012652172

至此,你可以通过cloudflare转发域名邮箱到gmail,在gmail上使用域名邮箱发送或回复邮件。

五、简单的发件程序

对于日常的发送邮件而不需要回复邮件,可以使用如下python代码进行发送,下面是一个python gui程序,方便发送邮件

1、email_sender.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import os
import sys
import base64
from pathlib import Path
import tkinter as tk
from tkinter import filedialog, messagebox
import requests

# Windows 高分屏适配,避免字体模糊
if sys.platform == "win32":
try:
from ctypes import windll
windll.shcore.SetProcessDpiAwareness(1)
except Exception:
pass


class EmailSenderApp:
"""极简 Resend 邮件发送器:仅保留发送功能"""

def __init__(self, root):
self.root = root
self.root.title("Resend 邮件发送器")
self.root.geometry("720x560")
self.api_key = os.getenv("RESEND_KEY")

self.attachments = []

self.build_ui()

if not self.api_key:
messagebox.showwarning("提示", "未检测到 RESEND_KEY 环境变量,发送前请先配置。")

def build_ui(self):
pad = 10
frm = tk.Frame(self.root)
frm.pack(fill=tk.BOTH, expand=True, padx=pad, pady=pad)

# From
tk.Label(frm, text="From").grid(row=0, column=0, sticky="w")
self.from_entry = tk.Entry(frm)
self.from_entry.grid(row=0, column=1, sticky="we", padx=(6, 0))

# To
tk.Label(frm, text="To").grid(row=1, column=0, sticky="w", pady=(6, 0))
self.to_entry = tk.Entry(frm)
self.to_entry.grid(row=1, column=1, sticky="we", padx=(6, 0), pady=(6, 0))

# Subject
tk.Label(frm, text="Subject").grid(row=2, column=0, sticky="w", pady=(6, 0))
self.subject_entry = tk.Entry(frm)
self.subject_entry.grid(row=2, column=1, sticky="we", padx=(6, 0), pady=(6, 0))

# Message
tk.Label(frm, text="Message").grid(row=3, column=0, sticky="nw", pady=(6, 0))
msg_frame = tk.Frame(frm)
msg_frame.grid(row=3, column=1, sticky="nsew", padx=(6, 0), pady=(6, 0))
self.msg_text = tk.Text(msg_frame, height=12, wrap=tk.WORD)
self.msg_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
msg_scroll = tk.Scrollbar(msg_frame, command=self.msg_text.yview)
msg_scroll.pack(side=tk.RIGHT, fill=tk.Y)
self.msg_text.configure(yscrollcommand=msg_scroll.set)

# Attachments
attach_bar = tk.Frame(frm)
attach_bar.grid(row=4, column=1, sticky="w", pady=(6, 0))
tk.Button(attach_bar, text="添加附件", command=self.add_attachments).pack(side=tk.LEFT)
tk.Button(attach_bar, text="移除选中", command=self.remove_attachment).pack(side=tk.LEFT, padx=(6, 0))

self.attach_list = tk.Listbox(frm, height=4)
self.attach_list.grid(row=5, column=1, sticky="nsew", padx=(6, 0), pady=(6, 0))

# Buttons
btn_bar = tk.Frame(frm)
btn_bar.grid(row=6, column=1, sticky="e", pady=(10, 0))
tk.Button(btn_bar, text="清空", command=self.clear_form).pack(side=tk.RIGHT)
tk.Button(btn_bar, text="发送", command=self.send_email).pack(side=tk.RIGHT, padx=(0, 8))

# Layout weights
frm.columnconfigure(1, weight=1)
frm.rowconfigure(3, weight=1)
frm.rowconfigure(5, weight=1)

def add_attachments(self):
files = filedialog.askopenfilenames(title="选择附件")
for f in files:
self.attachments.append(f)
self.attach_list.insert(tk.END, Path(f).name)

def remove_attachment(self):
sel = self.attach_list.curselection()
if not sel:
return
idx = sel[0]
self.attach_list.delete(idx)
self.attachments.pop(idx)

def clear_form(self):
self.from_entry.delete(0, tk.END)
self.to_entry.delete(0, tk.END)
self.subject_entry.delete(0, tk.END)
self.msg_text.delete("1.0", tk.END)
self.attachments.clear()
self.attach_list.delete(0, tk.END)

def send_email(self):
if not self.api_key:
messagebox.showerror("错误", "未配置 RESEND_KEY 环境变量。")
return

from_email = self.from_entry.get().strip()
to_email = self.to_entry.get().strip()
subject = self.subject_entry.get().strip()
message = self.msg_text.get("1.0", tk.END).strip()

if not all([from_email, to_email, subject, message]):
messagebox.showerror("错误", "请填写 From、To、Subject 和 Message。")
return

payload = {
"from": from_email,
"to": [to_email],
"subject": subject,
"html": f"<html><body><pre>{message}</pre></body></html>",
}

if self.attachments:
att = []
for p in self.attachments:
try:
with open(p, "rb") as fh:
att.append({
"filename": Path(p).name,
"content": base64.b64encode(fh.read()).decode(),
})
except Exception as e:
messagebox.showerror("错误", f"读取附件失败: {Path(p).name} - {e}")
return
payload["attachments"] = att

try:
resp = requests.post(
"https://api.resend.com/emails",
json=payload,
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
},
timeout=30,
)
if resp.status_code == 200:
messagebox.showinfo("成功", "邮件已发送。")
self.clear_form()
else:
try:
msg = resp.json().get("message", resp.text)
except Exception:
msg = resp.text
messagebox.showerror("发送失败", msg)
except Exception as e:
messagebox.showerror("错误", f"请求失败: {e}")


def main():
root = tk.Tk()
EmailSenderApp(root)
root.mainloop()


if __name__ == "__main__":
main()


2、requirements.txt

1
requests==2.31.0

Hysteria2 一键搭建脚本

Hysteria2 centos一键 搭建脚本

创建 hysteria.sh脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
#!/bin/bash
export LANG=en_US.UTF-8
RED="\033[31m"
GREEN="\033[32m"
YELLOW="\033[33m"
PLAIN="\033[0m"

SCRIPT_PATH="$(readlink -f "$0")"

red(){
echo -e "\033[31m\033[01m$1\033[0m"
}
green(){
echo -e "\033[32m\033[01m$1\033[0m"
}
yellow(){
echo -e "\033[33m\033[01m$1\033[0m"
}

[[ $EUID -ne 0 ]] && red "Notice: Please run this script as root" && exit 1

install_base_packages() {
yellow "Checking and installing base dependencies..."

if command -v apt-get >/dev/null 2>&1; then
apt-get update -qq
elif command -v yum >/dev/null 2>&1; then
yum makecache fast >/dev/null 2>&1
fi

if ! command -v curl >/dev/null 2>&1; then
echo "Installing curl..."
if command -v apt-get >/dev/null 2>&1; then
apt-get install -y curl
else
yum install -y curl
fi
fi

if ! command -v wget >/dev/null 2>&1; then
echo "Installing wget..."
if command -v apt-get >/dev/null 2>&1; then
apt-get install -y wget
else
yum install -y wget
fi
fi

if ! command -v systemctl >/dev/null 2>&1; then
red "systemd is required but not installed; attempting to install..."
if command -v apt-get >/dev/null 2>&1; then
apt-get install -y systemd
else
yum install -y systemd
fi

if ! command -v systemctl >/dev/null 2>&1; then
red "Failed to install systemd, exiting"
exit 1
fi
fi
}

install_base_packages

detect_system() {
if [[ -f /etc/os-release ]]; then
. /etc/os-release
OS=$ID
OS_VERSION=$VERSION_ID
elif [[ -f /etc/lsb-release ]]; then
. /etc/lsb-release
OS=$DISTRIB_ID
OS_VERSION=$DISTRIB_RELEASE
elif [[ -f /etc/redhat-release ]]; then
OS="centos"
OS_VERSION=$(rpm -q --qf "%{VERSION}" centos-release 2>/dev/null || echo "unknown")
else
OS="unknown"
fi

OS=$(echo "$OS" | tr '[:upper:]' '[:lower:]')

case "$OS" in
ubuntu|debian)
SYSTEM="Ubuntu"
PACKAGE_UPDATE="apt-get update -qq"
PACKAGE_INSTALL="apt-get -y install"
PACKAGE_REMOVE="apt-get -y remove"
PACKAGE_UNINSTALL="apt-get -y autoremove"
;;
centos|rhel|fedora|rocky|almalinux|alma)
SYSTEM="CentOS"
PACKAGE_UPDATE="yum -y update"
PACKAGE_INSTALL="yum -y install"
PACKAGE_REMOVE="yum -y remove"
PACKAGE_UNINSTALL="yum -y autoremove"
;;
*)
red "Unsupported system: $OS"
exit 1
;;
esac
}

detect_system
green "Detected system: $SYSTEM ($OS $OS_VERSION)"

mkdir -p /etc/hysteria
mkdir -p /root/hy

realip(){
ip=$(curl -s4m8 ip.sb -k) || ip=$(curl -s6m8 ip.sb -k)
if [[ -z "$ip" ]]; then
ip="Failed"
fi
}

check_dependencies() {
missing_deps=""

for cmd in curl wget systemctl; do
if ! command -v $cmd &>/dev/null; then
missing_deps="$missing_deps $cmd"
fi
done

if ! command -v iptables &>/dev/null; then
missing_deps="$missing_deps iptables"
fi

if ! command -v ip6tables &>/dev/null; then
missing_deps="$missing_deps ip6tables"
fi

if [[ -n "$missing_deps" ]]; then
yellow "Missing required dependencies: $missing_deps"
yellow "Attempting to install..."
${PACKAGE_UPDATE}
${PACKAGE_INSTALL} $missing_deps
fi

if [[ "$SYSTEM" == "Ubuntu" ]]; then
for pkg in net-tools iproute2 openssl ca-certificates; do
if ! dpkg -l | grep -q "^ii.*$pkg"; then
yellow "Installing $pkg..."
${PACKAGE_INSTALL} $pkg
fi
done
fi
}

save_iptables_rules() {
if [[ "$SYSTEM" == "CentOS" ]]; then
mkdir -p /etc/sysconfig
iptables-save > /etc/sysconfig/iptables 2>/dev/null
ip6tables-save > /etc/sysconfig/ip6tables 2>/dev/null
if command -v systemctl &>/dev/null; then
systemctl enable iptables 2>/dev/null || true
systemctl enable ip6tables 2>/dev/null || true
fi
else
if ! dpkg -l | grep -q "^ii.*iptables-persistent"; then
yellow "Installing iptables-persistent..."
echo iptables-persistent iptables-persistent/autosave_v4 boolean true | debconf-set-selections
echo iptables-persistent iptables-persistent/autosave_v6 boolean true | debconf-set-selections
DEBIAN_FRONTEND=noninteractive ${PACKAGE_INSTALL} iptables-persistent netfilter-persistent
fi

mkdir -p /etc/iptables
iptables-save > /etc/iptables/rules.v4 2>/dev/null
ip6tables-save > /etc/iptables/rules.v6 2>/dev/null

if command -v netfilter-persistent &>/dev/null; then
netfilter-persistent save >/dev/null 2>&1
systemctl enable netfilter-persistent 2>/dev/null || true
fi
fi
sync
}

# get Hysteria port
get_hysteria_port() {
port=$(grep "^listen:" /etc/hysteria/config.yaml 2>/dev/null | grep -oP ':\K[0-9]+' | head -1)
echo "${port:-0}"
}

# get Hysteria password
get_hysteria_password() {
pwd=$(grep -A2 "^auth:" /etc/hysteria/config.yaml 2>/dev/null | grep "password:" | awk '{print $2}' | head -1)
echo "$pwd"
}

# Get configuration info
get_config_info() {
port=$(get_hysteria_port)
auth_pwd=$(get_hysteria_password)

# Fetch IP
ip=$(curl -s4m8 ip.sb -k) || ip=$(curl -s6m8 ip.sb -k)
[[ -n $(echo $ip | grep ":") ]] && last_ip="[$ip]" || last_ip=$ip

# Read domain from persisted file
if [[ -f /etc/hysteria/domain.txt ]]; then
hy_domain=$(cat /etc/hysteria/domain.txt)
else
# If no saved domain, infer from cert path
cert_path=$(grep "cert:" /etc/hysteria/config.yaml 2>/dev/null | awk '{print $2}')
if [[ "$cert_path" == "/etc/hysteria/cert.crt" ]]; then
hy_domain="www.bing.com"
else
# Default value
hy_domain="www.bing.com"
fi
# Save to persisted file
echo "$hy_domain" > /etc/hysteria/domain.txt
fi

# Check feature flags
[[ -f /etc/hysteria/obfs.txt ]] && use_obfs=true && obfs_pwd=$(cat /etc/hysteria/obfs.txt) || use_obfs=false
[[ -f /etc/hysteria/alpn.txt ]] && use_alpn=true && alpn_str=$(cat /etc/hysteria/alpn.txt) || use_alpn=false
if [[ -f /etc/hysteria/bandwidth.txt ]]; then
use_bandwidth=true
bandwidth=$(cat /etc/hysteria/bandwidth.txt)
up_mbps=$(echo $bandwidth | cut -d, -f1)
down_mbps=$(echo $bandwidth | cut -d, -f2)
else
use_bandwidth=false
up_mbps=0
down_mbps=0
fi

if [[ -f /etc/hysteria/port_hopping.txt ]]; then
port_range=$(cat /etc/hysteria/port_hopping.txt)
firstport=$(echo $port_range | cut -d- -f1)
endport=$(echo $port_range | cut -d- -f2)
else
port_range=""
firstport=""
endport=""
fi
}

# Create iptables auto-restore service (improved)
create_iptables_service(){
green "Creating iptables auto-restore service..."

# Create dedicated rule-restore script
cat > /etc/hysteria/restore-iptables.sh << 'EOF'
#!/bin/bash
# Clean old rules to avoid duplicates
iptables -t nat -D PREROUTING -p udp -m comment --comment "hysteria2" -j DNAT 2>/dev/null
ip6tables -t nat -D PREROUTING -p udp -m comment --comment "hysteria2" -j DNAT 2>/dev/null

if [ -f /etc/hysteria/port_hopping.txt ] && [ -f /etc/hysteria/config.yaml ]; then
port_range=$(cat /etc/hysteria/port_hopping.txt)
firstport=${port_range%-*}
endport=${port_range#*-}
# Robust port extraction
port=$(grep "^listen:" /etc/hysteria/config.yaml | grep -oP ':\K[0-9]+' | head -1)

if [ -n "$port" ] && [ -n "$firstport" ] && [ -n "$endport" ]; then
iptables -t nat -A PREROUTING -p udp --dport $firstport:$endport -m comment --comment "hysteria2" -j DNAT --to-destination :$port
ip6tables -t nat -A PREROUTING -p udp --dport $firstport:$endport -m comment --comment "hysteria2" -j DNAT --to-destination :$port
fi
fi
EOF
chmod +x /etc/hysteria/restore-iptables.sh

# Create simplified systemd service
cat > /etc/systemd/system/hysteria-iptables.service << 'EOF'
[Unit]
Description=Hysteria 2 Port Hopping iptables Rules
After=network.target
Before=hysteria-server.service

[Service]
Type=oneshot
ExecStart=/etc/hysteria/restore-iptables.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable hysteria-iptables.service
green "iptables auto-restore service created"
echo ""
read -p "Press Enter to continue..."
return
}

# Create service dependency configuration
create_service_dependencies(){
green "Configuring service dependencies..."

mkdir -p /etc/systemd/system/hysteria-server.service.d
cat > /etc/systemd/system/hysteria-server.service.d/override.conf << 'EOF'
[Unit]
After=network-online.target
Wants=network-online.target hysteria-iptables.service
Requires=network-online.target

[Service]
Restart=always
RestartSec=10s
LimitNOFILE=1000000
LimitNPROC=1000000
EOF

systemctl daemon-reload
green "Service dependency configuration complete"
echo ""
read -p "Press Enter to continue..."
return
}

# System optimization - focus on UDP/network (improved)
optimize_system(){
green "Optimizing system network parameters (UDP/QUIC focus)..."

# Check if already optimized
if grep -q "# Hysteria 2 UDP/QUIC" /etc/sysctl.conf 2>/dev/null; then
yellow "System already optimized. Re-apply optimizations? (y/N)"
read -r reopt
[[ "$reopt" != "y" && "$reopt" != "Y" ]] && return
fi

# Remove old optimization config if present
sed -i '/# Hysteria 2 UDP\/QUIC/,/# End of Hysteria 2 optimization/d' /etc/sysctl.conf 2>/dev/null

# UDP and QUIC tuning
cat >> /etc/sysctl.conf << 'EOF'

# Hysteria 2 UDP/QUIC tuning
# UDP buffer tuning
net.core.rmem_max=134217728
net.core.wmem_max=134217728
net.core.rmem_default=65536
net.core.wmem_default=65536
net.core.netdev_max_backlog=4096
net.ipv4.udp_rmem_min=8192
net.ipv4.udp_wmem_min=8192

# Increase UDP buffers
net.ipv4.udp_mem=65536 131072 262144
net.unix.max_dgram_qlen=50

# IP forwarding
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1

# Network tuning
net.ipv4.tcp_syncookies=1
net.ipv4.tcp_fin_timeout=30
net.ipv4.tcp_keepalive_time=1200
net.ipv4.ip_local_port_range=10000 65000
net.ipv4.tcp_max_syn_backlog=8192
net.ipv4.tcp_max_tw_buckets=5000
net.ipv4.tcp_mtu_probing=1

# ICMP/DoS hardening
net.ipv4.icmp_echo_ignore_all=0
net.ipv4.icmp_echo_ignore_broadcasts=1
net.ipv4.icmp_ignore_bogus_error_responses=1

# Connection limits
net.core.somaxconn=4096
net.netfilter.nf_conntrack_max=2000000
net.nf_conntrack_max=2000000

# QUIC-related settings
net.core.netdev_budget=600
net.core.netdev_budget_usecs=20000
# End of Hysteria 2 optimization
EOF

# Apply settings
sysctl -p >/dev/null 2>&1

# Increase file descriptor limits
if ! grep -q "* soft nofile" /etc/security/limits.conf; then
cat >> /etc/security/limits.conf << 'EOF'
* soft nofile 1000000
* hard nofile 1000000
* soft nproc 1000000
* hard nproc 1000000
root soft nofile 1000000
root hard nofile 1000000
root soft nproc 1000000
root hard nproc 1000000
EOF
fi

# Optimize systemd limits
if [[ ! -d /etc/systemd/system.conf.d ]]; then
mkdir -p /etc/systemd/system.conf.d
fi
cat > /etc/systemd/system.conf.d/99-limits.conf << 'EOF'
[Manager]
DefaultLimitNOFILE=1000000
DefaultLimitNPROC=1000000
EOF
systemctl daemon-reload

green "UDP/QUIC optimizations applied!"
yellow "Some optimizations may require a reboot to fully take effect"
return
}

# System optimization menu wrapper
optimize_system_menu(){
optimize_system
echo ""
read -p "Press Enter to return to main menu..."
return
}

# Set bandwidth limits
set_bandwidth(){
green "Set bandwidth limits (0 means unlimited)"
read -p "Set upload bandwidth (mbps) [default: 0]: " up_mbps
[[ -z $up_mbps ]] && up_mbps=0

read -p "Set download bandwidth (mbps) [default: 0]: " down_mbps
[[ -z $down_mbps ]] && down_mbps=0

# Validate numeric input
if ! [[ "$up_mbps" =~ ^[0-9]+$ ]]; then
yellow "Invalid upload value, set to 0"
up_mbps=0
fi

if ! [[ "$down_mbps" =~ ^[0-9]+$ ]]; then
yellow "Invalid download value, set to 0"
down_mbps=0
fi

if [[ $up_mbps -gt 0 ]] || [[ $down_mbps -gt 0 ]]; then
use_bandwidth=true
yellow "Bandwidth limits applied: up ${up_mbps} mbps, down ${down_mbps} mbps"
else
use_bandwidth=false
green "No bandwidth limits"
fi
}

# Certificate setup
inst_cert(){
green "Choose how to set up the TLS certificate:"
echo ""
echo -e " ${GREEN}1.${PLAIN} Self-signed certificate ${YELLOW}(default)${PLAIN}"
echo -e " ${GREEN}2.${PLAIN} Custom certificate paths"
echo ""
read -rp "Select an option [1-2]: " certInput
if [[ $certInput == 2 ]]; then
read -p "Enter path to certificate (.crt): " cert_path
yellow "Certificate path: $cert_path "
read -p "Enter path to private key (.key): " key_path
yellow "Key path: $key_path "
read -p "Enter certificate domain (SNI): " domain
yellow "Certificate domain: $domain"
hy_domain=$domain
# Check if certificate files exist
if [[ ! -f "$cert_path" ]]; then
red "Error: certificate file not found: $cert_path"
return 1
fi
if [[ ! -f "$key_path" ]]; then
red "Error: key file not found: $key_path"
return 1
fi
elif [[ $certInput == 1 ]] || [[ -z $certInput ]]; then
green "Using self-signed certificate as the Hysteria 2 server certificate"
cert_path="/etc/hysteria/cert.crt"
key_path="/etc/hysteria/private.key"

# Ensure openssl installed
if ! command -v openssl &>/dev/null; then
yellow "Installing openssl..."
${PACKAGE_INSTALL} openssl
fi

openssl ecparam -genkey -name prime256v1 -out /etc/hysteria/private.key
openssl req -new -x509 -days 36500 -key /etc/hysteria/private.key -out /etc/hysteria/cert.crt -subj "/CN=www.bing.com"
chmod 644 /etc/hysteria/cert.crt
chmod 644 /etc/hysteria/private.key
hy_domain="www.bing.com"
domain="www.bing.com"
fi

# Persist domain to file
echo "$hy_domain" > /etc/hysteria/domain.txt
}

# Port selection (improved)
inst_port(){
# Only clean rules added by this script to avoid affecting other services
# Clean IPv4/IPv6 rules with the specific comment: hysteria2
while iptables -t nat -D PREROUTING -p udp -m comment --comment "hysteria2" -j DNAT >/dev/null 2>&1; do :; done
while ip6tables -t nat -D PREROUTING -p udp -m comment --comment "hysteria2" -j DNAT >/dev/null 2>&1; do :; done

while true; do
read -p "Set Hysteria 2 port [1-65535] (Enter for random): " port
[[ -z $port ]] && port=$(shuf -i 2000-65535 -n 1)

# Validate numeric input
if ! [[ "$port" =~ ^[0-9]+$ ]]; then
red "Please enter a valid numeric port"
continue
fi

# Validate port range
if [[ $port -lt 1 ]] || [[ $port -gt 65535 ]]; then
red "Port must be between 1 and 65535"
continue
fi

# Check if port is in use
if ss -tunlp | grep -q ":$port "; then
red "Port $port is in use; choose another"
continue
fi

break
done

yellow "Using Hysteria 2 server port: $port"
inst_jump
}

inst_jump(){
green "Hysteria 2 port modes:"
echo ""
echo -e " ${GREEN}1.${PLAIN} Single port ${YELLOW}(default)${PLAIN}"
echo -e " ${GREEN}2.${PLAIN} Port hopping"
echo ""
read -rp "Select an option [1-2]: " jumpInput
if [[ $jumpInput == 2 ]]; then
read -p "Set start of port range (recommend 10000-65535): " firstport
read -p "Set end of port range (recommend 10000-65535, must be > start): " endport
if [[ $firstport -ge $endport ]]; then
until [[ $firstport -lt $endport ]]; do
red "Start port must be less than end port; please re-enter both"
read -p "Set start of port range (recommend 10000-65535): " firstport
read -p "Set end of port range (recommend 10000-65535, must be > start): " endport
done
fi
# Clean existing rules if any
iptables -t nat -D PREROUTING -p udp -m comment --comment "hysteria2" -j DNAT 2>/dev/null
ip6tables -t nat -D PREROUTING -p udp -m comment --comment "hysteria2" -j DNAT 2>/dev/null
# Add new rules (with comment tag)
iptables -t nat -A PREROUTING -p udp --dport $firstport:$endport -m comment --comment "hysteria2" -j DNAT --to-destination :$port
ip6tables -t nat -A PREROUTING -p udp --dport $firstport:$endport -m comment --comment "hysteria2" -j DNAT --to-destination :$port
save_iptables_rules
# Save port hopping info to file
echo "$firstport-$endport" > /etc/hysteria/port_hopping.txt
else
green "Continuing with single port mode"
# Clear port hopping info
rm -f /etc/hysteria/port_hopping.txt
fi
}

inst_pwd(){
read -p "Set Hysteria 2 password (Enter for random): " auth_pwd
[[ -z $auth_pwd ]] && auth_pwd=$(date +%s%N | md5sum | cut -c 1-8)
yellow "Password for Hysteria 2 server: $auth_pwd"
}

inst_obfs(){
green "Enable obfuscation?"
echo ""
echo -e " ${GREEN}1.${PLAIN} Enable obfuscation ${YELLOW}(default)${PLAIN}"
echo -e " ${GREEN}2.${PLAIN} Disable obfuscation"
echo ""
read -rp "Select an option [1-2]: " obfsInput
if [[ $obfsInput == 2 ]]; then
green "Obfuscation disabled"
use_obfs=false
else
read -p "Set obfuscation password (Enter for random): " obfs_pwd
[[ -z $obfs_pwd ]] && obfs_pwd=$(date +%s%N | md5sum | cut -c 1-12)
yellow "Obfuscation password: $obfs_pwd"
use_obfs=true
fi
}

inst_alpn(){
green "Enable ALPN?"
echo ""
echo -e " ${GREEN}1.${PLAIN} Enable ALPN (h3, h2, http/1.1) ${YELLOW}(default)${PLAIN}"
echo -e " ${GREEN}2.${PLAIN} Disable ALPN"
echo ""
read -rp "Select an option [1-2]: " alpnInput
if [[ $alpnInput == 2 ]]; then
green "ALPN disabled"
use_alpn=false
else
yellow "ALPN enabled: h3, h2, http/1.1"
use_alpn=true
alpn_str="h3,h2,http/1.1"
fi
}

# Improved masquerade site selection
inst_site(){
green "Choose the masquerade site for Hysteria 2:"
echo ""

# Always offer cert domain as option 0 (recommended)
if [[ -n $hy_domain ]]; then
echo -e " ${GREEN}0.${PLAIN} Use certificate domain ${YELLOW}(recommended)${PLAIN}: $hy_domain"
fi

echo -e " ${GREEN}1.${PLAIN} www.bing.com"
echo -e " ${GREEN}2.${PLAIN} www.apple.com"
echo -e " ${GREEN}3.${PLAIN} www.amazon.com"
echo -e " ${GREEN}4.${PLAIN} www.microsoft.com"
echo -e " ${GREEN}5.${PLAIN} www.google.com"
echo -e " ${GREEN}6.${PLAIN} www.yahoo.com"
echo -e " ${GREEN}7.${PLAIN} Custom site"
echo ""

if [[ -n $hy_domain ]]; then
echo -ne "Select an option [0-7] (${YELLOW}recommend 0 - cert domain${PLAIN}): "
read -r siteChoice
[[ -z $siteChoice ]] && siteChoice=0
else
read -rp "Select an option [1-7]: " siteChoice
fi

case $siteChoice in
0)
if [[ -n $hy_domain ]]; then
proxysite="$hy_domain"
green "Using certificate domain as masquerade site (recommended)"
else
proxysite="www.bing.com"
fi
;;
1) proxysite="www.bing.com" ;;
2) proxysite="www.apple.com" ;;
3) proxysite="www.amazon.com" ;;
4) proxysite="www.microsoft.com" ;;
5) proxysite="www.google.com" ;;
6) proxysite="www.yahoo.com" ;;
7)
read -rp "Enter masquerade site (without https://): " proxysite
[[ -z $proxysite ]] && proxysite="www.bing.com"
;;
*)
if [[ -n $hy_domain ]]; then
proxysite="$hy_domain"
green "Defaulting to certificate domain as masquerade site"
else
proxysite="www.bing.com"
fi
;;
esac

yellow "Masquerade site for Hysteria 2: $proxysite"
}

# Improved install function
insthysteria(){
warpv6=""
warpv4=""
optChoice=""

# Check if already installed
if [[ -f "/usr/local/bin/hysteria" ]] && [[ -f "/etc/hysteria/config.yaml" ]]; then
yellow "Hysteria 2 is already installed. Reinstall? (y/N)"
read -r reinstall
[[ "$reinstall" != "y" && "$reinstall" != "Y" ]] && return
fi

# Check dependencies
check_dependencies

warpv6=$(curl -s6m8 https://www.cloudflare.com/cdn-cgi/trace -k | grep warp | cut -d= -f2)
warpv4=$(curl -s4m8 https://www.cloudflare.com/cdn-cgi/trace -k | grep warp | cut -d= -f2)
if [[ $warpv4 =~ on|plus || $warpv6 =~ on|plus ]]; then
echo "Warp detected (on/plus). Temporarily disable Warp to get real egress IP?"
echo -e " ${GREEN}1.${PLAIN} Yes (temporarily disable, then restore)"
echo -e " ${GREEN}2.${PLAIN} No (keep current) ${YELLOW}(default)${PLAIN}"
read -rp "Select an option [1-2]: " warpToggle
if [[ "$warpToggle" == "1" ]]; then
# Record current state
local warpgo_active=false
local wgquick_active=false
local wgcf_iface_up=false
if systemctl is-active warp-go >/dev/null 2>&1; then warpgo_active=true; fi
if systemctl is-active wg-quick@wgcf >/dev/null 2>&1; then wgquick_active=true; fi
if ip link show wgcf >/dev/null 2>&1; then
if ip -br link show wgcf 2>/dev/null | grep -qw UP; then wgcf_iface_up=true; fi
fi

# Temporarily disable Warp
if [[ "$wgquick_active" == true ]]; then
systemctl stop wg-quick@wgcf >/dev/null 2>&1
elif [[ "$wgcf_iface_up" == true ]]; then
wg-quick down wgcf >/dev/null 2>&1 || true
fi
if [[ "$warpgo_active" == true ]]; then
systemctl stop warp-go >/dev/null 2>&1 || true
fi

# Fetch real IP
realip

# Restore previous state
if [[ "$warpgo_active" == true ]]; then
systemctl start warp-go >/dev/null 2>&1 || true
fi
if [[ "$wgquick_active" == true ]]; then
systemctl start wg-quick@wgcf >/dev/null 2>&1 || true
elif [[ "$wgcf_iface_up" == true ]]; then
wg-quick up wgcf >/dev/null 2>&1 || true
fi
else
realip
fi
else
realip
fi

${PACKAGE_UPDATE}

# Install required packages
if [[ ${SYSTEM} == "CentOS" ]]; then
${PACKAGE_INSTALL} curl wget sudo qrencode procps iptables-services
else
# Ubuntu - improved package install
${PACKAGE_INSTALL} curl wget sudo qrencode procps iptables bc
# Non-interactive install for iptables-persistent
echo iptables-persistent iptables-persistent/autosave_v4 boolean true | debconf-set-selections
echo iptables-persistent iptables-persistent/autosave_v6 boolean true | debconf-set-selections
DEBIAN_FRONTEND=noninteractive ${PACKAGE_INSTALL} iptables-persistent netfilter-persistent
fi

# Ask to apply system optimizations
green "Apply system network optimizations? (recommended)"
echo ""
echo -e " ${GREEN}1.${PLAIN} Yes ${YELLOW}(recommended, UDP/QUIC tuning)${PLAIN}"
echo -e " ${GREEN}2.${PLAIN} No"
echo ""
read -rp "Select an option [1-2]: " optChoice
if [[ $optChoice == 1 ]] || [[ -z $optChoice ]]; then
optimize_system
fi

# Download and install Hysteria (with local cache + mirror fallback)
yellow "Preparing to install Hysteria 2 (cache + mirror)..."
CACHE_DIR="/etc/hysteria/cache"
CACHE_FILE="$CACHE_DIR/install_server.sh"
PRIMARY_URL="https://raw.githubusercontent.com/Misaka-blog/hysteria-install/main/hy2/install_server.sh"
MIRROR_URL="https://cdn.jsdelivr.net/gh/Misaka-blog/hysteria-install@main/hy2/install_server.sh"
mkdir -p "$CACHE_DIR"

use_cached=false
if [[ -s "$CACHE_FILE" ]]; then
yellow "Found local cache, will use: $CACHE_FILE"
use_cached=true
else
yellow "Downloading installer from primary source..."
if wget -q -O "$CACHE_FILE" --no-check-certificate "$PRIMARY_URL"; then
use_cached=true
else
yellow "Primary download failed, trying mirror..."
if curl -fsSL "$MIRROR_URL" -o "$CACHE_FILE"; then
use_cached=true
else
red "Failed to download installer script."
yellow "You can download it manually to $CACHE_FILE and retry:"
echo " 1) $PRIMARY_URL"
echo " 2) $MIRROR_URL"
read -p "Press Enter to return to main menu..."
return
fi
fi
fi

# Basic validation (syntax check)
if [[ "$use_cached" == true ]]; then
if ! bash -n "$CACHE_FILE" 2>/dev/null; then
red "Syntax check failed on cached/downloaded script; aborting for safety."
yellow "Please check $CACHE_FILE or remove and retry."
read -p "Press Enter to return to main menu..."
return
fi
bash "$CACHE_FILE"
fi

if [[ -f "/usr/local/bin/hysteria" ]]; then
green "Hysteria 2 installed successfully!"
else
red "Hysteria 2 installation failed!"
read -p "Press Enter to return to main menu..."
return
fi

# Prompt user for Hysteria configuration
inst_cert
if [[ $? -ne 0 ]]; then
red "Certificate configuration failed"
read -p "Press Enter to return to main menu..."
return
fi
inst_port
inst_pwd
inst_obfs
inst_alpn
inst_site
set_bandwidth

# Validate required variables
if [[ -z "$port" ]] || [[ -z "$cert_path" ]] || [[ -z "$key_path" ]] || [[ -z "$auth_pwd" ]]; then
red "Error: configuration is incomplete"
echo "Port: $port"
echo "Certificate: $cert_path"
echo "Key: $key_path"
echo "Password: $auth_pwd"
read -p "Press Enter to return to main menu..."
return
fi

# Write Hysteria server config
cat << EOF > /etc/hysteria/config.yaml
listen: :${port}
tls:
cert: ${cert_path}
key: ${key_path}
EOF

# If ALPN is enabled, append ALPN config
if [[ $use_alpn == true ]]; then
cat << EOF >> /etc/hysteria/config.yaml
alpn:
- h3
- h2
- http/1.1
EOF
fi

# Append QUIC tuning
cat << EOF >> /etc/hysteria/config.yaml
quic:
initStreamReceiveWindow: 26843545
maxStreamReceiveWindow: 26843545
initConnReceiveWindow: 67108864
maxConnReceiveWindow: 67108864
maxIdleTimeout: 30s
maxIncomingStreams: 1024
disablePathMTUDiscovery: false
EOF

# If bandwidth limits set, append bandwidth section
if [[ $use_bandwidth == true ]]; then
# Ensure variables have values
[[ -z "$up_mbps" ]] && up_mbps=0
[[ -z "$down_mbps" ]] && down_mbps=0
cat << EOF >> /etc/hysteria/config.yaml
bandwidth:
up: ${up_mbps} mbps
down: ${down_mbps} mbps
EOF
fi

cat << EOF >> /etc/hysteria/config.yaml
auth:
type: password
password: ${auth_pwd}
masquerade:
type: proxy
proxy:
url: https://${proxysite}
rewriteHost: true
EOF

# If obfuscation enabled, append obfuscation section
if [[ $use_obfs == true ]]; then
cat << EOF >> /etc/hysteria/config.yaml
obfs:
type: salamander
salamander:
password: ${obfs_pwd}
EOF
fi

# Wrap IPv6 in [] for host:port formatting
if [[ -n $(echo $ip | grep ":") ]]; then
last_ip="[$ip]"
else
last_ip=$ip
fi
mkdir -p /root/hy

# Generate client configs
generate_client_configs

# Save auxiliary config files
if [[ $use_obfs == true ]]; then
echo "$obfs_pwd" > /etc/hysteria/obfs.txt
else
rm -f /etc/hysteria/obfs.txt
fi

if [[ $use_alpn == true ]]; then
echo "$alpn_str" > /etc/hysteria/alpn.txt
else
rm -f /etc/hysteria/alpn.txt
fi

if [[ $use_bandwidth == true ]]; then
echo "$up_mbps,$down_mbps" > /etc/hysteria/bandwidth.txt
else
rm -f /etc/hysteria/bandwidth.txt
fi

# Save domain
echo "$hy_domain" > /etc/hysteria/domain.txt

# Start service
systemctl daemon-reload
systemctl enable hysteria-server
systemctl start hysteria-server

# Check service status a few times
sleep 2
service_active=false
for i in {1..3}; do
if systemctl is-active hysteria-server >/dev/null 2>&1; then
service_active=true
break
fi
sleep 1
done

if [[ "$service_active" == true ]] && [[ -f '/etc/hysteria/config.yaml' ]]; then
green "Hysteria 2 service started successfully"
else
red "Hysteria 2 service failed to start"
echo ""
yellow "Config file contents:"
cat /etc/hysteria/config.yaml
echo ""
yellow "Service status:"
systemctl status hysteria-server --no-pager -l
echo ""
red "Please review the configuration and error messages above"
read -p "Press Enter to return to main menu..."
return
fi

red "======================================================================================"
green "Hysteria 2 proxy service installation complete"
yellow "Server optimization status:"
green "UDP/QUIC optimization enabled"
if [[ $use_obfs == true ]]; then
yellow "Obfuscation enabled. Password: $obfs_pwd"
fi
if [[ $use_alpn == true ]]; then
yellow "ALPN enabled. Protocols: h3, h2, http/1.1"
fi
if [[ $use_bandwidth == true ]]; then
yellow "Bandwidth limits set: up ${up_mbps} mbps, down ${down_mbps} mbps"
fi
yellow "Masquerade site: $proxysite"
yellow "Client YAML saved to /root/hy/hy-client.yaml. Contents:"
red "$(cat /root/hy/hy-client.yaml)"
yellow "Client JSON saved to /root/hy/hy-client.json. Contents:"
red "$(cat /root/hy/hy-client.json)"
yellow "Share link saved to /root/hy/url.txt:"
red "$(cat /root/hy/url.txt)"
echo ""
green "Installation complete!"
echo ""
read -p "Press Enter to return to main menu..."
return
}

# Generate client configs (extracted as a function)
generate_client_configs(){
# Generate client YAML config
if [[ -n $firstport ]]; then
# Port hopping mode
cat << EOF > /root/hy/hy-client.yaml
server: $last_ip:$port
auth: $auth_pwd
tls:
sni: $hy_domain
insecure: true
fingerprint: chrome
EOF
if [[ $use_alpn == true ]]; then
cat << EOF >> /root/hy/hy-client.yaml
alpn:
- h3
- h2
- http/1.1
EOF
fi
cat << EOF >> /root/hy/hy-client.yaml
quic:
initStreamReceiveWindow: 26843545
maxStreamReceiveWindow: 26843545
initConnReceiveWindow: 67108864
maxConnReceiveWindow: 67108864
fastOpen: true
lazy: true
EOF
if [[ $use_bandwidth == true ]]; then
cat << EOF >> /root/hy/hy-client.yaml
bandwidth:
up: $up_mbps mbps
down: $down_mbps mbps
EOF
fi
if [[ $use_obfs == true ]]; then
cat << EOF >> /root/hy/hy-client.yaml
obfs:
type: salamander
salamander:
password: $obfs_pwd
EOF
fi
cat << EOF >> /root/hy/hy-client.yaml
socks5:
listen: 127.0.0.1:5678
http:
listen: 127.0.0.1:8080
transport:
udp:
hopInterval: 30s
hopPorts: $firstport-$endport
EOF
else
# Single port mode
cat << EOF > /root/hy/hy-client.yaml
server: $last_ip:$port
auth: $auth_pwd
tls:
sni: $hy_domain
insecure: true
fingerprint: chrome
EOF
if [[ $use_alpn == true ]]; then
cat << EOF >> /root/hy/hy-client.yaml
alpn:
- h3
- h2
- http/1.1
EOF
fi
cat << EOF >> /root/hy/hy-client.yaml
quic:
initStreamReceiveWindow: 26843545
maxStreamReceiveWindow: 26843545
initConnReceiveWindow: 67108864
maxConnReceiveWindow: 67108864
fastOpen: true
lazy: true
EOF
if [[ $use_bandwidth == true ]]; then
cat << EOF >> /root/hy/hy-client.yaml
bandwidth:
up: $up_mbps mbps
down: $down_mbps mbps
EOF
fi
if [[ $use_obfs == true ]]; then
cat << EOF >> /root/hy/hy-client.yaml
obfs:
type: salamander
salamander:
password: $obfs_pwd
EOF
fi
cat << EOF >> /root/hy/hy-client.yaml
socks5:
listen: 127.0.0.1:5678
http:
listen: 127.0.0.1:8080
EOF
fi

# Generate JSON config
generate_json_config

# Generate share link
generate_share_link
}

# Generate JSON config
generate_json_config(){
json_content=""

if [[ -n $firstport ]]; then
# Port hopping mode
json_content='{
"server": "'$last_ip':'$port'",
"auth": "'$auth_pwd'",
"tls": {
"sni": "'$hy_domain'",
"insecure": true,
"fingerprint": "chrome"'

if [[ $use_alpn == true ]]; then
json_content+=',
"alpn": ["h3", "h2", "http/1.1"]'
fi

json_content+='
},
"quic": {
"initStreamReceiveWindow": 26843545,
"maxStreamReceiveWindow": 26843545,
"initConnReceiveWindow": 67108864,
"maxConnReceiveWindow": 67108864
},
"fastOpen": true,
"lazy": true'

if [[ $use_bandwidth == true ]]; then
json_content+=',
"bandwidth": {
"up": "'$up_mbps' mbps",
"down": "'$down_mbps' mbps"
}'
fi

if [[ $use_obfs == true ]]; then
json_content+=',
"obfs": {
"type": "salamander",
"salamander": {
"password": "'$obfs_pwd'"
}
}'
fi

json_content+=',
"socks5": {
"listen": "127.0.0.1:5678"
},
"http": {
"listen": "127.0.0.1:8080"
},
"transport": {
"udp": {
"hopInterval": "30s",
"hopPorts": "'$firstport'-'$endport'"
}
}
}'
echo "$json_content" > /root/hy/hy-client.json
else
# Single port mode
json_content='{
"server": "'$last_ip':'$port'",
"auth": "'$auth_pwd'",
"tls": {
"sni": "'$hy_domain'",
"insecure": true,
"fingerprint": "chrome"'

if [[ $use_alpn == true ]]; then
json_content+=',
"alpn": ["h3", "h2", "http/1.1"]'
fi

json_content+='
},
"quic": {
"initStreamReceiveWindow": 26843545,
"maxStreamReceiveWindow": 26843545,
"initConnReceiveWindow": 67108864,
"maxConnReceiveWindow": 67108864
},
"fastOpen": true,
"lazy": true'

if [[ $use_bandwidth == true ]]; then
json_content+=',
"bandwidth": {
"up": "'$up_mbps' mbps",
"down": "'$down_mbps' mbps"
}'
fi

if [[ $use_obfs == true ]]; then
json_content+=',
"obfs": {
"type": "salamander",
"salamander": {
"password": "'$obfs_pwd'"
}
}'
fi

json_content+=',
"socks5": {
"listen": "127.0.0.1:5678"
},
"http": {
"listen": "127.0.0.1:8080"
}
}'
echo "$json_content" > /root/hy/hy-client.json
fi
}

# Generate share link
generate_share_link(){
url=""
alpn_encoded=""

url="hysteria2://$auth_pwd@$last_ip:$port/?insecure=1&sni=$hy_domain"

# Add port hopping params
if [[ -n $firstport ]]; then
url="${url}&mport=$firstport-$endport"
fi

# Add obfuscation params
if [[ $use_obfs == true ]]; then
url="${url}&obfs=salamander&obfs-password=$obfs_pwd"
fi

# Add ALPN params
if [[ $use_alpn == true ]]; then
alpn_encoded=$(echo "$alpn_str" | sed 's/,/%2C/g')
url="${url}&alpn=$alpn_encoded"
fi

url="${url}#Hysteria2"
echo $url > /root/hy/url.txt
}

# Uninstall - do not remove this script
unsthysteria(){
yellow "Are you sure you want to uninstall Hysteria 2? (y/N)"
read -r confirm
if [[ $confirm != "y" && $confirm != "Y" ]]; then
return
fi

systemctl stop hysteria-server.service >/dev/null 2>&1
systemctl disable hysteria-server.service >/dev/null 2>&1
systemctl stop hysteria-iptables.service >/dev/null 2>&1
systemctl disable hysteria-iptables.service >/dev/null 2>&1

rm -f /lib/systemd/system/hysteria-server.service /lib/systemd/system/hysteria-server@.service
rm -f /etc/systemd/system/hysteria-iptables.service
rm -rf /usr/local/bin/hysteria /etc/hysteria /root/hy
rm -rf /etc/systemd/system/hysteria-server.service.d

# Do not delete this script itself!
# rm -rf /root/hysteria.sh

# Clean iptables rules
iptables -t nat -D PREROUTING -p udp -m comment --comment "hysteria2" -j DNAT 2>/dev/null
ip6tables -t nat -D PREROUTING -p udp -m comment --comment "hysteria2" -j DNAT 2>/dev/null
save_iptables_rules

# Clean system optimization config
yellow "Cleaning system optimization config..."
if grep -q "# Hysteria 2 UDP/QUIC" /etc/sysctl.conf 2>/dev/null; then
# Remove optimization config
sed -i '/# Hysteria 2 UDP\/QUIC/,/# End of Hysteria 2 optimization/d' /etc/sysctl.conf
sysctl -p >/dev/null 2>&1
green "System optimization config cleaned"
else
yellow "System config does not contain Hysteria 2 optimization entries"
fi

systemctl daemon-reload

green "Hysteria 2 has been completely uninstalled"
yellow "Note: This script file was retained; you can run it again to install"
echo ""
read -p "Press Enter to return to main menu..."
return
}

starthysteria(){
systemctl start hysteria-server
systemctl enable hysteria-server >/dev/null 2>&1
}

stophysteria(){
systemctl stop hysteria-server
systemctl disable hysteria-server >/dev/null 2>&1
}

hysteriaswitch(){
switchInput=""
yellow "Choose an action:"
echo ""
echo -e " ${GREEN}1.${PLAIN} Start Hysteria 2"
echo -e " ${GREEN}2.${PLAIN} Stop Hysteria 2"
echo -e " ${GREEN}3.${PLAIN} Restart Hysteria 2"
echo -e " ${GREEN}0.${PLAIN} Back to main menu"
echo ""
read -rp "Select an option [0-3]: " switchInput
case $switchInput in
1 )
starthysteria
green "Hysteria 2 started"
# Check service status
sleep 1
if systemctl is-active hysteria-server >/dev/null 2>&1; then
green "Service is running"
else
red "Service failed to start, details:"
systemctl status hysteria-server --no-pager -l
fi
;;
2 )
stophysteria
green "Hysteria 2 stopped"
;;
3 )
stophysteria
sleep 1
starthysteria
green "Hysteria 2 restarted"
# Check service status
sleep 1
if systemctl is-active hysteria-server >/dev/null 2>&1; then
green "Service is running"
else
red "Service failed to restart, details:"
systemctl status hysteria-server --no-pager -l
fi
;;
0 ) return ;;
* )
red "Invalid option"
;;
esac
echo ""
read -p "Press Enter to return to main menu..."
return
}

# Reload service function
reload_service(){
yellow "Reloading service..."
systemctl daemon-reload
systemctl restart hysteria-server
sleep 2
if systemctl is-active hysteria-server >/dev/null 2>&1; then
green "Service reloaded successfully!"
else
red "Service reload failed, please check configuration"
systemctl status hysteria-server --no-pager -l
fi
}

# regenerate_client_config
regenerate_client_config(){
# Save current config state
yellow "Regenerating client configuration..."

# Re-read all configuration from files
get_config_info

# Ensure all required variables are present
if [[ -z "$port" ]] || [[ -z "$auth_pwd" ]] || [[ -z "$hy_domain" ]]; then
red "Error: failed to read complete configuration"
red "Port: $port, Password: $auth_pwd, Domain: $hy_domain"
return 1
fi

# Remove old client configs
rm -f /root/hy/hy-client.yaml /root/hy/hy-client.json /root/hy/url.txt

# Regenerate client configs
generate_client_configs

green "Client configuration regenerated"
return 0
}

# Change port
changeport(){
oldport=$(get_hysteria_port)

if [[ -z "$oldport" ]] || [[ "$oldport" == "0" ]]; then
red "Error: cannot read current port"
return
fi

read -p "Set Hysteria 2 port [1-65535] (Enter for random): " newport
[[ -z $newport ]] && newport=$(shuf -i 2000-65535 -n 1)

# Validate port
if ! [[ "$newport" =~ ^[0-9]+$ ]] || [[ $newport -lt 1 ]] || [[ $newport -gt 65535 ]]; then
red "Invalid port"
return
fi

# Check if port is in use
if ss -tunlp | grep -q ":$newport "; then
red "Port $newport is in use"
return
fi

# Update configuration file
sed -i "s/:$oldport/:$newport/g" /etc/hysteria/config.yaml

# Verify change
newport_check=$(get_hysteria_port)
if [[ "$newport_check" != "$newport" ]]; then
red "Failed to change port"
return
fi

# Update iptables rules (if using port hopping)
if [[ -f /etc/hysteria/port_hopping.txt ]]; then
port_range=$(cat /etc/hysteria/port_hopping.txt)
firstport=$(echo $port_range | cut -d- -f1)
endport=$(echo $port_range | cut -d- -f2)
# Clean old rules
iptables -t nat -D PREROUTING -p udp -m comment --comment "hysteria2" -j DNAT 2>/dev/null
ip6tables -t nat -D PREROUTING -p udp -m comment --comment "hysteria2" -j DNAT 2>/dev/null
# Add new rules
iptables -t nat -A PREROUTING -p udp --dport $firstport:$endport -m comment --comment "hysteria2" -j DNAT --to-destination :$newport
ip6tables -t nat -A PREROUTING -p udp --dport $firstport:$endport -m comment --comment "hysteria2" -j DNAT --to-destination :$newport
save_iptables_rules
fi

# Regenerate client configs
regenerate_client_config

# Restart service
reload_service

green "Hysteria 2 port changed to: $newport"
yellow "Please use the new client config file"
showconf_menu
}

# Change password
changepasswd(){
oldpasswd=$(get_hysteria_password)

if [[ -z "$oldpasswd" ]]; then
red "Error: cannot read current password"
return
fi

read -p "Set Hysteria 2 password (Enter for random): " newpasswd
[[ -z $newpasswd ]] && newpasswd=$(date +%s%N | md5sum | cut -c 1-8)

# Update server config
sed -i "/^auth:/,/^[^ ]/ s/password: .*/password: $newpasswd/" /etc/hysteria/config.yaml

# Verify change
newpasswd_check=$(get_hysteria_password)
if [[ "$newpasswd_check" != "$newpasswd" ]]; then
red "Failed to change password"
return
fi

# Regenerate client configs
regenerate_client_config

# Restart service
reload_service

green "Hysteria 2 server password changed to: $newpasswd"
yellow "Please use the new client config file"
showconf_menu
}

# Change obfuscation
changeobfs(){
if [[ -f /etc/hysteria/obfs.txt ]]; then
old_obfs=$(cat /etc/hysteria/obfs.txt)
green "Obfuscation is enabled. Password: $old_obfs"
echo ""
echo -e " ${GREEN}1.${PLAIN} Change obfuscation password"
echo -e " ${GREEN}2.${PLAIN} Disable obfuscation"
echo ""
read -rp "Select an option [1-2]: " obfsAction

if [[ $obfsAction == 1 ]]; then
read -p "Set new obfuscation password (Enter for random): " new_obfs
[[ -z $new_obfs ]] && new_obfs=$(date +%s%N | md5sum | cut -c 1-12)

# Update server config
if grep -q "^obfs:" /etc/hysteria/config.yaml; then
sed -i "/salamander:/,/^[^ ]/ s/password: .*/password: $new_obfs/" /etc/hysteria/config.yaml
else
# If no obfuscation section, add it
cat << EOF >> /etc/hysteria/config.yaml
obfs:
type: salamander
salamander:
password: $new_obfs
EOF
fi

echo "$new_obfs" > /etc/hysteria/obfs.txt

# Regenerate client configs
regenerate_client_config

reload_service
green "Obfuscation password updated to: $new_obfs"
else
# Remove obfuscation from server config
sed -i '/^obfs:/,/^[^ ]/d' /etc/hysteria/config.yaml
rm -f /etc/hysteria/obfs.txt

# Regenerate client configs
regenerate_client_config

reload_service
green "Obfuscation disabled"
fi
else
green "Obfuscation is disabled. Enable it?"
echo ""
echo -e " ${GREEN}1.${PLAIN} Enable obfuscation"
echo -e " ${GREEN}2.${PLAIN} Cancel"
echo ""
read -rp "Select an option [1-2]: " enableObfs

if [[ $enableObfs == 1 ]]; then
read -p "Set obfuscation password (Enter for random): " obfs_pwd
[[ -z $obfs_pwd ]] && obfs_pwd=$(date +%s%N | md5sum | cut -c 1-12)

# Add obfuscation to server config
cat << EOF >> /etc/hysteria/config.yaml
obfs:
type: salamander
salamander:
password: $obfs_pwd
EOF

echo "$obfs_pwd" > /etc/hysteria/obfs.txt

# Regenerate client configs
regenerate_client_config

reload_service
green "Obfuscation enabled. Password: $obfs_pwd"
fi
fi

yellow "Please use the new client config file"
showconf_menu
}

# Change ALPN
changealpn(){
if [[ -f /etc/hysteria/alpn.txt ]]; then
green "ALPN is enabled. Protocols: $(cat /etc/hysteria/alpn.txt)"
echo ""
echo -e " ${GREEN}1.${PLAIN} Disable ALPN"
echo -e " ${GREEN}2.${PLAIN} Cancel"
echo ""
read -rp "Select an option [1-2]: " alpnAction

if [[ $alpnAction == 1 ]]; then
# Remove ALPN from server config
sed -i '/^ alpn:/,/^ [^ ]/{/^ alpn:/d; /^ - /d}' /etc/hysteria/config.yaml
rm -f /etc/hysteria/alpn.txt

# Regenerate client configs
regenerate_client_config

reload_service
green "ALPN disabled"
fi
else
green "ALPN is disabled. Enable it?"
echo ""
echo -e " ${GREEN}1.${PLAIN} Enable ALPN (h3, h2, http/1.1)"
echo -e " ${GREEN}2.${PLAIN} Cancel"
echo ""
read -rp "Select an option [1-2]: " enableAlpn

if [[ $enableAlpn == 1 ]]; then
# Add ALPN under tls section
if grep -q "^tls:" /etc/hysteria/config.yaml; then
# Find 'key:' and append 'alpn' after it
sed -i "/^ key:/ a\\
alpn:\\
- h3\\
- h2\\
- http/1.1" /etc/hysteria/config.yaml
fi

echo "h3,h2,http/1.1" > /etc/hysteria/alpn.txt

# Regenerate client configs
regenerate_client_config

reload_service
green "ALPN enabled: h3, h2, http/1.1"
fi
fi

yellow "Please use the new client config file"
showconf_menu
}

# Change bandwidth
changebandwidth(){
if [[ -f /etc/hysteria/bandwidth.txt ]]; then
old_bandwidth=$(cat /etc/hysteria/bandwidth.txt)
old_up=$(echo $old_bandwidth | cut -d, -f1)
old_down=$(echo $old_bandwidth | cut -d, -f2)
green "Current bandwidth limits: up ${old_up} mbps, down ${old_down} mbps"
echo ""
echo -e " ${GREEN}1.${PLAIN} Update bandwidth limits"
echo -e " ${GREEN}2.${PLAIN} Remove bandwidth limits"
echo ""
read -rp "Select an option [1-2]: " bwAction

if [[ $bwAction == 1 ]]; then
read -p "Set upload bandwidth (mbps) [default: 0]: " new_up
[[ -z $new_up ]] && new_up=0
read -p "Set download bandwidth (mbps) [default: 0]: " new_down
[[ -z $new_down ]] && new_down=0

# Validate input
if ! [[ "$new_up" =~ ^[0-9]+$ ]]; then
new_up=0
fi
if ! [[ "$new_down" =~ ^[0-9]+$ ]]; then
new_down=0
fi

# Update configuration
sed -i '/^bandwidth:/,/^[^ ]/d' /etc/hysteria/config.yaml

if [[ $new_up -gt 0 ]] || [[ $new_down -gt 0 ]]; then
# Insert bandwidth config before 'auth'
sed -i "/^auth:/ i\\
bandwidth:\\
up: ${new_up} mbps\\
down: ${new_down} mbps" /etc/hysteria/config.yaml
echo "${new_up},${new_down}" > /etc/hysteria/bandwidth.txt
else
rm -f /etc/hysteria/bandwidth.txt
fi

green "Bandwidth limits updated"
else
# Remove bandwidth limits
sed -i '/^bandwidth:/,/^[^ ]/d' /etc/hysteria/config.yaml
rm -f /etc/hysteria/bandwidth.txt
green "Bandwidth limits removed"
fi
else
green "No bandwidth limits set. Configure now?"
echo ""
echo -e " ${GREEN}1.${PLAIN} Set bandwidth limits"
echo -e " ${GREEN}2.${PLAIN} Cancel"
echo ""
read -rp "Select an option [1-2]: " enableBW

if [[ $enableBW == 1 ]]; then
read -p "Set upload bandwidth (mbps) [default: 0]: " new_up
[[ -z $new_up ]] && new_up=0
read -p "Set download bandwidth (mbps) [default: 0]: " new_down
[[ -z $new_down ]] && new_down=0

# Validate input
if ! [[ "$new_up" =~ ^[0-9]+$ ]]; then
new_up=0
fi
if ! [[ "$new_down" =~ ^[0-9]+$ ]]; then
new_down=0
fi

if [[ $new_up -gt 0 ]] || [[ $new_down -gt 0 ]]; then
# Insert bandwidth config before 'auth'
sed -i "/^auth:/ i\\
bandwidth:\\
up: ${new_up} mbps\\
down: ${new_down} mbps" /etc/hysteria/config.yaml
echo "${new_up},${new_down}" > /etc/hysteria/bandwidth.txt
green "Bandwidth limits set"
else
green "Bandwidth not set"
fi
fi
fi

# Regenerate client configs
regenerate_client_config
reload_service

yellow "Please use the new client config file"
showconf_menu
}

change_cert(){
old_cert=""
old_key=""
old_hydomain=""
old_cert=$(cat /etc/hysteria/config.yaml | grep cert | awk -F " " '{print $2}')
old_key=$(cat /etc/hysteria/config.yaml | grep key | awk -F " " '{print $2}')
old_hydomain=$(cat /etc/hysteria/domain.txt 2>/dev/null)

inst_cert
if [[ $? -ne 0 ]]; then
red "Certificate configuration failed"
return
fi

sed -i "s!$old_cert!$cert_path!g" /etc/hysteria/config.yaml
sed -i "s!$old_key!$key_path!g" /etc/hysteria/config.yaml

# Save new domain
echo "$hy_domain" > /etc/hysteria/domain.txt

# Regenerate client configs
regenerate_client_config

reload_service
green "Hysteria 2 server certificate type updated"
yellow "Please use the new client config file"
showconf_menu
}

changeproxysite(){
oldproxysite=""
oldproxysite=$(cat /etc/hysteria/config.yaml | grep url | awk -F " " '{print $2}' | awk -F "https://" '{print $2}')

inst_site
# Update masquerade site in Hysteria config
sed -i "s#https://$oldproxysite#https://$proxysite#g" /etc/hysteria/config.yaml

# Regenerate client configs
regenerate_client_config
reload_service
green "Hysteria 2 masquerade site updated to: $proxysite"
}

changeconf(){
while true; do
confAnswer=""
clear
green "Hysteria 2 configuration changes:"
echo -e " ${GREEN}1.${PLAIN} Change port"
echo -e " ${GREEN}2.${PLAIN} Change password"
echo -e " ${GREEN}3.${PLAIN} Change certificate type"
echo -e " ${GREEN}4.${PLAIN} Change masquerade site"
echo -e " ${GREEN}5.${PLAIN} Change obfuscation settings"
echo -e " ${GREEN}6.${PLAIN} Change ALPN settings"
echo -e " ${GREEN}7.${PLAIN} Change bandwidth limits"
echo -e " ${GREEN}8.${PLAIN} Optimize system settings"
echo -e " ${GREEN}0.${PLAIN} Back to main menu"
echo ""
read -p " Select an option [0-8]: " confAnswer
case $confAnswer in
1 ) changeport ;;
2 ) changepasswd ;;
3 ) change_cert ;;
4 ) changeproxysite ;;
5 ) changeobfs ;;
6 ) changealpn ;;
7 ) changebandwidth ;;
8 ) optimize_system ;;
0 ) return ;;
* )
red "Invalid option"
sleep 1
;;
esac
done
}

showconf(){
# Show current service configuration summary
if [[ -f /etc/hysteria/config.yaml ]]; then
get_config_info
proxysite=$(grep -E "^\s*url:\s*https?://" /etc/hysteria/config.yaml 2>/dev/null | awk '{print $2}' | sed -e 's#https://##' -e 's#http://##')
[[ -z "$proxysite" ]] && proxysite="-"
if [[ -n "$port_range" ]]; then
port_mode="Port hopping $port_range"
else
port_mode="Single port"
fi
yellow "Current service configuration:"
echo -e " Server IP : ${last_ip:--}"
echo -e " Listen Port : $([[ -n \"$port\" && \"$port\" != \"0\" ]] && echo \"$port\" || echo '-')"
echo -e " SNI Domain : ${hy_domain:--}"
echo -e " Masquerade : $proxysite"
echo -e " Port Mode : $port_mode"
echo -e " Auth Pass : $([[ -n $auth_pwd ]] && echo $auth_pwd || echo '-')"
echo ""
fi
yellow "Client YAML (/root/hy/hy-client.yaml):"
red "$(cat /root/hy/hy-client.yaml)"
yellow "Client JSON (/root/hy/hy-client.json):"
red "$(cat /root/hy/hy-client.json)"
yellow "Share link (/root/hy/url.txt):"
red "$(cat /root/hy/url.txt)"
echo ""
yellow "Current service status:"
if [[ -f /etc/hysteria/obfs.txt ]]; then
obfs_pwd=$(cat /etc/hysteria/obfs.txt)
green "Obfuscation enabled. Password: $obfs_pwd"
else
yellow "Obfuscation not enabled"
fi
if [[ -f /etc/hysteria/alpn.txt ]]; then
green "ALPN enabled. Protocols: $(cat /etc/hysteria/alpn.txt)"
else
yellow "ALPN not enabled"
fi
if [[ -f /etc/hysteria/bandwidth.txt ]]; then
bandwidth=$(cat /etc/hysteria/bandwidth.txt)
up_mbps=$(echo $bandwidth | cut -d, -f1)
down_mbps=$(echo $bandwidth | cut -d, -f2)
green "Bandwidth limits set: up ${up_mbps} mbps, down ${down_mbps} mbps"
else
yellow "No bandwidth limits set"
fi
green "UDP/QUIC optimization applied"
}

showconf_menu(){
showconf
echo ""
read -p "Press Enter to return to main menu..."
return
}

# Manage boot auto-load configuration
manage_autoload(){
while true; do
autoloadInput=""
clear
echo "#############################################################"
echo -e "# ${GREEN}Boot Auto-load Configuration${PLAIN} #"
echo "#############################################################"
echo ""
echo -e " ${GREEN}1.${PLAIN} Create/Update iptables auto-restore service"
echo -e " ${GREEN}2.${PLAIN} Create/Update service dependency config"
echo -e " ${GREEN}3.${PLAIN} Check auto-load configuration status"
echo -e " ${GREEN}4.${PLAIN} Test auto-load after restart"
echo -e " ${GREEN}5.${PLAIN} Remove auto-load configuration"
echo " ------------------------------------------------------------"
echo -e " ${RED}0.${PLAIN} Back to main menu"
echo ""
read -rp "Select an option [0-5]: " autoloadInput
case $autoloadInput in
1 )
create_iptables_service
;;
2 )
create_service_dependencies
;;
3 )
check_autoload_status
;;
4 )
test_autoload
;;
5 )
remove_autoload
;;
0 )
return # Back to main menu
;;
* )
red "Invalid option, please choose again"
sleep 1
;;
esac
done
}

# Check auto-load configuration status
check_autoload_status(){
clear
echo "#############################################################"
echo -e "# ${GREEN}Auto-load Configuration Status${PLAIN} #"
echo "#############################################################"
echo ""

if systemctl is-enabled hysteria-iptables.service >/dev/null 2>&1; then
green "✓ iptables auto-restore service is enabled"
systemctl status hysteria-iptables.service --no-pager -l
else
red "✗ iptables auto-restore service is not enabled"
fi
echo ""

if [[ -f /etc/systemd/system/hysteria-server.service.d/override.conf ]]; then
green "✓ Service dependency configuration exists"
echo "Dependency config contents:"
cat /etc/systemd/system/hysteria-server.service.d/override.conf
else
red "✗ Service dependency configuration not created"
fi
echo ""

# Dynamically check port rules
if [[ -f /etc/hysteria/port_hopping.txt ]]; then
port_range=$(cat /etc/hysteria/port_hopping.txt)
green "✓ Port hopping configuration present"
echo "Port range: $port_range"
echo "iptables rules:"
iptables -t nat -L PREROUTING -n | grep "hysteria2" || echo "No port hopping rules found"
else
if [[ -f /etc/hysteria/config.yaml ]]; then
port=$(get_hysteria_port)
yellow "⚠ Port hopping not used. Current port: $port"
else
yellow "⚠ Hysteria configuration file does not exist"
fi
fi
echo ""

read -p "Press Enter to continue..."
return
}

# Test auto-load
test_autoload(){
confirm=""
clear
echo "#############################################################"
echo -e "# ${GREEN}Test Auto-load After Restart${PLAIN} #"
echo "#############################################################"
echo ""

yellow "Note: This will restart related services and may briefly disrupt connectivity"
read -p "Proceed? (y/N): " confirm
if [[ $confirm != "y" && $confirm != "Y" ]]; then
return
fi

green "Testing auto-load configuration..."

# Restart iptables service
if [[ -f /etc/systemd/system/hysteria-iptables.service ]]; then
systemctl restart hysteria-iptables.service
if systemctl is-active hysteria-iptables.service >/dev/null 2>&1; then
green "✓ iptables service restarted successfully"
else
red "✗ iptables service restart failed"
fi
else
yellow "iptables service not created"
fi

# Restart Hysteria 2 service
systemctl restart hysteria-server
sleep 2
if systemctl is-active hysteria-server >/dev/null 2>&1; then
green "✓ Hysteria 2 service restarted successfully"
else
red "✗ Hysteria 2 service restart failed"
systemctl status hysteria-server --no-pager -l
fi

# Check iptables rules
echo ""
echo "Current iptables rules:"
if [[ -f /etc/hysteria/port_hopping.txt ]]; then
iptables -t nat -L PREROUTING -n | grep "hysteria2" || echo "No port hopping rules found"
fi

echo ""
green "Test complete!"
read -p "Press Enter to continue..."
return
}

# Remove auto-load configuration
remove_autoload(){
confirm=""
clear
echo "#############################################################"
echo -e "# ${RED}Remove Auto-load Configuration${PLAIN} #"
echo "#############################################################"
echo ""

red "Warning: This will delete all auto-load configuration; manual recovery will be required after reboot!"
read -p "Are you sure? (y/N): " confirm
if [[ $confirm != "y" && $confirm != "Y" ]]; then
return
fi

# Stop and disable iptables service
systemctl stop hysteria-iptables.service 2>/dev/null
systemctl disable hysteria-iptables.service 2>/dev/null
rm -f /etc/systemd/system/hysteria-iptables.service
rm -f /etc/hysteria/restore-iptables.sh

# Remove service dependency config
rm -f /etc/systemd/system/hysteria-server.service.d/override.conf
rmdir /etc/systemd/system/hysteria-server.service.d 2>/dev/null

systemctl daemon-reload

green "Auto-load configuration removed"
read -p "Press Enter to continue..."
return
}

# Exit confirmation
confirm_exit(){
yellow "Are you sure you want to exit? (y/N)"
read -r exit_confirm
if [[ $exit_confirm == "y" || $exit_confirm == "Y" ]]; then
green "Thanks for using the Hysteria 2 installer!"
exit 0
fi
return
}

# Main menu
menu() {
while true; do
# Reset variables to a clean state for each loop
menuInput=""
installed_status=""
running_status=""
autoload_status=""
optimize_status=""
proxysite=""
port_mode=""
alpn_status=""
obfs_status=""
bw_status=""
ip_show="-"
port_show="-"
sni_show="-"
is_installed=0
is_running=0

# Important: clear all configuration-related variables
port=""
auth_pwd=""
hy_domain=""
obfs_pwd=""
alpn_str=""
up_mbps=""
down_mbps=""
use_obfs=false
use_alpn=false
use_bandwidth=false
port_range=""
firstport=""
endport=""
last_ip=""
ip=""

clear

# Header
echo -e "======================================================================"
echo -e "# ${GREEN}Hysteria 2 Installer${PLAIN} #"
echo -e "======================================================================"

# Installation / Running status
if [[ -f "/usr/local/bin/hysteria" ]]; then
is_installed=1
installed_status="${GREEN}Installed${PLAIN}"
if systemctl is-active hysteria-server >/dev/null 2>&1; then
is_running=1
running_status="${GREEN}Running${PLAIN}"
else
running_status="${YELLOW}Not running${PLAIN}"
fi
else
installed_status="${RED}Not installed${PLAIN}"
running_status="${RED}-${PLAIN}"
fi

# Auto-load / Optimization status
if systemctl is-enabled hysteria-iptables.service >/dev/null 2>&1; then
autoload_status="${GREEN}Enabled${PLAIN}"
else
autoload_status="${YELLOW}Disabled${PLAIN}"
fi
if grep -q "# Hysteria 2 UDP/QUIC" /etc/sysctl.conf 2>/dev/null; then
optimize_status="${GREEN}Optimized${PLAIN}"
else
optimize_status="${YELLOW}Not optimized${PLAIN}"
fi

# Load config status (display only)
# Only read when installed and config file exists
if [[ $is_installed -eq 1 ]] && [[ -f /etc/hysteria/config.yaml ]]; then
get_config_info
# Fields for display
ip_show="${last_ip:--}"
[[ -n "$port" && "$port" != "0" ]] && port_show="$port" || port_show="-"
sni_show="${hy_domain:--}"

# Masquerade site
proxysite=$(grep -E "^\s*url:\s*https?://" /etc/hysteria/config.yaml 2>/dev/null | awk '{print $2}' | sed -e 's#https://##' -e 's#http://##')
[[ -z "$proxysite" ]] && proxysite="-"

# Port mode
if [[ -n "$port_range" ]]; then
port_mode="Port hopping $port_range"
else
port_mode="Single port"
fi

# Obfuscation
if [[ "$use_obfs" == true ]]; then
obfs_status="${GREEN}Enabled${PLAIN} (${obfs_pwd})"
else
obfs_status="${YELLOW}Disabled${PLAIN}"
fi

# ALPN
if [[ "$use_alpn" == true ]]; then
alpn_status="${GREEN}${alpn_str}${PLAIN}"
else
alpn_status="${YELLOW}Disabled${PLAIN}"
fi

# Bandwidth
if [[ "$use_bandwidth" == true ]]; then
bw_status="${GREEN}Up ${up_mbps} / Down ${down_mbps} mbps${PLAIN}"
else
bw_status="${YELLOW}Unlimited${PLAIN}"
fi
else
# If not installed or config missing: show only IP, others '-'
realip
if [[ -n $(echo $ip | grep ":") ]]; then
ip_show="[$ip]"
else
ip_show="$ip"
fi
port_show="-"
sni_show="-"
port_mode="-"
obfs_status="-"
alpn_status="-"
bw_status="-"
proxysite="-"
auth_pwd="" # Ensure password cleared
fi

# Status overview bar
echo -e " Status | Installed: ${installed_status} | Service: ${running_status} | Auto-load: ${autoload_status} | Optimize: ${optimize_status}"
echo "----------------------------------------------------------------------"
echo -e " Server IP : $ip_show"
echo -e " Listen Port: $port_show"
echo -e " Port Mode : $port_mode"
echo -e " SNI Domain : $sni_show"
echo -e " Masquerade : $proxysite"
echo -e " Auth Pass : $([[ -n $auth_pwd ]] && echo $auth_pwd || echo '-')"
echo -e " Obfuscation: $obfs_status"
echo -e " ALPN : $alpn_status"
echo -e " Bandwidth : $bw_status"
echo -e "======================================================================"
echo ""

# Actions
echo -e " ${GREEN}1.${PLAIN} Install Hysteria 2"
echo -e " ${RED}2.${PLAIN} Uninstall Hysteria 2"
echo -e " ----------------------------------------------------------------------"
echo -e " ${GREEN}3.${PLAIN} Stop/Start/Restart Hysteria 2"
echo -e " ${GREEN}4.${PLAIN} Modify Hysteria 2 configuration"
echo -e " ${GREEN}5.${PLAIN} Show Hysteria 2 configuration files"
echo -e " ----------------------------------------------------------------------"
echo -e " ${GREEN}6.${PLAIN} System performance optimization (UDP/QUIC)"
echo -e " ${GREEN}7.${PLAIN} Boot auto-load configuration management"
echo -e " ----------------------------------------------------------------------"
echo -e " ${RED}0.${PLAIN} Exit"
echo ""

read -rp "Select an option [0-7]: " menuInput
case $menuInput in
1 ) insthysteria ;;
2 ) unsthysteria ;;
3 ) hysteriaswitch ;;
4 ) changeconf ;;
5 ) showconf_menu ;;
6 ) optimize_system_menu ;;
7 ) manage_autoload ;;
0 ) confirm_exit ;;
* )
red "Invalid option, please choose again"
sleep 1
;;
esac
done
}

# Entry point
menu

启动端口跳跃时,需要确保端口跳跃的端口也开放了

自定义证书,注意,证书位置不要放在root目录及其子目录下,脚本没有权限获取会无法启动服务

1
/etc/cf/cert.pem
1
/etc/cf/key.pem
1
hy.guangyin.blog

重启服务

1
2
systemctl restart hysteria-server.service
systemctl status hysteria-server.service

CTP API 开发流程

API和文档下载:https://www.simnow.com.cn/static/apiDownload.action

image-20250814152725311

将动态库继承到cmake时有点坑,simnow的动态库没有前缀lib,需要自行添加,否则cmake链接不到

1
2
3
4
5
6
7
8
FIND_PACKAGE(ZLIB REQUIRED)
LINK_DIRECTORIES(${PROJECT_SOURCE_DIR}/lib)

TARGET_LINK_LIBRARIES(${PROJECT_NAME}
${PROJECT_SOURCE_DIR}/lib/libthostmduserapi_se.so
${ZLIB_LIBRARIES}
${PROJECT_SOURCE_DIR}/lib/libstdc++.so.6
pthread)

API 命名规则

消息 格式 示例
请求 Req—— ReqUserLogin
响应 OnRsp—— OnRspUserLogin
查询 ReqQry—— ReqQryInstrument
查询请求的响应 OnRspQry—— OnRspQryInstrument
回报 OnRtn—— OnRtnOrder
错误回报 OnErrRtn—— OnErrRtnOrderInsert

交互流程

CTP程序的Api请求,都会在Spi回调线程中处理

image-20250814154623940

CTP通用参数

  1. nRequestID:客户端发送请求时要为该请求指定一个请求编号。交易接口会在响应或回报中返回与该请求相同
    的请求编号。当客户端进行频繁操作时,很有可能会造成同一个响应函数被调用多次,这种情况下,能将请
    求与响应关联起来的纽带就是请求编号。

  2. IsLast: 当响应函数需要携带的数据包过大时,该数据包会被分割成数个小的数据包并按顺序逐次发送,这种
    情况下同一个响应函数就是被调用多次,而参数 IsLast 就是用于描述当前收到的响应数据包是不是所有数据
    包中的最后一个

    例如在查询持仓时,如果查询结果为多条记录,则会分多次回调返回,此时除了最后一次 IsLast 为 true 外,其余全
    为 false。`

  3. RspInfo 该参数用于描述请求执行过程中是否出现错误。该数据结构中的属性 ErrorId 如果是 0,则说明该请

    求被交易核心认可通过。否则,该参数描述了交易核心返回的错误信息。

  4. error.xml 文件中包含所有可能的错误信息。

Api

首先需要创建一个 CThostFtdcMdApi* api_对象,一般习惯于将其再封装一层CMdApi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class CTraderApi
{
private:
CThostFtdcTraderApi* api_ = nullptr;
int generate_request_id();

public:
CTraderApi();
~CTraderApi();
CThostFtdcTraderApi* CreateFtdcTraderApi(const char* pszFlowPath = "");
virtual void RegisterSpi(CThostFtdcTraderSpi* pSpi);
virtual void RegisterFront();

//下面两个订阅函数仅在交易接口需要配置,在行情接口中不需要
virtual void SubscribePrivateTopic();
virtual void SubscribePublicTopic();

virtual void Init();
virtual int ReqAuthenticate();
virtual int ReqUserLogin();

virtual int Join();
virtual void Release();
};

为了使投资者及时准确的了解自己的交易状况,如可用资金,持仓,保证金占用等,从而及时了解自己的风险状况,综合交易平台要求投资者在每一个交易日进行交易前都必须对前一交易日的结算结果进行确认。交易接口需要调用下面的确认函数。

1
api->ReqSettlementInfoConfirm();

Spi

Spi需要先继承行情接口类 CThostFtdcMdSpi,并实现需要实现的虚函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class CTraderSpi : public CThostFtdcTraderSpi
{
private:
CThostFtdcTraderApi* api_ = nullptr;

public:
explicit CTraderSpi(CThostFtdcTraderApi*);
~CTraderSpi();

virtual void OnFrontConnected();
virtual void OnFrontDisconnected(int nReason);
virtual void OnRspAuthenticate(CThostFtdcRspAuthenticateField* pRspAuthenticateField,
CThostFtdcRspInfoField* pRspInfo,
int nRequestID,
bool bIsLast);
virtual void OnRspUserLogin(CThostFtdcRspUserLoginField* pRspUserLogin,
CThostFtdcRspInfoField* pRspInfo,
int nRequestID,
bool bIsLast);
virtual void OnRspError(CThostFtdcRspInfoField* pRspInfo, int nRequestID, bool bIsLast);
};

踩坑

1.注意,Release函数会销毁对象,最后如果调用Join函数会导致段错误

image-20250814161653234 image-20250814161804541

2、回调线程不要有耗时的操作,使用生产者和消费者处理任务

3、不要在回调线程发送请求操作,可能会造成意料之外的事情

muduo(9)-状态机

在muduo库中,使用了很多有限状态机来管理事件的处理流程,如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//TcpConnection
enum StateE { kDisconnected, kConnecting, kConnected, kDisconnecting };
StateE state_;

//Connector
enum States { kDisconnected, kConnecting, kConnected };
States state_;

//HttpContext
enum HttpRequestParseState
{
kExpectRequestLine, // 解析请求行
kExpectHeaders, // 解析头部
kExpectBody, // 解析包体
kGotAll, // 完成
};
HttpRequestParseState state_;

通过状态机管理对象或流程的执行状态,流程清晰易于管理。

分析管理的对象或流程,将整个生命周期中所有稳定且互斥的阶段识别出来,这些阶段就是一个一个状态。然后找到所有可能导致状态改变的外部输入或内部条件。

下面几个示例,方便理解与使用

TcpConnection:管理连接的完整生命周期

  1. kConnecting:连接已建立,正在进行初始化(如调用 connectEstablished)。
  2. kConnected:连接完全就绪,可以进行数据收发。
  3. kDisconnecting:已调用 shutdown(),正在等待数据发送完毕或对端关闭。
  4. kDisconnected:连接已完全关闭。

事件与状态转移

  • 事件connectEstablished() 被调用
    • 转移kConnecting -> kConnected
    • 动作:启用读事件,调用用户的 ConnectionCallback
  • 事件:用户调用 shutdown()
    • 转移kConnected -> kDisconnecting
    • 动作:关闭写端,但仍可接收数据。
  • 事件read() 返回0 或发生错误,触发 handleClose()
    • 转移kConnectedkDisconnecting -> kDisconnected
    • 动作:关闭 socket,调用用户的 ConnectionCallback 和内部的 CloseCallback

Connector:连接管理

  1. kDisconnected:初始状态或连接失败后的状态。
  2. kConnecting:已发起非阻塞 connect,正在等待结果。
  3. kConnected:连接成功建立。

事件与状态转移

  • 事件:调用 start() 发起连接
    • 转移kDisconnected -> kConnecting
  • 事件:socket 可写,handleWrite() 检查 SO_ERROR 成功
    • 转移kConnecting -> kConnected
  • 事件:连接失败,retry() 被调用
    • 转移kConnecting -> kDisconnected

HttpContext:TCP流式协议解析

  1. kExpectRequestLine:正在解析请求行
  2. kExpectHeaders:正在解析请求头
  3. kExpectBody:正在解析请求体
  4. kGotAll:解析完成

事件与状态转移

  • 事件:在缓冲区中找到并成功解析了请求行
    • 转移kExpectRequestLine -> kExpectHeaders
  • 事件:在缓冲区中解析完所有头部,并遇到了一个空行
    • 转移kExpectHeaders -> kGotAll

文件下载器

1
2
3
4
5
6
7
8
enum status{
kIdle,//空闲
kConnecting,//正在建立连接
kDownloading,//正在下载
kPaused,//正在暂停
kCompleted,//下载完成
kError,//异常发生
}
  • 事件: user_starts_download()
    • 转移: kIdle -> kConnecting
  • 事件: connection_succeeded()
    • 转移: kConnecting -> kDownloading
  • 事件: data_chunk_received()
    • 转移: kDownloading -> kDownloading (保持状态,但更新进度)
  • 事件: user_pauses()
    • 转移: kDownloading -> kPaused
  • 事件: download_finished()
    • 转移: kDownloading -> kCompleted
  • 事件: network_error()
    • 转移: kDownloadingkConnecting -> kError

muduo(8)-Connector

断开连接重试机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void Connector::retry(int sockfd)
{
sockets::close(sockfd);
setState(kDisconnected);
if (connect_)
{
loop_->runAfter(retryDelayMs_ / 1000.0,std::bind(&Connector::startInLoop, shared_from_this()));
retryDelayMs_ = std::min(retryDelayMs_ * 2, kMaxRetryDelayMs);
}
else
{
LOG_DEBUG << "do not connect";
}
}

muduo在尝试重连时,并不是立刻进行连接,而是创建一个定时任务,并且,这个定时任务的间隔时间越来越长,通过翻倍的方式进行,知道最大间隔时长kMaxRetryDelayMs,避免频繁的尝试重连对服务器造成压力。

muduo 中 TcpClient 从开始连接到关闭重试的完整生命周期

阶段一:客户端初始化

在创建一个 TcpClient 对象时,主要完成了以下工作:

  1. 构造函数会保存 EventLoop 指针、服务器地址 InetAddress 和客户端名称。
  2. TcpClient 自身不处理连接的细节,而是将这个任务委托给一个内部的 Connector 对象。Connector 的核心职责就是与服务器建立连接
  3. TcpClient 会向 Connector 注册一个回调函数 TcpClient::newConnection。这个回调函数会在 Connector 成功建立连接后被调用。
1
2
3
4
5
6
7
8
9
10
11
12
TcpClient::TcpClient(EventLoop* loop, const InetAddress& serverAddr, const string& nameArg)
: loop_(CHECK_NOTNULL(loop)),
connector_(new Connector(loop, serverAddr)),
name_(nameArg),
connectionCallback_(defaultConnectionCallback),
messageCallback_(defaultMessageCallback),
retry_(false),
connect_(true),
nextConnId_(1)
{
connector_->setNewConnectionCallback(std::bind(&TcpClient::newConnection, this, _1));//设置成功连接的回调函数
}

阶段二:发起连接

  1. 调用TcpClient::connect函数连接服务器

    1
    2
    3
    4
    5
    void TcpClient::connect()
    {
    connect_ = true;
    connector_->start();
    }
  2. 启动connector,调用connector::start()函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    void Connector::start()
    {
    connect_ = true;
    loop_->runInLoop(std::bind(&Connector::startInLoop, this)); // FIXME: unsafe
    }

    void Connector::startInLoop()
    {
    loop_->assertInLoopThread();
    assert(state_ == kDisconnected);
    if (connect_)
    {
    connect();
    }
    else
    {
    LOG_DEBUG << "do not connect";
    }
    }

    void Connector::connect()
    {
    int sockfd = sockets::createNonblockingOrDie(serverAddr_.family());
    int ret = sockets::connect(sockfd, serverAddr_.getSockAddr());
    int savedErrno = (ret == 0) ? 0 : errno;
    switch (savedErrno)
    {
    case 0:
    case EINPROGRESS:
    case EINTR:
    case EISCONN:
    connecting(sockfd);
    break;

    case EAGAIN:
    case EADDRINUSE:
    case EADDRNOTAVAIL:
    case ECONNREFUSED:
    case ENETUNREACH:
    retry(sockfd);
    break;

    case EACCES:
    case EPERM:
    case EAFNOSUPPORT:
    case EALREADY:
    case EBADF:
    case EFAULT:
    case ENOTSOCK:
    LOG_SYSERR << "connect error in Connector::startInLoop " << savedErrno;
    sockets::close(sockfd);
    break;

    default:
    LOG_SYSERR << "Unexpected error in Connector::startInLoop " << savedErrno;
    sockets::close(sockfd);
    // connectErrorCallback_();
    break;
    }
    }

  3. start函数内部调用将Connector::startInLoop任务放到反应堆中执行,然后执行connect();函数,这个函数是非阻塞的,所以立即返回

    • 如果返回 0,表示连接立即成功(通常发生在连接本地地址时)。
    • 如果返回 -1errnoEINPROGRESS,表示连接正在进行中。这是最常见的情况。
    • 其他错误则表示连接失败,可能会触发重试逻辑。

    调用connecting函数,关注它的写事件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void Connector::connecting(int sockfd)
    {
    setState(kConnecting);
    assert(!channel_);
    channel_.reset(new Channel(loop_, sockfd));
    channel_->setWriteCallback(std::bind(&Connector::handleWrite, this));
    channel_->setErrorCallback(std::bind(&Connector::handleError, this));

    channel_->enableWriting();
    }

阶段三:连接建立成功

  1. 当建立连接成功时,poller触发时间,调用Connector::handleWrite方法,在这个方法中,会检验是否真的连接成功了,如果连接成功,调用newConnectionCallback_方法,也就是TcpClient::newConnection方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    void Connector::handleWrite()
    {
    LOG_TRACE << "Connector::handleWrite " << state_;

    if (state_ == kConnecting)
    {
    int sockfd = removeAndResetChannel();
    // 获取socket的错误码,判断是否真的连接成功了,0表示连接成功,非0表示连接失败
    int err = sockets::getSocketError(sockfd);
    if (err)
    {
    LOG_WARN << "Connector::handleWrite - SO_ERROR = " << err << " " << strerror_tl(err);
    // 尝试重连
    retry(sockfd);
    }
    // 判断是否是自连接,如果是自连接,则尝试重连
    else if (sockets::isSelfConnect(sockfd))
    {
    LOG_WARN << "Connector::handleWrite - Self connect";
    retry(sockfd);
    }
    // 连接成功,设置状态为已连接,并调用回调函数
    else
    {
    setState(kConnected);
    if (connect_)
    {
    newConnectionCallback_(sockfd);
    }
    else
    {
    sockets::close(sockfd);
    }
    }
    }
    else
    {
    // what happened?
    assert(state_ == kDisconnected);
    }
    }

    void TcpClient::newConnection(int sockfd)
    {
    loop_->assertInLoopThread();
    // 获取对端地址
    InetAddress peerAddr(sockets::getPeerAddr(sockfd));
    char buf[32];
    // 格式化连接名称
    snprintf(buf, sizeof buf, ":%s#%d", peerAddr.toIpPort().c_str(), nextConnId_);
    ++nextConnId_;
    // 连接名称
    string connName = name_ + buf;

    // 获取本地地址
    InetAddress localAddr(sockets::getLocalAddr(sockfd));
    // 创建TcpConnection对象
    TcpConnectionPtr conn(new TcpConnection(loop_, connName, sockfd, localAddr, peerAddr));

    // 设置回调函数
    conn->setConnectionCallback(connectionCallback_);
    conn->setMessageCallback(messageCallback_);
    // 设置写完成回调函数
    conn->setWriteCompleteCallback(writeCompleteCallback_);
    conn->setCloseCallback(std::bind(&TcpClient::removeConnection, this, _1));
    // 设置连接
    {
    MutexLockGuard lock(mutex_);
    connection_ = conn;
    }
    conn->connectEstablished();
    }

    void TcpConnection::connectEstablished()
    {
    loop_->assertInLoopThread();
    assert(state_ == kConnecting);
    setState(kConnected);
    channel_->tie(shared_from_this());
    channel_->enableReading();

    connectionCallback_(shared_from_this());
    }

    TcpClient::newConnection会设置各种回调,然后调用TcpConnection::connectEstablished方法,开始监听读事件,然后调用connectionCallback_提示建立连接成功

阶段四:连接断开与触发重试

连接可能因为多种原因断开:客户端主动断开、服务器断开、网络故障等。

检测到断开

  • 对端关闭TcpConnection::handleReadread() 时返回 0,表示对端关闭了连接。
  • 发生错误handleRead 读取时出错,或 handleError 被调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void TcpConnection::handleRead(Timestamp receiveTime)
{
loop_->assertInLoopThread();
int savedErrno = 0;
ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);
if (n > 0)
{
messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
}
else if (n == 0)
{
handleClose();
}
else
{
errno = savedErrno;
LOG_SYSERR << "TcpConnection::handleRead";
handleError();
}
}

void TcpConnection::handleClose()
{
loop_->assertInLoopThread();
LOG_TRACE << "fd = " << channel_->fd() << " state = " << stateToString();
assert(state_ == kConnected || state_ == kDisconnecting);

setState(kDisconnected);
channel_->disableAll();

TcpConnectionPtr guardThis(shared_from_this());
connectionCallback_(guardThis);

closeCallback_(guardThis);
}
  1. 将连接状态设置为 kDisconnected
  2. Poller 中移除所有事件监听。
  3. 调用用户的 ConnectionCallback,通知连接已断开。
  4. 调用内部设置的 closeCallback_,也就是 TcpClient::removeConnection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void TcpClient::removeConnection(const TcpConnectionPtr& conn)
{
loop_->assertInLoopThread();
assert(loop_ == conn->getLoop());

{
MutexLockGuard lock(mutex_);
assert(connection_ == conn);
connection_.reset();
}

loop_->queueInLoop(std::bind(&TcpConnection::connectDestroyed, conn));
if (retry_ && connect_)
{
connector_->restart();
}
}

void TcpConnection::connectDestroyed()
{
loop_->assertInLoopThread();
if (state_ == kConnected)
{
setState(kDisconnected);
channel_->disableAll();

connectionCallback_(shared_from_this());
}
channel_->remove();
}
  1. 释放对 TcpConnection 对象的引用。TcpConnection 对象会在其所在的 I/O 线程中被安全地销毁。
  2. 检查retry_和connect_标志判断是否重试

阶段五:执行重试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void Connector::restart()
{
loop_->assertInLoopThread();
setState(kDisconnected);
retryDelayMs_ = kInitRetryDelayMs;
connect_ = true;
startInLoop();
}

void Connector::startInLoop()
{
loop_->assertInLoopThread();
assert(state_ == kDisconnected);
if (connect_)
{
connect();
}
else
{
LOG_DEBUG << "do not connect";
}
}

void Connector::retry(int sockfd)
{
sockets::close(sockfd);
setState(kDisconnected);
if (connect_)
{
loop_->runAfter(retryDelayMs_ / 1000.0,
std::bind(&Connector::startInLoop, shared_from_this()));
retryDelayMs_ = std::min(retryDelayMs_ * 2, kMaxRetryDelayMs);
}
else
{
LOG_DEBUG << "do not connect";
}
}

然后重试连接失败,调用retry(sockfd);函数重试,这个过程会一直循环,直到连接成功或用户调用 stop()

阶段六:客户端关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void Connector::stop()
{
connect_ = false;
loop_->queueInLoop(std::bind(&Connector::stopInLoop, this));
}

void Connector::stopInLoop()
{
loop_->assertInLoopThread();
if (state_ == kConnecting)
{
setState(kDisconnected);
int sockfd = removeAndResetChannel();
retry(sockfd);
}
}

int Connector::removeAndResetChannel()
{
//取消监听事件
channel_->disableAll();
//删除poller上的监听
channel_->remove();
int sockfd = channel_->fd();
loop_->queueInLoop(std::bind(&Connector::resetChannel, this));
return sockfd;
}

void Connector::resetChannel()
{
channel_.reset();
}

TcpClient 对象析构时,它会确保 Connector 被停止,并且如果还存在 TcpConnection,会通过 forceClose() 强制关闭它,保证所有资源被正确释放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
TcpClient::~TcpClient()
{
LOG_INFO << "TcpClient::~TcpClient[" << name_ << "] - connector " << get_pointer(connector_);
TcpConnectionPtr conn;
bool unique = false;
{
MutexLockGuard lock(mutex_);
unique = connection_.unique();
conn = connection_;
}
if (conn)
{
assert(loop_ == conn->getLoop());
// FIXME: not 100% safe, if we are in different thread
CloseCallback cb = std::bind(&detail::removeConnection, loop_, _1);
loop_->runInLoop(std::bind(&TcpConnection::setCloseCallback, conn, cb));
if (unique)
{
conn->forceClose();
}
}
else
{
connector_->stop();
// FIXME: HACK
loop_->runAfter(1, std::bind(&detail::removeConnector, connector_));
}
}