From 56997fdbee6910fcb351f4579131899ea7993ba3 Mon Sep 17 00:00:00 2001 From: fscarmen <62703343+fscarmen@users.noreply.github.com> Date: Mon, 20 Jul 2026 11:31:50 +0000 Subject: [PATCH] v1.3.17 feat: add SIGHUP hot-reload support, replace restart sequences with reload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - special thanks to @starfendou for the technical guidance on hot reloading. - add `cmd_systemctl reload` subcommand with PID detection (Alpine: pidfile → kill -HUP; systemd: systemctl kill -s HUP) - add OpenRC `reload()` function in sing-box service via start-stop-daemon --signal HUP - replace `restart` with `reload` for config changes (ports, hy2realm, bindinterface, uuid/sni/name) - replace `disable+enable` with `reload` for protocol add/remove and port change flows - replace `restart` with `reload` for custom route add/delete operations - remove unnecessary `sync_firewall_rules` call in hy2bw bandwidth change (only modifies subscribe/proxies, no port changes) - run `sing-box check -C` with the new binary before replacing the running one. abort upgrade early when the new binary rejects the existing config - force base-config regeneration on upgrade to guarantee sing-box check passes v1.3.17 feat: 新增 SIGHUP 热更支持,用 reload 替换重启流程 - 感谢 @starfendou 在热加载(Hot Reloading)技术上给予的指导与支持。 - cmd_systemctl 新增 reload 子命令,支持 PID 检测(Alpine: pidfile → kill -HUP; systemd: systemctl kill -s HUP) - OpenRC 服务脚本新增 reload() 函数,通过 start-stop-daemon --signal HUP 实现热更 - 端口 / hy2realm / 绑定接口 / 通用配置修改改为 reload - 协议增删和端口变更流程从 disable+enable 改为 reload - 自定义路由增删改为 reload - 移除 hy2bw 带宽修改中无意义的 sync_firewall_rules 调用(只改 subscribe/proxies,不涉及端口) - 替换运行中二进制前,先用新内核执行 `sing-box check -C` 预检配置,新内核不兼容现有配置时提前中止升级 - 升级时强制重置基础配置至新版格式,保证兼容性 --- README.md | 2 + sing-box.sh | 223 +++++++++++++++++++++++++++++----------------------- 2 files changed, 125 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index 73e54cd..c38dbf8 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ * * * ## 1.更新信息 +2026.07.20 v1.3.17 1. Add SIGHUP hot-reload support, replace restart sequences with reload; 2. Force base-config regeneration on upgrade to guarantee sing-box check passes; 1. 新增 SIGHUP 热更支持,用 reload 替换重启流程; 2. 升级时强制重置基础配置至新版格式,保证兼容性 + 2026.07.16 v1.3.16 1. Add bind_interface option in sb -d menu to bind outbound traffic to a specific NIC; 2. Change v2rayN Hysteria2 Realm config from Finalmask field to ProtoExtraObj; 1. sb -d 菜单新增「指定网络出口」选项,可为出站流量绑定特定网卡; 2. v2rayN 的 Hysteria2 Realm 配置从 Finalmask 字段改为 ProtoExtraObj 2026.07.01 v1.3.15 Add v2rayN Finalmask field for hysteria2 realm subscription output; v2rayN 订阅输出增加 Hysteria2 Realm 的 Finalmask 字段 diff --git a/sing-box.sh b/sing-box.sh index 84a28bc..6c14dce 100644 --- a/sing-box.sh +++ b/sing-box.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # 当前脚本版本号 -VERSION='v1.3.16 (2026.07.16)' +VERSION='v1.3.17 (2026.07.20)' # Github 反代加速代理 GITHUB_PROXY=('https://hub.glowp.xyz/' 'https://proxy.vvvv.ee/') @@ -40,8 +40,8 @@ mkdir -p "$TEMP_DIR" E[0]="Language:\n 1. English (default) \n 2. 简体中文" C[0]="${E[0]}" -E[1]="1. Add bind_interface option in sb -d menu to bind outbound traffic to a specific NIC; 2. Change v2rayN Hysteria2 Realm config from Finalmask field to ProtoExtraObj" -C[1]="1. sb -d 菜单新增「指定网络出口」选项,可为出站流量绑定特定网卡; 2. v2rayN 的 Hysteria2 Realm 配置从 Finalmask 字段改为 ProtoExtraObj" +E[1]="1. Add bind_interface option in sb -d menu to bind outbound traffic to a specific NIC; 2. Change v2rayN Hysteria2 Realm config from Finalmask field to ProtoExtraObj; 3. add SIGHUP hot-reload support, replace restart sequences with reload; 4. force base-config regeneration on upgrade to guarantee sing-box check passes" +C[1]="1. sb -d 菜单新增「指定网络出口」选项,可为出站流量绑定特定网卡; 2. v2rayN 的 Hysteria2 Realm 配置从 Finalmask 字段改为 ProtoExtraObj; 3. 新增 SIGHUP 热更支持,用 reload 替换重启流程; 4. 新增 SIGHUP 热更支持,用 reload 替换重启流程; 4. 升级时强制重置基础配置至新版格式,保证兼容性" E[2]="Downloading Sing-box. Please wait a seconds ..." C[2]="下载 Sing-box 中,请稍等 ..." E[3]="Input errors up to 5 times.The script is aborted." @@ -146,8 +146,8 @@ E[52]="Please set the ip \[\${WS_SERVER_IP_SHOW}] to domain \[\${TYPE_HOST_DOMAI C[52]="请在 Cloudflare 绑定 \[\${WS_SERVER_IP_SHOW}] 的域名为 \[\${TYPE_HOST_DOMAIN}], 并设置 origin rule 为 \[\${TYPE_PORT_WS}]" E[53]="Please select or enter the preferred address (domain / IPv4 / [IPv6], optional :port), the default is \${CDN_DOMAIN[0]}:" C[53]="请选择或者填入优选地址(域名 / IPv4 / [IPv6],可选 :端口),默认为 \${CDN_DOMAIN[0]}:" -E[54]="" -C[54]="" +E[54]="Configuration check failed, new version \$ONLINE is incompatible with current config." +C[54]="配置文件检查失败,新版本 \$ONLINE 与当前配置不兼容" E[55]="The script runs today: \$TODAY. Total: \$TOTAL" C[55]="脚本当天运行次数: \$TODAY,累计运行次数: \$TOTAL" E[56]="Process ID" @@ -262,8 +262,8 @@ E[110]="No CDN protocol is currently in use" C[110]="当前没有使用 CDN 的协议" E[111]="Please select or enter a new CDN (press Enter to keep the current one):" C[111]="请选择或输入新的 CDN (回车保持当前值):" -E[112]="Change complete, restarting service..." -C[112]="修改完成,正在重启服务..." +E[112]="Change complete" +C[112]="修改完成" E[113]="Failed to change CDN, using random privateKey" C[113]="privateKey 格式失败次数过多,已使用随机私钥" E[114]="Invalid privateKey format: expected a 43-character base64url-encoded string." @@ -382,6 +382,12 @@ E[171]="1. Default (not specified)" C[171]="1. 默认(不指定)" E[172]="Bound interface updated to: " C[172]="绑定接口已更新为: " +E[173]="Hot reload successful (PID unchanged: \$MAINPID)" +C[173]="热加载成功(PID 未变: \$MAINPID)" +E[174]="Failed to update configuration. Please check manually. Suggestion: reinstall script" +C[174]="更新配置后仍然无法检查成功,建议重装脚本" +E[175]="Update base configuration? (Node configs will remain unaffected; only log, outbounds, endpoints, route, experimental, dns, ntp, http_clients, etc., will be reset) [Y/n]:" +C[175]="是否更新基础配置?(不影响节点配置,仅重置 log、outbounds、endpoints、route、experimental、dns、ntp、http_clients)[Y/n]:" # 自定义字体彩色,read 函数 warning() { echo -e "\033[31m\033[01m$*\033[0m"; } # 红色 @@ -751,18 +757,16 @@ change_config() { OLD_START_PORT=$(awk 'NR == 1 { min = $0 } { if ($0 < min) min = $0; count++ } END {print min}' <<< "$OLD_PORTS") OLD_CONSECUTIVE_PORTS=$(awk 'END { print NR }' <<< "$OLD_PORTS") input_start_port $OLD_CONSECUTIVE_PORTS - cmd_systemctl disable sing-box for ((a=0; a<$OLD_CONSECUTIVE_PORTS; a++)) do [ -s ${WORK_DIR}/conf/${CONF_FILES[a]} ] && sed -i "s/\(.*listen_port.*:\)$((OLD_START_PORT+a))/\1$((START_PORT+a))/" ${WORK_DIR}/conf/* done fetch_nodes_value [ -n "$PORT_NGINX" ] && UUID_CONFIRM=$(awk '/location/ && /\// {match($0, /\/([^ \/]+)/, arr); p=arr[1]; sub(/-(vmess|vless|auto2|auto).*/, "", p); print p; exit}' ${WORK_DIR}/nginx.conf) && export_nginx_conf_file - cmd_systemctl enable sing-box + cmd_systemctl reload sing-box [ -n "$ARGO_DOMAIN" ] && export_argo_json_file sync_firewall_rules sleep 2 export_list - cmd_systemctl status sing-box &>/dev/null && info " Sing-box $(text 121) $(text 37) " || error " Sing-box $(text 121) $(text 38) " return elif [ "$KEY" = "hy2bw" ]; then # 修改 Hysteria2 带宽 @@ -778,7 +782,6 @@ change_config() { warning " $(text 143) " done sed -i -E "s/(up: \")([0-9]+)( Mbps\")/\1${HY2_UP}\3/g; s/(down: \")([0-9]+)( Mbps\")/\1${HY2_DOWN}\3/g" ${WORK_DIR}/subscribe/proxies - sync_firewall_rules hint " $(text 112) " export_list return @@ -799,8 +802,7 @@ change_config() { set_hy2_realm_config enable [ "$IS_HY2_WARP" = 'is_hy2_warp' ] && sync_hy2_warp_route enable || sync_hy2_warp_route disable fi - hint " $(text 112) " - cmd_systemctl restart sing-box + cmd_systemctl reload sing-box export_list return elif [ "$KEY" = "hy2hopping" ]; then @@ -915,9 +917,7 @@ change_config() { warning " Invalid selection " && return fi - cmd_systemctl restart sing-box - sleep 2 - cmd_systemctl status sing-box &>/dev/null && info "\n Sing-box $(text 28) $(text 37) \n" || warning "\n Sing-box $(text 27) $(text 38) \n" + cmd_systemctl reload sing-box export_list return fi @@ -936,8 +936,6 @@ change_config() { fi # 批量替换,更换服务IP和指纹不需重启服务 - [[ ! "$KEY" =~ ^(fingerprint|serverip|cdn)$ ]] && hint " $(text 112) " - if [[ "$KEY" =~ ^(serverip|cdn)$ ]]; then # IP 在配置里出现的形式有多种,逐一替换 find ${WORK_DIR} -type f | xargs -P 50 sed -i -e "s|\"server\": \"${OLD}\"|\"server\": \"${NEW_VAL}\"|g; s|\"WS_SERVER_IP_SHOW\": \"${OLD}\"|\"WS_SERVER_IP_SHOW\": \"${NEW_VAL}\"|g" 2>/dev/null @@ -946,11 +944,7 @@ change_config() { find ${WORK_DIR}/subscribe -type f | xargs -P 50 sed -i "s|${OLD}|${NEW_VAL}|g" 2>/dev/null else find ${WORK_DIR} -type f | xargs -P 50 sed -i "s|${OLD}|${NEW_VAL}|g" 2>/dev/null - if [[ ! "$KEY" =~ ^(fingerprint)$ ]]; then - cmd_systemctl restart sing-box - sleep 2 - cmd_systemctl status sing-box &>/dev/null && info "\n Sing-box $(text 28) $(text 37) \n" || warning "\n Sing-box $(text 27) $(text 38) \n" - fi + [[ ! "$KEY" =~ ^(fingerprint)$ ]] && cmd_systemctl reload sing-box fi export_list } @@ -1549,8 +1543,7 @@ custom_route_add() { custom_route_compact_rules info " $(text 157) " - hint " $(text 112) " - cmd_systemctl restart sing-box + cmd_systemctl reload sing-box sleep 2 cmd_systemctl status sing-box &>/dev/null && \ info "\n Sing-box $(text 28) $(text 37) \n" || \ @@ -1690,8 +1683,7 @@ custom_route_delete() { fi info " $(text 160) " - hint " $(text 112) " - cmd_systemctl restart sing-box + cmd_systemctl reload sing-box sleep 2 cmd_systemctl status sing-box &>/dev/null && \ info "\n Sing-box $(text 28) $(text 37) \n" || \ @@ -2094,6 +2086,15 @@ cmd_systemctl() { restart ) rc-service "$2" restart >/dev/null 2>&1 ;; + reload ) + local MAINPID=$(cat /var/run/sing-box.pid 2>/dev/null) + if [ -n "$MAINPID" ] && kill -0 "$MAINPID" 2>/dev/null; then + kill -HUP "$MAINPID" 2>/dev/null + info "\n $(text 173) \n" + else + rc-service "$2" restart >/dev/null 2>&1 + fi + ;; status ) rc-service "$2" status ;; @@ -2116,6 +2117,15 @@ cmd_systemctl() { systemctl restart "$2" >/dev/null 2>&1 [ "$IS_CENTOS" = 'CentOS7' ] && [ "$2" = 'sing-box' ] && [ -s $WORK_DIR/nginx.conf ] && nginx_run ;; + reload ) + local MAINPID=$(systemctl show -p MainPID sing-box 2>/dev/null | awk -F= '{print $2}') + if [ -n "$MAINPID" ] && [ "$MAINPID" -gt 0 ] 2>/dev/null; then + systemctl kill -s HUP sing-box >/dev/null 2>&1 + info "\n $(text 173) \n" + else + systemctl restart sing-box >/dev/null 2>&1 + fi + ;; status ) systemctl is-active "$2" ;; @@ -3445,20 +3455,10 @@ http { echo "$NGINX_CONF" > ${WORK_DIR}/nginx.conf } -# 生成 sing-box 配置文件 -sing-box_json() { - local IS_CHANGE=$1 - mkdir -p ${WORK_DIR}/conf ${WORK_DIR}/logs ${WORK_DIR}/subscribe - - # 判断是否为新安装,不为 change 就是新安装 - if [ "$IS_CHANGE" = 'change' ]; then - # 判断 sing-box 主程序所在路径 - DIR=${WORK_DIR} - else - DIR=$TEMP_DIR - - # 生成 log 配置 - cat > ${WORK_DIR}/conf/00_log.json << EOF +# 生成 sing-box 基础配置 +generate_sing_box_base_conf() { + # 生成 log 配置 + cat > ${WORK_DIR}/conf/00_log.json << EOF { "log":{ "disabled":false, @@ -3469,8 +3469,8 @@ sing-box_json() { } EOF - # 生成 outbound 配置 - cat > ${WORK_DIR}/conf/01_outbounds.json << EOF + # 生成 outbound 配置 + cat > ${WORK_DIR}/conf/01_outbounds.json << EOF { "outbounds":[ { @@ -3482,8 +3482,8 @@ EOF } EOF - # 生成 endpoint 配置 - cat > ${WORK_DIR}/conf/02_endpoints.json << EOF + # 生成 endpoint 配置 + cat > ${WORK_DIR}/conf/02_endpoints.json << EOF { "endpoints":[ { @@ -3516,8 +3516,8 @@ EOF } EOF - # 生成 route 配置 - cat > ${WORK_DIR}/conf/03_route.json << EOF + # 生成 route 配置 + cat > ${WORK_DIR}/conf/03_route.json << EOF { "route":{ "default_http_client": "http-client-direct", @@ -3554,15 +3554,15 @@ EOF "rule_set":[ "geosite-openai" ], - "outbound":"${CHATGPT_OUT}" + "outbound":"${CHATGPT_OUT:-direct}" } ] } } EOF - # 生成缓存文件 - cat > ${WORK_DIR}/conf/04_experimental.json << EOF + # 生成缓存文件 + cat > ${WORK_DIR}/conf/04_experimental.json << EOF { "experimental": { "cache_file": { @@ -3573,8 +3573,8 @@ EOF } EOF - # 生成 dns 配置文件 - cat > ${WORK_DIR}/conf/05_dns.json << EOF + # 生成 dns 配置文件 + cat > ${WORK_DIR}/conf/05_dns.json << EOF { "dns":{ "servers":[ @@ -3588,8 +3588,8 @@ EOF } EOF - # 内建的 NTP 客户端服务配置文件,这对于无法进行时间同步的环境很有用 - cat > ${WORK_DIR}/conf/06_ntp.json << EOF + # 内建的 NTP 客户端服务配置文件,这对于无法进行时间同步的环境很有用 + cat > ${WORK_DIR}/conf/06_ntp.json << EOF { "ntp": { "enabled": true, @@ -3600,8 +3600,8 @@ EOF } EOF - # 专门给 sing-box 内部组件发 HTTP 请求用,比如这些场景会用到它:下载远程 rule_set:.srs 规则文件,ACME 申请证书,Cloudflare Origin CA 证书提供器,DERP / Tailscale 相关 HTTP 请求 - cat > ${WORK_DIR}/conf/07_http_clients.json << EOF + # 专门给 sing-box 内部组件发 HTTP 请求用,比如这些场景会用到它:下载远程 rule_set:.srs 规则文件,ACME 申请证书,Cloudflare Origin CA 证书提供器,DERP / Tailscale 相关 HTTP 请求 + cat > ${WORK_DIR}/conf/07_http_clients.json << EOF { "http_clients": [ { @@ -3610,6 +3610,20 @@ EOF ] } EOF +} + +# 生成 sing-box 配置文件 +sing-box_json() { + local IS_CHANGE=$1 + mkdir -p ${WORK_DIR}/conf ${WORK_DIR}/logs ${WORK_DIR}/subscribe + + # 判断是否为新安装,不为 change 就是新安装 + if [ "$IS_CHANGE" = 'change' ]; then + # 判断 sing-box 主程序所在路径 + DIR=${WORK_DIR} + else + DIR=$TEMP_DIR + generate_sing_box_base_conf fi # 生成 Reality 公私钥,第一次安装的时候,如有指定的私钥,则使用该私钥及生成对应的公钥;如没有指定私钥则使用新生成的;添加协议的时,使用相应数组里的第一个非空值,如全空则像第一次安装那样使用新生成的 @@ -4224,10 +4238,16 @@ depend() { [ -n "$PORT_NGINX" ] && OPENRC_SERVICE+=" need nginx" - # 添加 start_pre 函数,确保目录存在并设置正确权限 + # 添加 reload 函数,支持 SIGHUP 热更 OPENRC_SERVICE+=" } +reload() { + ebegin \"Reloading \${RC_SVCNAME}\" + start-stop-daemon --signal HUP --pidfile \$pidfile + eend \$? \"Failed to reload \${RC_SVCNAME}\" +} + start_pre() { # 确保日志目录和PID目录存在并有正确权限 mkdir -p ${WORK_DIR}/logs @@ -5583,9 +5603,6 @@ change_protocols() { unset PORT_NAIVE fi - # 停止 sing-box 服务 - cmd_systemctl disable sing-box - # 生成 Nginx 配置文件 [ -n "$PORT_NGINX" ] && export_nginx_conf_file @@ -5610,8 +5627,8 @@ change_protocols() { # 如有需要,删除 nginx 配置文件 ! ls ${ARGO_DAEMON_FILE} >/dev/null 2>&1 && [[ -s ${WORK_DIR}/nginx.conf && "$IS_SUB" = 'no_sub' ]] && IS_ARGO=no_argo && rm -f ${WORK_DIR}/nginx.conf - # 运行 sing-box - cmd_systemctl enable sing-box + # 热更 sing-box(SIGHUP 重新加载配置,PID 不变) + cmd_systemctl reload sing-box # 打开防火墙相关端口 sync_firewall_rules @@ -5621,17 +5638,6 @@ change_protocols() { # 再次检测状态,运行 sing-box check_install - case "${STATUS[0]}" in - "$(text 26)" ) - error "\n Sing-box $(text 28) $(text 38) \n" - ;; - "$(text 27)" ) - cmd_systemctl enable sing-box - cmd_systemctl status sing-box &>/dev/null && info "\n Sing-box $(text 28) $(text 37) \n" || error "\n Sing-box $(text 28) $(text 38) \n" - ;; - "$(text 28)" ) - info "\n Sing-box $(text 28) $(text 37) \n" - esac # 导出节点和订阅服务信息 export_list @@ -5669,38 +5675,55 @@ version() { check_system_info wget --no-check-certificate --continue ${GH_PROXY}https://github.com/SagerNet/sing-box/releases/download/v$ONLINE/sing-box-$ONLINE-linux-$SING_BOX_ARCH.tar.gz -qO- | tar xz -C $TEMP_DIR sing-box-$ONLINE-linux-$SING_BOX_ARCH/sing-box - if [ -s $TEMP_DIR/sing-box-$ONLINE-linux-$SING_BOX_ARCH/sing-box ]; then - cmd_systemctl disable sing-box + [ -s $TEMP_DIR/sing-box-$ONLINE-linux-$SING_BOX_ARCH/sing-box ] || error "\n $(text 42) \n" + if ! $TEMP_DIR/sing-box-$ONLINE-linux-$SING_BOX_ARCH/sing-box check -C ${WORK_DIR}/conf >/dev/null; then + warning "\n $(text 54) " && reading "\n $(text 175) " UPDATE_CONFIG + [ "${UPDATE_CONFIG,,}" = 'n' ] && exit 1 - # 备份旧版本 - cp ${WORK_DIR}/sing-box ${WORK_DIR}/sing-box.bak - hint "\n $(text 102) \n" + # 设置基础配置参数 dns.servers.prefer_go 和 dns.strategy + local STRATEGY=$(grep -E --exclude="03_route.json" 'ipv4_only|ipv6_only|prefer_ipv4|prefer_ipv6' ${WORK_DIR}/conf/0*.json | awk -F '"' '{print $(NF-1); exit}') + STRATEGY=${STRATEGY:-prefer_ipv4} + command -v systemctl >/dev/null 2>&1 && systemctl is-active --quiet systemd-resolved && local IS_PREFER_GO=false || local IS_PREFER_GO=true - # 安装新版本 - chmod +x $TEMP_DIR/sing-box-$ONLINE-linux-$SING_BOX_ARCH/sing-box && mv $TEMP_DIR/sing-box-$ONLINE-linux-$SING_BOX_ARCH/sing-box ${WORK_DIR}/sing-box + # 备份旧基础配置 + for i in $(ls ${WORK_DIR}/conf/0*); do + cp $i ${i}.bak + done + generate_sing_box_base_conf + + if ! $TEMP_DIR/sing-box-$ONLINE-linux-$SING_BOX_ARCH/sing-box check -C ${WORK_DIR}/conf >/dev/null; then + for i in $(ls ${WORK_DIR}/conf/0*.bak); do + mv $i ${i%%.bak} + done + error "\n $(text 174) \n" + fi + fi + + cmd_systemctl disable sing-box + + # 备份旧版本 + cp ${WORK_DIR}/sing-box ${WORK_DIR}/sing-box.bak + hint "\n $(text 102) \n" + + # 安装新版本 + chmod +x $TEMP_DIR/sing-box-$ONLINE-linux-$SING_BOX_ARCH/sing-box && mv $TEMP_DIR/sing-box-$ONLINE-linux-$SING_BOX_ARCH/sing-box ${WORK_DIR}/sing-box + cmd_systemctl enable sing-box + sleep 2 + + # 检查新版本是否成功运行 + if cmd_systemctl status sing-box &>/dev/null; then + # 新版本运行成功,删除备份 + rm -f ${WORK_DIR}/sing-box.bak ${WORK_DIR}/conf/*.bak + info "\n $(text 103) \n" + else + # 新版本运行失败,恢复旧版本 + warning "\n $(text 104) \n" + mv ${WORK_DIR}/sing-box.bak ${WORK_DIR}/sing-box + rm -f ${WORK_DIR}/conf/*.bak cmd_systemctl enable sing-box sleep 2 - # 检查新版本是否成功运行 - if cmd_systemctl status sing-box &>/dev/null; then - # 新版本运行成功,删除备份 - rm -f ${WORK_DIR}/sing-box.bak - info "\n $(text 103) \n" - else - # 新版本运行失败,恢复旧版本 - warning "\n $(text 104) \n" - mv ${WORK_DIR}/sing-box.bak ${WORK_DIR}/sing-box - cmd_systemctl enable sing-box - sleep 2 - - if cmd_systemctl status sing-box &>/dev/null; then - info "\n $(text 105) \n" - else - error "\n $(text 106) \n" - fi - fi - else - error "\n $(text 42) " + cmd_systemctl status sing-box &>/dev/null && info "\n $(text 105) \n" || error "\n $(text 106) \n" fi fi }