├── README.md └── tuic.sh /README.md: -------------------------------------------------------------------------------- 1 | ### Surge Mac/iOS 2 | ``` 3 | wget -N --no-check-certificate https://raw.githubusercontent.com/CCCOrz/auto-tuic/main/tuic.sh && bash tuic.sh 4 | ``` 5 | 6 | ### 功能 7 | - 使用certbot申请证书并添加自动续期 8 | - 开启Tuic证书指纹 9 | - 自动生成 Sruge 客户端配置 10 | -------------------------------------------------------------------------------- /tuic.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sleep 1 3 | RED="\033[31m" 4 | GREEN="\033[32m" 5 | YELLOW="\033[33m" 6 | PLAIN="\033[0m" 7 | WHITLE="\033[37m" 8 | MAGENTA="\033[35m" 9 | CYAN="\033[36m" 10 | BLUE="\033[34m" 11 | BOLD="\033[01m" 12 | 13 | error() { 14 | echo -e "$RED$BOLD$1$PLAIN" 15 | } 16 | 17 | success() { 18 | echo -e "$GREEN$BOLD$1$PLAIN" 19 | } 20 | 21 | warning() { 22 | echo -e "$YELLOW$BOLD$1$PLAIN" 23 | } 24 | 25 | info() { 26 | echo -e "$PLAIN$BOLD$1$PLAIN" 27 | } 28 | 29 | REGEX=("debian" "ubuntu" "centos|red hat|kernel|oracle linux|alma|rocky" "'amazon linux'" "fedora") 30 | RELEASE=("Debian" "Ubuntu" "CentOS" "CentOS" "Fedora") 31 | PACKAGE_UPDATE=("apt-get update" "apt-get update" "yum -y update" "yum -y update" "yum -y update") 32 | PACKAGE_INSTALL=("apt -y install" "apt -y install" "yum -y install" "yum -y install" "yum -y install") 33 | PACKAGE_REMOVE=("apt -y remove" "apt -y remove" "yum -y remove" "yum -y remove" "yum -y remove") 34 | PACKAGE_UNINSTALL=("apt -y autoremove" "apt -y autoremove" "yum -y autoremove" "yum -y autoremove" "yum -y autoremove") 35 | 36 | [[ $EUID -ne 0 ]] && error "请切换至ROOT用户" && exit 1 37 | 38 | CMD=("$(grep -i pretty_name /etc/os-release 2>/dev/null | cut -d \" -f2)" "$(hostnamectl 2>/dev/null | grep -i system | cut -d : -f2)" "$(lsb_release -sd 2>/dev/null)" "$(grep -i description /etc/lsb-release 2>/dev/null | cut -d \" -f2)" "$(grep . /etc/redhat-release 2>/dev/null)" "$(grep . /etc/issue 2>/dev/null | cut -d \\ -f1 | sed '/^[ ]*$/d')") 39 | 40 | for i in "${CMD[@]}"; do 41 | SYS="$i" 42 | if [[ -n $SYS ]]; then 43 | break 44 | fi 45 | done 46 | 47 | for ((int = 0; int < ${#REGEX[@]}; int++)); do 48 | if [[ $(echo "$SYS" | tr '[:upper:]' '[:lower:]') =~ ${REGEX[int]} ]]; then 49 | SYSTEM="${RELEASE[int]}" 50 | if [[ -n $SYSTEM ]]; then 51 | break 52 | fi 53 | fi 54 | done 55 | 56 | [[ -z $SYSTEM ]] && error "操作系统类型不支持" && exit 1 57 | 58 | ## 59 | workspace="/opt/tuic" 60 | service="/lib/systemd/system/tuic.service" 61 | fullchain="/root/cert/cert.crt" 62 | private_key="/root/cert/private.key" 63 | 64 | back2menu() { 65 | echo "" 66 | success "运行成功" 67 | read -rp "请输入“y”退出, 或按任意键回到主菜单:" back2menuInput 68 | case "$back2menuInput" in 69 | y) exit 1 ;; 70 | *) menu ;; 71 | esac 72 | } 73 | 74 | brefore_install() { 75 | if [[ ! $SYSTEM == "CentOS" ]]; then 76 | info "更新系统软件源" 77 | ${PACKAGE_UPDATE[int]} 78 | fi 79 | info "安装所需软件" 80 | ${PACKAGE_INSTALL[int]} curl wget sudo socat openssl certbot 81 | if [[ $SYSTEM == "CentOS" ]]; then 82 | ${PACKAGE_INSTALL[int]} cronie 83 | systemctl start crond 84 | systemctl enable crond 85 | else 86 | ${PACKAGE_INSTALL[int]} cron 87 | systemctl start cron 88 | systemctl enable cron 89 | fi 90 | } 91 | 92 | check_80(){ 93 | # Fork from https://github.com/Misaka-blog/acme-script/blob/main/acme.sh 94 | # thanks 95 | if [[ -z $(type -P lsof) ]]; then 96 | if [[ ! $SYSTEM == "CentOS" ]]; then 97 | ${PACKAGE_UPDATE[int]} 98 | fi 99 | ${PACKAGE_INSTALL[int]} lsof 100 | fi 101 | 102 | warning "正在检测80端口是否占用..." 103 | sleep 1 104 | 105 | if [[ $(lsof -i:"80" | grep -i -c "listen") -eq 0 ]]; then 106 | success "检测到目前80端口未被占用" 107 | sleep 1 108 | else 109 | error "检测到目前80端口被其他程序被占用,以下为占用程序信息" 110 | lsof -i:"80" 111 | read -rp "如需结束占用进程请按Y,按其他键则退出 [Y/N]: " yn 112 | if [[ $yn =~ "Y"|"y" ]]; then 113 | lsof -i:"80" | awk '{print $2}' | grep -v "PID" | xargs kill -9 114 | sleep 1 115 | else 116 | exit 1 117 | fi 118 | fi 119 | } 120 | 121 | cert_update() { 122 | cat > /etc/letsencrypt/renewal-hooks/post/tuic.sh << EOF 123 | #!/bin/bash 124 | cat /etc/letsencrypt/live/$1*/fullchain.pem > ${workspace}/fullchain.pem 125 | cat /etc/letsencrypt/live/$1*/privkey.pem > ${workspace}/private_key.pem 126 | systemctl restart tuic.service 127 | EOF 128 | warning "测试证书自动续费..." 129 | chmod +x /etc/letsencrypt/renewal-hooks/post/tuic.sh 130 | bash /etc/letsencrypt/renewal-hooks/post/tuic.sh 131 | if crontab -l | grep -q "$1"; then 132 | warning "已存在$1的证书自动续期任务" 133 | else 134 | crontab -l > conf_temp && echo "0 0 * */2 * certbot renew --cert-name $1 --dry-run" >> conf_temp && crontab conf_temp && rm -f conf_temp 135 | warning "已添加$1的证书自动续期任务" 136 | fi 137 | } 138 | 139 | apply_cert() { 140 | warning "正在为您申请证书,您稍等..." 141 | certbot certonly \ 142 | --standalone \ 143 | --agree-tos \ 144 | --no-eff-email \ 145 | --email $1 \ 146 | -d $2 147 | exit_cert=$(cat /etc/letsencrypt/live/$2*/fullchain.pem | grep -i -c "cert") 148 | exit_key=$(cat /etc/letsencrypt/live/$2*/privkey.pem | grep -i -c "key") 149 | if [[ ${exit_cert} -eq 0 || ${exit_key} -eq 0 ]]; then 150 | error "证书申请失败" && exit 1 151 | fi 152 | } 153 | 154 | check_cert() { 155 | exit_cert=$(cat /etc/letsencrypt/live/$2*/fullchain.pem | grep -i -c "cert") 156 | exit_key=$(cat /etc/letsencrypt/live/$2*/privkey.pem | grep -i -c "key") 157 | if [[ ${exit_cert} -ne 0 || ${exit_key} -ne 0 ]]; then 158 | read -rp "是否撤销已有证书?(y/[n]):" del_cert 159 | if [[ ${del_cert} == [yY] ]]; then 160 | warning "正在撤销$2的证书..." 161 | certbot revoke --cert-path /etc/letsencrypt/live/$2*/cert.pem 162 | apply_cert $1 $2 163 | else 164 | info "使用已有证书" 165 | fi 166 | else 167 | apply_cert $1 $2 168 | fi 169 | cert_update $2 170 | } 171 | 172 | create_systemd() { 173 | cat > $service << EOF 174 | [Unit] 175 | Description=Delicately-TUICed high-performance proxy built on top of the QUIC protocol 176 | Documentation=https://github.com/EAimTY/tuic 177 | After=network.target 178 | 179 | [Service] 180 | User=root 181 | WorkingDirectory=${workspace} 182 | ExecStart=${workspace}/tuic-server -c config.json 183 | Restart=on-failure 184 | RestartPreventExitStatus=1 185 | RestartSec=5 186 | 187 | [Install] 188 | WantedBy=multi-user.target 189 | EOF 190 | success "已添加${service}" 191 | systemctl daemon-reload 192 | } 193 | 194 | generate_random_password() { 195 | local length=$1 196 | local password=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w $length | head -n 1) 197 | echo "$password" 198 | } 199 | 200 | find_unused_port() { 201 | local port 202 | while true; do 203 | port=$(shuf -i 1024-65535 -n 1) 204 | if [[ $(lsof -i:"${port}" | grep -i -c "listen") -eq 0 ]]; then 205 | echo "$port" 206 | break 207 | fi 208 | done 209 | } 210 | 211 | 212 | create_conf() { 213 | read -rp "请输入注册邮箱: " email_input 214 | if [[ -z $email_input ]]; then 215 | error "邮箱不能为空" && exit 1 216 | fi 217 | read -rp "请输入域名:" domain_input 218 | if [[ -z ${domain_input} ]]; then 219 | error "域名不能为空" && exit 1 220 | fi 221 | 222 | read -rp "是否自定义证书路径(没有证书的话直接回车)?(y/[n])" is_self_cert 223 | if [[ ${is_self_cert} == [yY] ]]; then 224 | read -rp "请输入certificate完整路径(/xx/xx.crt):" cert_full_path 225 | if [[ -e ${cert_full_path} ]]; then 226 | cat ${cert_full_path} > ${workspace}/fullchain.pem 227 | else 228 | error "证书文件${cert_full_path}不存在" && exit 1 229 | fi 230 | read -rp "请输入private_key完整路径(/xx/xx.key):" key_full_path 231 | if [[ -e ${key_full_path} ]]; then 232 | cat ${key_full_path} > ${workspace}/private_key.pem 233 | else 234 | error "秘钥文件${key_full_path}不存在" && exit 1 235 | fi 236 | else 237 | check_80 238 | check_cert $email_input $domain_input 239 | fi 240 | read -rp "请为tuic分配端口(留空随机分配):" port_input 241 | if [[ -z ${port_input} ]]; then 242 | port_input=$(find_unused_port) 243 | warning "使用随机端口 : $port_input" 244 | fi 245 | 246 | uuid=$(cat /proc/sys/kernel/random/uuid) 247 | password=$(generate_random_password 10) 248 | cat > config.json << EOF 249 | { 250 | "server": "[::]:${port_input}", 251 | "users": { 252 | "${uuid}": "${password}" 253 | }, 254 | "certificate": "${workspace}/fullchain.pem", 255 | "private_key": "${workspace}/private_key.pem", 256 | "congestion_control": "bbr", 257 | "alpn": ["h3", "spdy/3.1"], 258 | "udp_relay_ipv6": true, 259 | "zero_rtt_handshake": false, 260 | "auth_timeout": "3s", 261 | "max_idle_time": "10s", 262 | "max_external_packet_size": 1500, 263 | "gc_interval": "3s", 264 | "gc_lifetime": "15s", 265 | "log_level": "WARN" 266 | } 267 | EOF 268 | read -rp "是否启用证书指纹?(y/[n]):" not_fingerprint 269 | if [[ ${not_fingerprint} == [yY] ]]; then 270 | str=$(openssl x509 -noout -fingerprint -sha256 -inform pem -in "${workspace}/fullchain.pem") 271 | fingerprint=$(echo "$str" | cut -d '=' -f 2) 272 | if [[ -n ${fingerprint} ]]; then 273 | warning "已添加证书指纹" 274 | echo -e "TUIC_V5 = tuic, $(curl -s ipinfo.io/ip) , ${port_input}, server-cert-fingerprint-sha256=${fingerprint}, sni=${domain_input}, uuid=${uuid}, alpn=h3, password=${password}, version=5" > client.txt 275 | else 276 | error "证书指纹生成失败,请检查证书有效性" && exit 1 277 | fi 278 | else 279 | echo -e "TUIC_V5 = tuic, $(curl -s ipinfo.io/ip) , ${port_input}, skip-cert-verify=true, sni=${domain_input}, uuid=${uuid}, alpn=h3, password=${password}, version=5" > client.txt 280 | fi 281 | } 282 | 283 | uninstall() { 284 | systemctl stop tuic && \ 285 | systemctl disable --now tuic.service && \ 286 | rm -rf ${workspace} && rm -rf ${service} 287 | error "已停止并卸载tuic" 288 | } 289 | 290 | run() { 291 | if [[ ! -e "$service" ]]; then 292 | error "tuic未安装" && back2menu 293 | fi 294 | systemctl enable --now tuic.service 295 | if systemctl status tuic | grep -q "active"; then 296 | success "tuic启动成功" 297 | warning "[Proxy] 配置" 298 | info "----------------------" 299 | cat "${workspace}/client.txt" 300 | info "----------------------" 301 | warning "客户端配置目录:${workspace}/client.txt" 302 | return 0 303 | else 304 | error "tuic启动失败" 305 | warning "======== ERROR INFO =========" 306 | systemctl status tuic 307 | return 1 308 | fi 309 | } 310 | 311 | stop() { 312 | if [[ ! -e "$service" ]]; then 313 | error "tuic未安装" 314 | else 315 | systemctl stop tuic 316 | info "tuic已停止" 317 | fi 318 | back2menu 319 | } 320 | 321 | install() { 322 | ARCH=$(uname -m) 323 | if [[ -e "$service" ]]; then 324 | read -rp "是否重新安装tuic?(y/[n])" input 325 | case "$input" in 326 | y) uninstall ;; 327 | *) back2menu ;; 328 | esac 329 | else 330 | brefore_install 331 | fi 332 | mkdir ${workspace} 333 | cd ${workspace} 334 | info "当前工作目录:$(pwd)" 335 | info "下载tuic文件" 336 | REPO_URL="https://api.github.com/repos/EAimTY/tuic/releases/latest" 337 | TAG=$(wget -qO- -t1 -T2 ${REPO_URL} | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g') 338 | URL="https://github.com/EAimTY/tuic/releases/download/$TAG/$TAG-$ARCH-unknown-linux-gnu" 339 | wget -N --no-check-certificate $URL -O tuic-server 340 | chmod +x tuic-server 341 | create_systemd 342 | create_conf 343 | run 344 | } 345 | 346 | 347 | menu() { 348 | echo "" 349 | echo -e " ${GREEN}1.${PLAIN} 安装TUIC" 350 | echo -e " ${GREEN}2.${PLAIN} 运行TUIC" 351 | echo -e " ${GREEN}3.${PLAIN} 停止TUIC" 352 | echo -e " ${GREEN}4.${PLAIN} ${RED}卸载TUIC${PLAIN}" 353 | echo " -------------" 354 | echo -e " ${GREEN}0.${PLAIN} 退出脚本" 355 | echo "" 356 | read -rp "请选择: " NumberInput 357 | case "$NumberInput" in 358 | 1) install ;; 359 | 2) run ;; 360 | 3) stop ;; 361 | 4) uninstall ;; 362 | *) exit 1 ;; 363 | esac 364 | } 365 | 366 | menu 367 | --------------------------------------------------------------------------------