Add files via upload

This commit is contained in:
wcwq98 2024-12-28 22:47:31 +08:00 committed by GitHub
parent 73e3c632d9
commit b7a3db2cd9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 401 additions and 163 deletions

345
realm.sh
View File

@ -48,8 +48,6 @@ EOF
exit 0 exit 0
fi fi
# 更新realm状态 # 更新realm状态
update_realm_status() { update_realm_status() {
if [ -f "/root/realm/realm" ]; then if [ -f "/root/realm/realm" ]; then
@ -64,75 +62,96 @@ update_realm_status() {
# 检查realm服务状态 # 检查realm服务状态
check_realm_service_status() { check_realm_service_status() {
if systemctl is-active --quiet realm; then if systemctl is-active --quiet realm; then
echo -e "${green}启用${plain}" realm_service_status="启用"
realm_service_status_color=$green
else else
echo -e "${red}未启用${plain}" realm_service_status="未启用"
realm_service_status_color=$red
fi
}
# 更新面板状态
update_panel_status() {
if [ -f "/root/realm/web/realm_web" ]; then
panel_status="已安装"
panel_status_color=$green
else
panel_status="未安装"
panel_status_color=$red
fi
}
# 检查面板服务状态
check_panel_service_status() {
if systemctl is-active --quiet realm-panel; then
panel_service_status="启用"
panel_service_status_color=$green
else
panel_service_status="未启用"
panel_service_status_color=$red
fi fi
} }
# 更新脚本 # 更新脚本
Update_Shell() { Update_Shell() {
echo -e "当前脚本版本为 [ ${sh_ver} ],开始检测最新版本..." echo -e "当前脚本版本为 [ ${sh_ver} ],开始检测最新版本..."
# 获取最新版本号
sh_new_ver=$(wget --no-check-certificate -qO- "https://raw.githubusercontent.com/wcwq98/realm/main/realm.sh" | grep 'sh_ver="' | awk -F "=" '{print $NF}' | sed 's/\"//g' | head -1) sh_new_ver=$(wget --no-check-certificate -qO- "https://raw.githubusercontent.com/wcwq98/realm/main/realm.sh" | grep 'sh_ver="' | awk -F "=" '{print $NF}' | sed 's/\"//g' | head -1)
if [[ -z ${sh_new_ver} ]]; then if [[ -z ${sh_new_ver} ]]; then
echo -e "${red}检测最新版本失败!请检查网络或稍后再试。${plain}" echo -e "${red}检测最新版本失败!请检查网络或稍后再试。${plain}"
return 1 return 1
fi fi
if [[ ${sh_new_ver} == ${sh_ver} ]]; then if [[ ${sh_new_ver} == ${sh_ver} ]]; then
echo -e "当前已是最新版本 [ ${sh_new_ver} ]" echo -e "当前已是最新版本 [ ${sh_new_ver} ]"
return 0 return 0
fi fi
# 提示用户是否更新
echo -e "发现新版本 [ ${sh_new_ver} ],是否更新?[Y/n]" echo -e "发现新版本 [ ${sh_new_ver} ],是否更新?[Y/n]"
read -p "(默认: y): " yn read -p "(默认: y): " yn
yn=${yn:-y} # 默认值为 'y' yn=${yn:-y}
if [[ ${yn} =~ ^[Yy]$ ]]; then if [[ ${yn} =~ ^[Yy]$ ]]; then
# 下载最新脚本
wget -N --no-check-certificate https://raw.githubusercontent.com/wcwq98/realm/main/realm.sh -O realm.sh wget -N --no-check-certificate https://raw.githubusercontent.com/wcwq98/realm/main/realm.sh -O realm.sh
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
echo -e "${red}下载脚本失败,请检查网络连接!${plain}" echo -e "${red}下载脚本失败,请检查网络连接!${plain}"
return 1 return 1
fi fi
chmod +x realm.sh chmod +x realm.sh
echo -e "脚本已更新为最新版本 [ ${sh_new_ver} ]" echo -e "脚本已更新为最新版本 [ ${sh_new_ver} ]"
# 自动重启更新后的脚本
echo "正在重新启动脚本..."
exec bash realm.sh exec bash realm.sh
else else
echo -e "已取消更新。" echo -e "已取消更新。"
fi fi
} }
# 检查依赖
check_dependencies() {
echo "正在检查当前环境依赖"
local dependencies=("wget" "tar" "systemctl" "sed" "grep")
# 初始化realm状态 for dep in "${dependencies[@]}"; do
update_realm_status() { if ! command -v "$dep" &> /dev/null; then
if [ -f "/root/realm/realm" ]; then echo "正在安装 $dep..."
realm_status="已安装" if [ -x "$(command -v apt-get)" ]; then
realm_status_color=$green apt-get update && apt-get install -y "$dep"
else elif [ -x "$(command -v yum)" ]; then
realm_status="未安装" yum install -y "$dep"
realm_status_color=$red else
fi echo "无法安装 $dep。请手动安装后重试。"
} exit 1
fi
fi
done
# 检查realm服务状态 echo "所有依赖已满足。"
check_realm_service_status() {
if systemctl is-active --quiet realm; then
echo -e "${green}启用${plain}"
else
echo -e "${red}未启用${plain}"
fi
} }
# 显示菜单的函数 # 显示菜单的函数
show_menu() { show_menu() {
clear clear
update_realm_status
check_realm_service_status
update_panel_status
check_panel_service_status
echo "欢迎使用realm一键转发脚本" echo "欢迎使用realm一键转发脚本"
echo "=================" echo "================="
echo "1. 部署环境" echo "1. 部署环境"
@ -145,11 +164,13 @@ show_menu() {
echo "8. 检测更新" echo "8. 检测更新"
echo "9. 一键卸载" echo "9. 一键卸载"
echo "10. 更新脚本" echo "10. 更新脚本"
echo "11. 面板管理"
echo "0. 退出脚本" echo "0. 退出脚本"
echo "=================" echo "================="
echo -e "realm 状态:${realm_status_color}${realm_status}${plain}" echo -e "realm 状态:${realm_status_color}${realm_status}${plain}"
echo -n "realm 转发状态:" echo -e "realm 转发状态:${realm_service_status_color}${realm_service_status}${plain}"
check_realm_service_status echo -e "面板状态:${panel_status_color}${panel_status}${plain}"
echo -e "面板服务状态:${panel_service_status_color}${panel_service_status}${plain}"
} }
# 部署环境的函数 # 部署环境的函数
@ -198,11 +219,9 @@ deploy_realm() {
tar -xvf "/root/realm/realm-${_version}.tar.gz" -C /root/realm/ tar -xvf "/root/realm/realm-${_version}.tar.gz" -C /root/realm/
chmod +x /root/realm/realm chmod +x /root/realm/realm
# 创建 config.toml 模板 # 创建 config.toml 模板
mkdir -p /root/.realm mkdir -p /root/.realm
cat <<EOF > "$CONFIG_PATH" cat <<EOF > "$CONFIG_PATH"
[network] [network]
no_tcp = false #是否关闭tcp转发 no_tcp = false #是否关闭tcp转发
use_udp = true #是否开启udp转发 use_udp = true #是否开启udp转发
@ -215,7 +234,6 @@ use_udp = true #是否开启udp转发
[[endpoints]] [[endpoints]]
listen = "0.0.0.0:1234" listen = "0.0.0.0:1234"
remote = "0.0.0.0:5678" remote = "0.0.0.0:5678"
EOF EOF
echo "[Unit] echo "[Unit]
@ -255,6 +273,7 @@ uninstall_realm() {
if [[ $delete_config == "Y" || $delete_config == "y" ]]; then if [[ $delete_config == "Y" || $delete_config == "y" ]]; then
rm -rf /root/realm rm -rf /root/realm
rm -rf /root/.realm
echo "配置文件已删除。" echo "配置文件已删除。"
else else
echo "配置文件保留。" echo "配置文件保留。"
@ -266,15 +285,17 @@ uninstall_realm() {
# 删除转发规则的函数 # 删除转发规则的函数
delete_forward() { delete_forward() {
echo "当前转发规则:" echo "当前转发规则:"
local IFS=$'\n' local lines=($(grep -n 'remote =' /root/.realm/config.toml | grep -v '#' | awk -F: '{print $1}'))
local lines=($(grep -n 'remote =' /root/realm/config.toml))
if [ ${#lines[@]} -eq 0 ]; then if [ ${#lines[@]} -eq 0 ]; then
echo "没有发现任何转发规则。" echo "没有发现任何转发规则。"
return return
fi fi
local index=1 local index=1
for line in "${lines[@]}"; do for line_num in "${lines[@]}"; do
echo "${index}. $(echo $line | cut -d '"' -f 2)" listen_line=$((line_num - 1))
listen_port=$(sed -n "${listen_line}p" /root/.realm/config.toml | cut -d '"' -f 2)
remote_port=$(sed -n "${line_num}p" /root/.realm/config.toml | cut -d '"' -f 2)
echo "${index}. 本地监听: ${listen_port} --> 远程目标: ${remote_port}"
let index+=1 let index+=1
done done
@ -295,13 +316,20 @@ delete_forward() {
return return
fi fi
local chosen_line=${lines[$((choice-1))]} local line_number=${lines[$((choice-1))]}
local line_number=$(echo $chosen_line | cut -d ':' -f 1)
# 找到 [[endpoints]] 的起始行
local start_line=$line_number local start_line=$line_number
local end_line=$(($line_number + 2)) while [ $start_line -ge 1 ]; do
local line_content=$(sed -n "${start_line}p" /root/.realm/config.toml)
if [[ $line_content =~ $$\[endpoints$$\] ]]; then
break
fi
((start_line--))
done
sed -i "${start_line},${end_line}d" /root/realm/config.toml # 删除从 start_line 开始的 3 行
sed -i "${start_line},$(($start_line+3))d" /root/.realm/config.toml
echo "转发规则已删除。" echo "转发规则已删除。"
} }
@ -312,9 +340,10 @@ add_forward() {
read -e -p "请输入落地鸡的IP: " ip read -e -p "请输入落地鸡的IP: " ip
read -e -p "请输入本地中转鸡的端口port1: " port1 read -e -p "请输入本地中转鸡的端口port1: " port1
read -e -p "请输入落地鸡端口port2: " port2 read -e -p "请输入落地鸡端口port2: " port2
echo "[[endpoints]] echo "
[[endpoints]]
listen = \"0.0.0.0:$port1\" listen = \"0.0.0.0:$port1\"
remote = \"$ip:$port2\"" >> /root/realm/config.toml remote = \"$ip:$port2\"" >> /root/.realm/config.toml
read -e -p "是否继续添加转发规则(Y/N)? " answer read -e -p "是否继续添加转发规则(Y/N)? " answer
if [[ $answer != "Y" && $answer != "y" ]]; then if [[ $answer != "Y" && $answer != "y" ]]; then
@ -331,9 +360,10 @@ add_port_range_forward() {
read -e -p "请输入落地鸡端口: " remote_port read -e -p "请输入落地鸡端口: " remote_port
for ((port=$start_port; port<=$end_port; port++)); do for ((port=$start_port; port<=$end_port; port++)); do
echo "[[endpoints]] echo "
[[endpoints]]
listen = \"0.0.0.0:$port\" listen = \"0.0.0.0:$port\"
remote = \"$ip:$remote_port\"" >> /root/realm/config.toml remote = \"$ip:$remote_port\"" >> /root/.realm/config.toml
done done
echo "端口段转发规则已添加。" echo "端口段转发规则已添加。"
@ -346,30 +376,23 @@ start_service() {
systemctl restart realm.service systemctl restart realm.service
systemctl enable realm.service systemctl enable realm.service
echo "realm服务已启动并设置为开机自启。" echo "realm服务已启动并设置为开机自启。"
update_realm_status check_realm_service_status
# 检查服务状态
if ! systemctl is-active --quiet realm; then
echo "请检查是否存在config.toml或config.toml配置是否正确"
fi
} }
# 停止服务 # 停止服务
stop_service() { stop_service() {
systemctl stop realm systemctl stop realm.service
echo "realm服务已停止。" systemctl disable realm.service
update_realm_status echo "realm服务已停止并已禁用开机自启。"
check_realm_service_status
} }
# 重启服务 # 重启服务
restart_service() { restart_service() {
systemctl restart realm systemctl daemon-reload
systemctl restart realm.service
echo "realm服务已重启。" echo "realm服务已重启。"
update_realm_status check_realm_service_status
# 检查服务状态
if ! systemctl is-active --quiet realm; then
echo "请检查是否存在config.toml或config.toml配置是否正确"
fi
} }
# 更新realm # 更新realm
@ -407,26 +430,174 @@ update_realm() {
update_realm_status update_realm_status
} }
# 初始化realm状态 # 面板管理函数
update_realm_status panel_management() {
clear
echo "==========================="
echo "Realm 面板管理"
echo "==========================="
echo "1. 启动面板"
echo "2. 暂停面板"
echo "3. 安装面板"
echo "4. 卸载面板"
echo "5. 修改面板配置"
echo "0. 返回主菜单"
echo "==========================="
read -p "请选择操作 [0-5]: " panel_choice
# 主循环 case $panel_choice in
while true; do 1) start_panel ;;
show_menu 2) stop_panel ;;
read -p "请选择一个选项: " choice 3) install_panel ;;
case $choice in 4) uninstall_panel ;;
1) deploy_realm ;; 5) modify_panel_config ;;
2) add_forward ;; 0) return ;;
3) add_port_range_forward ;; *) echo "无效的选择" ;;
4) delete_forward ;;
5) start_service ;;
6) stop_service ;;
7) restart_service ;;
8) update_realm ;;
9) uninstall_realm ;;
10) Update_Shell ;;
0) echo "退出脚本。"; exit 0 ;;
*) echo "无效选项: $choice" ;;
esac esac
read -p "按任意键继续..." key }
done
install_panel() {
echo "开始安装 Realm 面板..."
# 检测系统架构
arch=$(uname -m)
case "$arch" in
x86_64)
panel_file="realm-panel-linux-amd64.tar.gz"
;;
aarch64|arm64)
panel_file="realm-panel-linux-arm64.tar.gz"
;;
*)
echo "不支持的系统架构: $arch"
return 1
;;
esac
cd /root/realm
# 从 GitHub 下载面板文件
echo "正在从 GitHub 下载面板文件..."
echo "检测到系统架构: $arch,将下载: $panel_file"
# 下载面板文件
download_url="https://github.com/wcwq98/realm/releases/download/v2.0/${panel_file}"
if ! wget -O "${panel_file}" "$download_url"; then
echo "下载失败,请检查网络连接或稍后再试。"
return 1
fi
# 解压并设置权限
tar -xvf "${panel_file}" -C /root/realm/
# 重命名文件夹
if [ -d "realm-panel-linux-amd64" ]; then
mv realm-panel-linux-amd64 web
elif [ -d "realm-panel-linux-arm64" ]; then
mv realm-panel-linux-arm64 web
else
echo "未找到解压后的文件夹。"
return 1
fi
cd web
# 设置权限
chmod +rwx realm-web-amd64
# 重命名文件
if [ -f "realm-web-amd64" ]; then
mv realm-web-amd64 realm_web
elif [ -f "realm-web-arm64" ]; then
mv realm-web-arm64 realm_web
else
echo "未找到解压后的文件。"
return 1
fi
# 创建服务文件
echo "[Unit]
Description=Realm Web Panel
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/root/realm/web
ExecStart=/root/realm/web/realm_web
Restart=on-failure
[Install]
WantedBy=multi-user.target" > /etc/systemd/system/realm-panel.service
# 重新加载 systemd 并启动服务
systemctl daemon-reload
systemctl enable realm-panel
systemctl start realm-panel
update_panel_status
echo "Realm 面板安装完成。"
}
# 启动面板
start_panel() {
systemctl start realm-panel
echo "面板服务已启动。"
check_panel_service_status
}
# 停止面板
stop_panel() {
systemctl stop realm-panel
echo "面板服务已停止。"
check_panel_service_status
}
# 卸载面板
uninstall_panel() {
systemctl stop realm-panel
systemctl disable realm-panel
rm -f /etc/systemd/system/realm-panel.service
systemctl daemon-reload
rm -f /root/realm/realm_web
echo "面板已被卸载。"
update_panel_status
}
# 修改面板配置
modify_panel_config() {
echo "修改面板配置..."
# 在此添加修改配置的具体逻辑
echo "配置已修改。"
}
# 主程序
main() {
check_dependencies
init_env
while true; do
show_menu
read -p "请输入选项 [0-11]: " choice
case $choice in
1) deploy_realm ;;
2) add_forward ;;
3) add_port_range_forward ;;
4) delete_forward ;;
5) start_service ;;
6) stop_service ;;
7) restart_service ;;
8) update_realm ;;
9) uninstall_realm ;;
10) Update_Shell ;;
11) panel_management ;;
0) exit 0 ;;
*) echo "无效的选项,请重新输入。" ;;
esac
done
}
main

View File

@ -1,10 +1,11 @@
[auth] [auth]
password = "你的密码" password = "123456" # 面板密码
[server] [server]
port = 8080 port = 8081 # 面板端口
[https] [https]
enabled = true enabled = false #是否开启HTTPS
cert_file = "./certificate/cert.pem" cert_file = "./certificate/cert.pem"
key_file = "./certificate/private.key" key_file = "./certificate/private.key"

View File

@ -10,16 +10,12 @@ require (
github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sessions v1.0.1 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.10.0 // indirect github.com/gin-gonic/gin v1.10.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.2.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect

View File

@ -12,8 +12,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI=
github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
@ -27,12 +25,6 @@ github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaC
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
@ -15,8 +16,8 @@ import (
) )
type ForwardingRule struct { type ForwardingRule struct {
Listen string `toml:"listen"` Listen string `toml:"listen" json:"listen"`
Remote string `toml:"remote"` Remote string `toml:"remote" json:"remote"`
} }
type Config struct { type Config struct {
@ -42,10 +43,10 @@ type PanelConfig struct {
} }
var ( var (
rules []ForwardingRule
mu sync.Mutex mu sync.Mutex
config Config config Config
panelConfig PanelConfig panelConfig PanelConfig
httpsWarningShown = false
) )
func LoadConfig() error { func LoadConfig() error {
@ -58,7 +59,6 @@ func LoadConfig() error {
return err return err
} }
rules = config.Endpoints
return nil return nil
} }
@ -75,20 +75,34 @@ func LoadPanelConfig() error {
return nil return nil
} }
func SaveRules() error { func SaveConfig() error {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
config.Endpoints = rules var buf bytes.Buffer
data, err := toml.Marshal(config) encoder := toml.NewEncoder(&buf)
if err != nil {
// 编码 network 部分
if err := encoder.Encode(map[string]interface{}{"network": config.Network}); err != nil {
return err return err
} }
return ioutil.WriteFile("/root/.realm/config.toml", data, 0644) // 只有在有规则时才添加 endpoints 部分
if len(config.Endpoints) > 0 {
buf.WriteString("\n")
for _, endpoint := range config.Endpoints {
buf.WriteString("[[endpoints]]\n")
if err := encoder.Encode(endpoint); err != nil {
return err
}
buf.WriteString("\n")
}
}
// 写入文件
return ioutil.WriteFile("/root/.realm/config.toml", buf.Bytes(), 0644)
} }
// 认证中间件
func AuthRequired() gin.HandlerFunc { func AuthRequired() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
session := sessions.Default(c) session := sessions.Default(c)
@ -102,6 +116,21 @@ func AuthRequired() gin.HandlerFunc {
} }
} }
func HTTPSRedirect() gin.HandlerFunc {
return func(c *gin.Context) {
if panelConfig.HTTPS.Enabled && c.Request.TLS == nil {
target := "https://" + c.Request.Host + c.Request.URL.Path
if c.Request.URL.RawQuery != "" {
target += "?" + c.Request.URL.RawQuery
}
c.Redirect(http.StatusMovedPermanently, target)
c.Abort()
return
}
c.Next()
}
}
func main() { func main() {
if err := LoadConfig(); err != nil { if err := LoadConfig(); err != nil {
log.Fatalf("无法加载 realm 配置: %v", err) log.Fatalf("无法加载 realm 配置: %v", err)
@ -113,14 +142,12 @@ func main() {
r := gin.Default() r := gin.Default()
// 设置 session
store := cookie.NewStore([]byte("secret")) store := cookie.NewStore([]byte("secret"))
r.Use(sessions.Sessions("realm_session", store)) r.Use(sessions.Sessions("realm_session", store))
r.Use(HTTPSRedirect())
// 静态文件
r.Static("/static", "./static") r.Static("/static", "./static")
// 登录页面
r.GET("/login", func(c *gin.Context) { r.GET("/login", func(c *gin.Context) {
session := sessions.Default(c) session := sessions.Default(c)
if session.Get("user") != nil { if session.Get("user") != nil {
@ -130,7 +157,6 @@ func main() {
c.File("./templates/login.html") c.File("./templates/login.html")
}) })
// 登录处理
r.POST("/login", func(c *gin.Context) { r.POST("/login", func(c *gin.Context) {
var loginData struct { var loginData struct {
Password string `json:"password"` Password string `json:"password"`
@ -157,101 +183,90 @@ func main() {
} }
}) })
// 登出
r.POST("/logout", AuthRequired(), func(c *gin.Context) {
session := sessions.Default(c)
session.Clear()
session.Save()
c.JSON(http.StatusOK, gin.H{"message": "登出成功"})
})
// 需要认证的路由
authorized := r.Group("/") authorized := r.Group("/")
authorized.Use(AuthRequired()) authorized.Use(AuthRequired())
{ {
// 主页
authorized.GET("/", func(c *gin.Context) { authorized.GET("/", func(c *gin.Context) {
if !panelConfig.HTTPS.Enabled && !httpsWarningShown {
c.Header("X-HTTPS-Warning", "当前未启用HTTPS强烈建议启用HTTPS")
httpsWarningShown = true
}
c.File("./templates/index.html") c.File("./templates/index.html")
}) })
// 获取转发规则
authorized.GET("/get_rules", func(c *gin.Context) { authorized.GET("/get_rules", func(c *gin.Context) {
mu.Lock() mu.Lock()
defer mu.Unlock() rules := config.Endpoints
mu.Unlock()
c.JSON(200, rules) c.JSON(200, rules)
}) })
// 添加转发规则
authorized.POST("/add_rule", func(c *gin.Context) { authorized.POST("/add_rule", func(c *gin.Context) {
var input struct { var input ForwardingRule
Listen string `json:"listen"`
Remote string `json:"remote"`
}
if err := c.ShouldBindJSON(&input); err != nil { if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(400, gin.H{"error": "Invalid input"}) c.JSON(400, gin.H{"error": "无效的输入"})
return return
} }
mu.Lock() mu.Lock()
rules = append(rules, ForwardingRule{ config.Endpoints = append(config.Endpoints, input)
Listen: input.Listen,
Remote: input.Remote,
})
mu.Unlock() mu.Unlock()
if err := SaveRules(); err != nil { if err := SaveConfig(); err != nil {
c.JSON(500, gin.H{"error": "Failed to save rules"}) c.JSON(500, gin.H{"error": "保存配置失败"})
return return
} }
c.JSON(201, input) c.JSON(201, input)
}) })
// 删除转发规则
authorized.DELETE("/delete_rule", func(c *gin.Context) { authorized.DELETE("/delete_rule", func(c *gin.Context) {
listen := c.Query("listen") listen := c.Query("listen")
mu.Lock() mu.Lock()
for i, rule := range rules { found := false
for i, rule := range config.Endpoints {
if rule.Listen == listen { if rule.Listen == listen {
rules = append(rules[:i], rules[i+1:]...) config.Endpoints = append(config.Endpoints[:i], config.Endpoints[i+1:]...)
found = true
break break
} }
} }
mu.Unlock() mu.Unlock()
if err := SaveRules(); err != nil { if err := SaveConfig(); err != nil {
c.JSON(500, gin.H{"error": "Failed to save rules"}) c.JSON(500, gin.H{"error": "保存转发规则失败"})
return return
} }
c.Status(200) if found {
c.JSON(200, gin.H{"message": "保存转发规则成功"})
} else {
c.JSON(404, gin.H{"error": "未找到转发规则"})
}
}) })
// 启动服务
authorized.POST("/start_service", func(c *gin.Context) { authorized.POST("/start_service", func(c *gin.Context) {
cmd := exec.Command("systemctl", "start", "realm") cmd := exec.Command("systemctl", "start", "realm")
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
c.JSON(500, gin.H{"error": "Failed to start service"}) c.JSON(500, gin.H{"error": "服务启动失败"})
return return
} }
c.JSON(200, gin.H{"message": "Service started successfully"}) c.JSON(200, gin.H{"message": "服务启动成功"})
}) })
// 停止服务
authorized.POST("/stop_service", func(c *gin.Context) { authorized.POST("/stop_service", func(c *gin.Context) {
cmd := exec.Command("systemctl", "stop", "realm") cmd := exec.Command("systemctl", "stop", "realm")
if err := cmd.Run(); err != nil { if err := cmd.Run(); err != nil {
c.JSON(500, gin.H{"error": "Failed to stop service"}) c.JSON(500, gin.H{"error": "服务停止失败"})
return return
} }
c.JSON(200, gin.H{"message": "Service stopped successfully"}) c.JSON(200, gin.H{"message": "服务停止成功"})
}) })
// 检查服务状态
authorized.GET("/check_status", func(c *gin.Context) { authorized.GET("/check_status", func(c *gin.Context) {
cmd := exec.Command("systemctl", "is-active", "--quiet", "realm") cmd := exec.Command("systemctl", "is-active", "--quiet", "realm")
err := cmd.Run() err := cmd.Run()
@ -273,6 +288,13 @@ func main() {
c.JSON(200, gin.H{"status": status}) c.JSON(200, gin.H{"status": status})
}) })
authorized.POST("/logout", func(c *gin.Context) {
session := sessions.Default(c)
session.Clear()
session.Save()
c.JSON(http.StatusOK, gin.H{"message": "登出成功"})
})
} }
port := panelConfig.Server.Port port := panelConfig.Server.Port
@ -287,10 +309,24 @@ func main() {
r.Run(fmt.Sprintf(":%d", port)) r.Run(fmt.Sprintf(":%d", port))
} else { } else {
log.Printf("服务器正在使用 HTTPS 运行,端口:%d\n", port) log.Printf("服务器正在使用 HTTPS 运行,端口:%d\n", port)
r.RunTLS(fmt.Sprintf(":%d", port), panelConfig.HTTPS.CertFile, panelConfig.HTTPS.KeyFile) go func() {
log.Printf("HTTP 服务器正在运行端口8082用于重定向到 HTTPS\n")
if err := http.ListenAndServe(":8082", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
target := "https://" + r.Host + r.URL.Path
if r.URL.RawQuery != "" {
target += "?" + r.URL.RawQuery
}
http.Redirect(w, r, target, http.StatusMovedPermanently)
})); err != nil {
log.Fatalf("HTTP 服务器错误: %v", err)
}
}()
if err := r.RunTLS(fmt.Sprintf(":%d", port), panelConfig.HTTPS.CertFile, panelConfig.HTTPS.KeyFile); err != nil {
log.Fatalf("HTTPS 服务器错误: %v", err)
}
} }
} else { } else {
log.Println("警告:未启用 HTTPS将使用 HTTP 继续。") log.Println("警告:未启用 HTTPS强烈建议启用 HTTPS。")
log.Printf("服务器正在使用 HTTP 运行,端口:%d\n", port) log.Printf("服务器正在使用 HTTP 运行,端口:%d\n", port)
r.Run(fmt.Sprintf(":%d", port)) r.Run(fmt.Sprintf(":%d", port))
} }

View File

@ -118,6 +118,7 @@
<div class="button-group"> <div class="button-group">
<button id="startButton">启动服务</button> <button id="startButton">启动服务</button>
<button id="stopButton">停止服务</button> <button id="stopButton">停止服务</button>
<button id="logoutButton">登出</button>
<div class="status-wrapper"> <div class="status-wrapper">
<span class="status-label">状态:</span> <span class="status-label">状态:</span>
<span id="serviceStatus" class="status-tag">检查中...</span> <span id="serviceStatus" class="status-tag">检查中...</span>
@ -291,21 +292,43 @@
rules.forEach((rule, index) => { rules.forEach((rule, index) => {
const row = document.createElement('tr'); const row = document.createElement('tr');
let localPort = '', remoteIP = '', remotePort = ''; let localPort = '', remoteIP = '', remotePort = '';
if (rule.Listen) { // 处理本地端口
[, localPort] = rule.Listen.split(':'); if (rule.listen) { // 改为小写 listen
[, localPort] = rule.listen.split(':');
} }
if (rule.Remote) { // 处理远程地址和端口
[remoteIP, remotePort] = rule.Remote.split(':'); if (rule.remote) { // 改为小写 remote
// 检查是否是IPv6地址
if (rule.remote.includes('[')) {
// IPv6格式: [2001:db8::1]:80
const matches = rule.remote.match(/\[(.*)\]:(.*)$/);
if (matches) {
remoteIP = matches[1];
remotePort = matches[2];
}
} else if (rule.remote.includes(':')) {
// 检查冒号的数量来判断是IPv6还是IPv4
const colonCount = (rule.remote.match(/:/g) || []).length;
if (colonCount > 1) {
// IPv6地址没有方括号的情况
const lastColon = rule.remote.lastIndexOf(':');
remoteIP = rule.remote.substring(0, lastColon);
remotePort = rule.remote.substring(lastColon + 1);
} else {
// IPv4地址
[remoteIP, remotePort] = rule.remote.split(':');
}
}
} }
row.innerHTML = ` row.innerHTML = `
<td>${index + 1}</td> <td>${index + 1}</td>
<td>${localPort || 'N/A'}</td> <td>${localPort || 'N/A'}</td>
<td>${remoteIP || 'N/A'}</td> <td>${remoteIP || 'N/A'}</td>
<td>${remotePort || 'N/A'}</td> <td>${remotePort || 'N/A'}</td>
<td><button onclick="deleteRule('${rule.Listen || ''}')">删除</button></td> <td><button onclick="deleteRule('${rule.listen || ''}')">删除</button></td>
`; `;
tbody.appendChild(row); tbody.appendChild(row);
}); });
@ -323,6 +346,25 @@
// 每10秒更新一次状态 // 每10秒更新一次状态
setInterval(updateServiceStatus, 10000); setInterval(updateServiceStatus, 10000);
}; };
// 登出功能
document.getElementById('logoutButton').onclick = async function() {
try {
const response = await fetch('/logout', {
method: 'POST'
});
if (!response.ok) {
throw new Error('登出失败:' + response.statusText);
}
// 登出成功,重定向到登录页面
window.location.href = '/login';
} catch (error) {
console.error('登出失败:', error);
alert('登出失败:' + error.message);
}
};
</script> </script>
</body> </body>