translate into Chinese

This commit is contained in:
Arvin
2026-03-22 00:07:01 +08:00
parent 5a7a059605
commit 63df1d22f1
8 changed files with 216 additions and 222 deletions
+109 -109
View File
@@ -38,7 +38,7 @@ resolve_project_dir() {
fi
done
echo "[ERROR] Unable to locate project directory" >&2
echo "[ERROR] 无法定位项目目录" >&2
exit 1
}
@@ -62,10 +62,10 @@ err() { log "\033[31m[ERROR]\033[0m $*"; }
usage() {
cat <<'EOF'
Usage:
clashctl COMMAND [OPTIONS]
用法:
clashctl 命令 [选项]
Commands:
指令:
on 开启当前终端代理
off 关闭当前终端代理
start 启动 Clash
@@ -82,10 +82,10 @@ Commands:
tun status|on|off 查看/启用/关闭 Tun
mixin status|on|off 查看/启用/关闭 Mixin
Advanced Commands:
高级命令:
generate 生成配置(调试用,不会启动服务)
Options:
选项:
--from-systemd 内部使用,避免 stop 递归调用 systemctl
-h, --help 显示帮助信息
EOF
@@ -132,7 +132,7 @@ write_env_bool() {
if grep -qE "^[[:space:]]*(export[[:space:]]+)?${key}=" "$ENV_FILE"; then
sed -i -E "s|^[[:space:]]*(export[[:space:]]+)?${key}=.*$|export ${key}=\"${value}\"|g" "$ENV_FILE"
else
echo "export ${key}=\"${value}\"" >> "$ENV_FILE"
echo "已写入环境变量 ${key}=\"${value}\"" >> "$ENV_FILE"
fi
}
@@ -240,11 +240,11 @@ cmd_start() {
case "$mode" in
systemd|systemd-installed)
start_via_systemd
ok "Clash started via systemd"
ok "已通过 systemd 启动 Clash"
;;
script|none)
start_via_script
ok "Clash started via script mode"
ok "已通过脚本模式启动 Clash"
;;
*)
err "未知模式: $mode"
@@ -261,21 +261,21 @@ cmd_stop() {
case "$mode" in
systemd)
if [ "$from_systemd" = "true" ]; then
ok "Stop requested from systemd, skip recursive systemctl stop"
ok "来自 systemd 的停止请求,已跳过重复停止操作"
return 0
fi
stop_via_systemd
ok "Clash stopped via systemd"
ok "已通过 systemd 停止 Clash"
;;
systemd-installed)
info "systemd service installed but not running"
info "systemd 服务已安装但未运行"
;;
script)
stop_via_script
ok "Clash stopped via script mode"
ok "已通过脚本模式停止 Clash"
;;
none)
info "Clash is not running"
info "Clash 未运行"
;;
*)
err "未知模式: $mode"
@@ -312,7 +312,7 @@ cmd_update() {
fi
if [ "$pull_ok" -eq 1 ]; then
echo "[INFO] pulling latest code from ${remote_name}/${branch} ..."
echo "[INFO] 正在从 ${remote_name}/${branch} 拉取最新代码 ..."
if ! git -C "$PROJECT_DIR" pull --ff-only "$remote_name" "$branch"; then
echo "[WARN] git pull 失败,自动切换到强制更新模式"
pull_ok=0
@@ -320,13 +320,13 @@ cmd_update() {
fi
if [ "$pull_ok" -eq 0 ]; then
echo "[INFO] fetching latest code from ${remote_name}/${branch} ..."
echo "[INFO] 正在获取 ${remote_name}/${branch} 最新代码 ..."
if ! git -C "$PROJECT_DIR" fetch "$remote_name" "$branch"; then
err "git fetch 失败"
exit 1
fi
echo "[WARN] resetting local code to ${remote_name}/${branch}"
echo "[WARN] 正在重置本地代码到 ${remote_name}/${branch}"
if ! git -C "$PROJECT_DIR" reset --hard "${remote_name}/${branch}"; then
err "git reset --hard 失败"
exit 1
@@ -339,32 +339,32 @@ cmd_update() {
fi
# 修复脚本权限,避免 203/EXEC
echo "[INFO] fixing executable permissions ..."
echo "[INFO] 修复执行权限中..."
chmod +x "$PROJECT_DIR"/scripts/*.sh 2>/dev/null || true
chmod +x "$PROJECT_DIR"/bin/* 2>/dev/null || true
sed -i 's/\r$//' "$PROJECT_DIR"/scripts/*.sh 2>/dev/null || true
# 先停服务,避免 generate 时误判端口占用导致漂移
echo "[INFO] stopping service before regenerate ..."
echo "[INFO] 停止服务(准备重新生成配置)..."
if has_systemd; then
systemctl stop clash-for-linux.service 2>/dev/null || true
sleep 1
fi
echo "[INFO] regenerating config ..."
echo "[INFO] 生成配置中..."
if ! bash "$PROJECT_DIR/scripts/generate_config.sh"; then
err "配置生成失败"
exit 1
fi
echo "[INFO] starting service ..."
echo "[INFO] 启动服务中..."
if has_systemd; then
if ! systemctl start clash-for-linux.service; then
err "systemd 启动失败"
exit 1
fi
echo "[INFO] waiting for service to be active ..."
echo "[INFO] 等待服务就绪..."
if wait_for_systemd_active; then
ok "更新完成"
cmd_status
@@ -407,11 +407,11 @@ cmd_update_force() {
mkdir -p "$PROJECT_DIR/runtime"
if [ -f "$env_file" ]; then
echo "[INFO] backing up local env overrides ..."
echo "[INFO] 正在备份本地环境变量覆盖项 ..."
backup_local_env_overrides "$env_file" "$backup_file"
fi
echo "[WARN] force update: local code changes will be discarded"
echo "[WARN] 强制更新:本地代码修改将被覆盖"
git -C "$PROJECT_DIR" fetch "$remote_name" "$branch" || {
err "git fetch 失败"
exit 1
@@ -423,17 +423,17 @@ cmd_update_force() {
}
if [ -f "$env_file" ] && [ -f "$backup_file" ]; then
echo "[INFO] restoring local env overrides ..."
echo "[INFO] 正在恢复本地环境变量 ..."
restore_local_env_overrides "$env_file" "$backup_file"
fi
echo "[INFO] regenerating config ..."
echo "[INFO] 正在重新生成配置 ..."
if ! bash "$PROJECT_DIR/scripts/generate_config.sh"; then
err "配置生成失败"
exit 1
fi
echo "[INFO] restarting service ..."
echo "[INFO] 正在重启服务 ..."
if has_systemd; then
systemctl restart clash-for-linux.service || {
err "systemd 重启失败"
@@ -523,29 +523,29 @@ wait_for_systemd_active() {
}
cmd_restart() {
echo "[INFO] stopping Clash ..."
echo "[INFO] 正在停止 Clash ..."
cmd_stop "${1:-false}" || true
sleep 1
echo "[INFO] regenerating config ..."
echo "[INFO] 正在重新生成配置 ..."
if ! cmd_generate; then
return 1
fi
echo "[INFO] starting Clash ..."
echo "[INFO] 正在启动 Clash ..."
if ! cmd_start; then
err "启动失败"
return 1
fi
if has_systemd; then
echo "[INFO] waiting for service to be active ..."
echo "[INFO] 正在等待服务启动完成 ..."
if wait_for_systemd_active; then
ok "重启完成"
return 0
fi
err "服务启动未就绪"
err "服务未就绪"
systemctl status "$SERVICE_NAME" -l --no-pager || true
return 1
fi
@@ -589,14 +589,14 @@ cmd_secret() {
local secret
if [ ! -s "$RUNTIME_CONFIG" ]; then
err "runtime config not found: $RUNTIME_CONFIG"
echo "Please run install.sh or clashctl generate" >&2
err "未找到运行时配置文件: $RUNTIME_CONFIG"
echo "请先执行 install.sh 或 clashctl generate" >&2
exit 1
fi
secret="$(read_runtime_config_value "secret" || true)"
if [ -z "${secret:-}" ]; then
err "secret not found in $RUNTIME_CONFIG"
err "未找到 secret 在配置: $RUNTIME_CONFIG"
exit 1
fi
@@ -746,77 +746,77 @@ cmd_status() {
secret_exists="yes"
fi
echo "=== Clash Status ==="
echo "Project : $PROJECT_DIR"
echo "Mode : $mode"
echo "Running : $running"
echo "Config : $RUNTIME_CONFIG"
echo "=== Clash 状态 ==="
echo "项目路径 : $PROJECT_DIR"
echo "运行模式 : $mode"
echo "运行状态 : $running"
echo "配置文件 : $RUNTIME_CONFIG"
if [ -f "$RUNTIME_CONFIG" ]; then
echo "ConfigExists : yes"
echo "配置存在 : 是"
else
echo "ConfigExists : no"
echo "配置存在 : 否"
fi
if [ -f "$STATE_FILE" ]; then
echo "StateFile : $STATE_FILE"
echo "状态文件 : $STATE_FILE"
else
echo "StateFile : missing"
echo "状态文件 : 缺失"
fi
case "$mode" in
systemd|systemd-installed)
echo "Service : installed"
echo "Active : ${service_active:-unknown}"
echo "Enabled : ${service_enabled:-unknown}"
echo "服务类型 : systemd"
echo "服务状态 : ${service_active:-未知}"
echo "开机自启 : ${service_enabled:-未知}"
;;
script)
echo "Service : script"
echo "PID : ${pid:-unknown}"
echo "服务类型 : 脚本模式"
echo "进程 ID : ${pid:-未知}"
;;
none)
echo "Service : none"
echo "服务类型 : 未安装"
;;
esac
echo "Generate : ${generate_status:-unknown}"
echo "配置生成状态 : ${generate_status:-未知}"
if [ -n "${generate_reason:-}" ]; then
echo "GenReason : $generate_reason"
echo "生成失败原因 : $generate_reason"
fi
if [ -n "${config_source:-}" ]; then
echo "ConfigSource : $config_source"
echo "配置来源 : $config_source"
fi
if [ -n "${generate_at:-}" ]; then
echo "GeneratedAt : $generate_at"
echo "生成时间 : $generate_at"
fi
if [ -n "${run_status:-}" ]; then
echo "RunStatus : $run_status"
echo "运行结果 : $run_status"
fi
if [ -n "${run_mode:-}" ]; then
echo "RunMode : $run_mode"
echo "运行方式 : $run_mode"
fi
if [ -n "${run_pid:-}" ]; then
echo "RunPID : $run_pid"
echo "运行进程 : $run_pid"
fi
if [ -n "${run_at:-}" ]; then
echo "RunAt : $run_at"
echo "启动时间 : $run_at"
fi
echo "ProxyPort : ${http_port:-unknown}"
echo "DashPort : ${dashboard_port:-unknown}"
echo "代理端口 : ${http_port:-未知}"
echo "面板端口 : ${dashboard_port:-未知}"
if [ -n "${controller:-}" ]; then
echo "Controller : $controller"
echo "控制地址 : $controller"
else
echo "Controller : unknown"
echo "控制地址 : 未知"
fi
if [ -n "${dashboard_url:-}" ]; then
echo "Dashboard : $dashboard_url"
echo "面板地址 : $dashboard_url"
fi
echo "Secret : $secret_exists"
echo "密钥状态 : $secret_exists"
}
doctor_ok() {
@@ -843,30 +843,30 @@ cmd_doctor() {
mode="$(detect_mode)"
echo "=== Clash Doctor ==="
echo "Project : $PROJECT_DIR"
echo "Mode : $mode"
echo "Config : $RUNTIME_CONFIG"
echo "=== Clash 健康检查 ==="
echo "项目路径 : $PROJECT_DIR"
echo "运行模式 : $mode"
echo "配置文件 : $RUNTIME_CONFIG"
echo
if [ -s "$RUNTIME_CONFIG" ]; then
doctor_ok "runtime config exists: $RUNTIME_CONFIG"
doctor_ok "运行配置文件存在: $RUNTIME_CONFIG"
else
doctor_err "runtime config missing or empty: $RUNTIME_CONFIG"
doctor_err "运行配置文件不存在或为空: $RUNTIME_CONFIG"
failed=1
fi
if [ -f "$RUNTIME_CONFIG" ]; then
if grep -q '\${' "$RUNTIME_CONFIG"; then
doctor_err "runtime config contains unresolved placeholders"
doctor_err "配置文件存在未替换变量"
failed=1
else
doctor_ok "runtime config has no unresolved placeholders"
doctor_ok "配置文件检查通过(无未替换变量)"
fi
fi
if [ -f "$STATE_FILE" ]; then
doctor_ok "state file exists: $STATE_FILE"
doctor_ok "状态文件存在: $STATE_FILE"
last_generate_status="$(read_state_value LAST_GENERATE_STATUS || true)"
last_generate_reason="$(read_state_value LAST_GENERATE_REASON || true)"
@@ -874,21 +874,21 @@ cmd_doctor() {
if [ -n "${last_generate_status:-}" ]; then
if [ "$last_generate_status" = "success" ]; then
doctor_ok "last generate status: success (${last_generate_reason:-unknown})"
doctor_ok "最近配置生成状态:成功 (${last_generate_reason:-未知})"
else
doctor_warn "last generate status: ${last_generate_status:-unknown} (${last_generate_reason:-unknown})"
doctor_warn "最近配置生成状态:${last_generate_status:-未知} (${last_generate_reason:-未知})"
warned=1
fi
else
doctor_warn "state file exists but LAST_GENERATE_STATUS is empty"
doctor_warn "状态文件存在,但 LAST_GENERATE_STATUS 为空"
warned=1
fi
if [ -n "${last_config_source:-}" ]; then
doctor_ok "config source: $last_config_source"
doctor_ok "配置来源: $last_config_source"
fi
else
doctor_warn "state file missing: $STATE_FILE"
doctor_warn "状态文件缺失: $STATE_FILE"
warned=1
fi
@@ -896,31 +896,31 @@ cmd_doctor() {
systemd)
if systemctl is-active --quiet "$SERVICE_NAME"; then
running="yes"
doctor_ok "systemd service is active"
doctor_ok "systemd 服务运行正常"
else
doctor_err "systemd service is installed but not active"
doctor_err "systemd 服务已安装但未运行"
failed=1
fi
;;
script)
if is_script_running; then
running="yes"
doctor_ok "script mode process is running (pid=$(read_pid 2>/dev/null || echo unknown))"
doctor_ok "脚本模式进程正在运行 (pid=$(read_pid 2>/dev/null || echo 未知))"
else
doctor_err "script mode detected but PID is not running"
doctor_err "已检测到脚本模式,但进程未运行"
failed=1
fi
;;
systemd-installed)
doctor_warn "systemd unit exists but service is not running"
doctor_warn "systemd 单元已存在,但服务未运行"
warned=1
;;
none)
doctor_warn "no running instance detected"
doctor_warn "未检测到运行中的实例"
warned=1
;;
*)
doctor_err "unknown mode: $mode"
doctor_err "未知运行模式: $mode"
failed=1
;;
esac
@@ -930,51 +930,51 @@ cmd_doctor() {
dashboard_port="$(port_from_controller)"
if [ -n "${controller:-}" ]; then
doctor_ok "external-controller configured: $controller"
doctor_ok "控制地址已配置: $controller"
else
doctor_warn "external-controller not found in runtime config, fallback assumed: 127.0.0.1:9090"
doctor_warn "未配置 external-controller,使用默认值: 127.0.0.1:9090"
warned=1
fi
if [ -n "${dashboard_url:-}" ]; then
doctor_ok "dashboard url: $dashboard_url"
doctor_ok "面板地址: $dashboard_url"
else
doctor_warn "dashboard url could not be derived"
doctor_warn "无法获取面板地址"
warned=1
fi
http_port="$(http_port_from_config)"
if check_port_listening "$http_port"; then
doctor_ok "proxy port is listening: $http_port"
doctor_ok "代理端口正常监听: $http_port"
else
rc=$?
if [ "$rc" -eq 2 ]; then
doctor_warn "cannot verify proxy port (ss/netstat not available)"
doctor_warn "无法检测代理端口(缺少 ss/netstat 工具)"
warned=1
else
if [ "$running" = "yes" ]; then
doctor_err "proxy port is not listening: $http_port"
doctor_err "代理端口未监听: $http_port"
failed=1
else
doctor_warn "proxy port is not listening: $http_port"
doctor_warn "代理端口未监听: $http_port"
warned=1
fi
fi
fi
if check_port_listening "$dashboard_port"; then
doctor_ok "dashboard port is listening: $dashboard_port"
doctor_ok "面板端口正常监听: $dashboard_port"
else
rc=$?
if [ "$rc" -eq 2 ]; then
doctor_warn "cannot verify dashboard port (ss/netstat not available)"
doctor_warn "无法检测面板端口(缺少 ss/netstat 工具)"
warned=1
else
if [ "$running" = "yes" ]; then
doctor_err "dashboard port is not listening: $dashboard_port"
doctor_err "面板端口未监听: $dashboard_port"
failed=1
else
doctor_warn "dashboard port is not listening: $dashboard_port"
doctor_warn "面板端口未监听: $dashboard_port"
warned=1
fi
fi
@@ -982,14 +982,14 @@ cmd_doctor() {
if [ -n "${dashboard_url:-}" ]; then
if check_dashboard_http "$dashboard_url"; then
doctor_ok "dashboard http reachable"
doctor_ok "面板访问正常"
else
rc=$?
if [ "$rc" -eq 2 ]; then
doctor_warn "cannot verify dashboard http (curl/wget not available)"
doctor_warn "无法检测面板 HTTP(缺少 curl/wget 工具)"
warned=1
else
doctor_warn "dashboard http not reachable: $dashboard_url"
doctor_warn "面板无法访问: $dashboard_url"
warned=1
fi
fi
@@ -997,38 +997,38 @@ cmd_doctor() {
secret="$(read_runtime_config_value "secret" || true)"
if [ -n "${secret:-}" ]; then
doctor_ok "secret exists in runtime config"
doctor_ok "已配置 secret"
else
doctor_warn "secret missing in runtime config"
doctor_warn "未配置 secret"
warned=1
fi
if command_exists clashctl; then
doctor_ok "clashctl command available: $(command -v clashctl)"
doctor_ok "clashctl 命令可用: $(command -v clashctl)"
else
doctor_warn "clashctl command not found in PATH"
doctor_warn "未在 PATH 中找到 clashctl 命令"
warned=1
fi
if [ -f "$PROFILED_FILE" ]; then
doctor_ok "shell proxy helper exists: $PROFILED_FILE"
doctor_ok "Shell 代理辅助脚本存在: $PROFILED_FILE"
else
doctor_warn "shell proxy helper missing: $PROFILED_FILE"
doctor_warn "Shell 代理辅助脚本缺失: $PROFILED_FILE"
warned=1
fi
echo
if [ "$failed" -ne 0 ]; then
doctor_err "doctor result: FAILED"
doctor_err "检查结果:失败"
return 1
fi
if [ "$warned" -ne 0 ]; then
doctor_warn "doctor result: WARN"
doctor_warn "检查结果:警告"
return 0
fi
doctor_ok "doctor result: HEALTHY"
doctor_ok "检查结果:健康"
return 0
}
+24 -24
View File
@@ -2,18 +2,18 @@
set -euo pipefail
# 作用:
# - 将订阅内容转换成 Clash Meta / Mihomo 可用的完整 YAML 配置
# - 默认使用 subconverter HTTP /sub 接口(最稳:用 -G + --data-urlencode)
# - 失败则跳过,不影响主流程
# - 将订阅内容转换为 Clash Meta / Mihomo 可用的完整 YAML 配置
# - 默认使用 subconverter HTTP /sub 接口(最稳:使用 -G + --data-urlencode)
# - 转换失败则跳过,不影响主流程
#
# 输入/输出约定:
# - IN_FILE:原订阅(默认 temp/clash.yaml)
# 输入 / 输出约定:
# - IN_FILE:原始订阅(默认 temp/clash.yaml)
# - OUT_FILE:转换后的配置(默认 temp/clash_config.yaml)
#
# 设计原则:
# - 绝不 exit 1(失败只 warn 并 exit 0)
# - 已是完整 Clash 配置则直接 copy
# - 没有 CLASH_URL(原始订阅 URL)则不转换(subconverter 最稳是 url=...)
# - 绝不 exit 1(失败只输出 warn 并 exit 0)
# - 如果本身已是完整 Clash 配置,则直接复制
# - 如果没有 CLASH_URL(原始订阅 URL),则不执行转换(subconverter 最稳妥的方式是 url=...)
Server_Dir="${Server_Dir:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
Temp_Dir="${Temp_Dir:-$Server_Dir/temp}"
@@ -23,38 +23,38 @@ mkdir -p "$Temp_Dir"
IN_FILE="${IN_FILE:-$Temp_Dir/clash.yaml}"
OUT_FILE="${OUT_FILE:-$Temp_Dir/clash_config.yaml}"
# “更先进”的默认:Clash Meta / Mihomo
SUB_TARGET="${SUB_TARGET:-clashmeta}" # 推荐 clashmeta(兼容面最广)
SUB_URL="${CLASH_URL:-}" # 原始订阅 URL(.env 里 export CLASH_URL=...)
# 更推荐的默认目标:Clash Meta / Mihomo
SUB_TARGET="${SUB_TARGET:-clashmeta}" # 推荐 clashmeta(兼容性最好)
SUB_URL="${CLASH_URL:-}" # 原始订阅 URL(.env 中 export CLASH_URL=...)
# 0) 输入不存在就跳过
# 0) 输入文件不存在则跳过
if [ ! -s "$IN_FILE" ]; then
echo "[WARN] no input file: $IN_FILE"
echo "[WARN] 未找到输入文件: $IN_FILE"
exit 0
fi
# 1) 如果看起来已经是完整 Clash 配置,就直接用,不转换
# (包含 proxies / proxy-providers / rules / port 等任一关键词即可认为是完整配置)
# 1) 如果看起来已经是完整 Clash 配置,则直接使用,不再转换
# (包含 proxies / proxy-providers / rules / port 等任一关键字即可视为完整配置)
if grep -qE '^(proxies:|proxy-providers:|mixed-port:|port:|rules:|dns:)' "$IN_FILE"; then
cp -f "$IN_FILE" "$OUT_FILE"
echo "[OK] input already looks like a Clash config -> $OUT_FILE"
echo "[OK] 输入内容已是 Clash 配置,直接使用 -> $OUT_FILE"
exit 0
fi
# 2) subconverter 不可用就跳过
# 2) subconverter 不可用则跳过
if [ "${SUBCONVERTER_READY:-false}" != "true" ] || [ -z "${SUBCONVERTER_URL:-}" ]; then
echo "[WARN] subconverter not ready, skip conversion"
echo "[WARN] subconverter 未就绪,跳过转换"
exit 0
fi
# 3) 没有原始 URL 就不转(subconverter 最稳是 url=... 拉取)
# 3) 没有原始 URL 则不转换(subconverter 最稳妥的方式是 url=... 拉取)
if [ -z "${SUB_URL:-}" ]; then
echo "[WARN] CLASH_URL empty, cannot convert via /sub, skip"
echo "[WARN] CLASH_URL 为空,无法通过 /sub 转换,已跳过"
exit 0
fi
# 4) 调用 subconverter:用 -G + --data-urlencode,避免 url 参数里含 ? & 导致 400
# 注意:SUB_URL 必须是原始订阅 URL(例如 https://.../subscribe?token=xxx)
# 4) 调用 subconverter:使用 -G + --data-urlencode,避免 url 参数中包含 ? 和 & 导致 400
# 注意:SUB_URL 必须为原始订阅 URL(例如 https://.../subscribe?token=xxx)
TMP_OUT="${OUT_FILE}.tmp"
rm -f "$TMP_OUT" 2>/dev/null || true
@@ -67,12 +67,12 @@ rc=$?
set -e
if [ "$rc" -ne 0 ] || [ ! -s "$TMP_OUT" ]; then
echo "[WARN] convert failed (rc=${rc}), skip"
echo "[WARN] 转换失败(rc=${rc}),已跳过"
rm -f "$TMP_OUT" 2>/dev/null || true
exit 0
fi
mv -f "$TMP_OUT" "$OUT_FILE"
echo "[OK] converted via subconverter -> ${OUT_FILE} (target=${SUB_TARGET})"
echo "[OK] 已通过 subconverter 完成转换 -> ${OUT_FILE} (target=${SUB_TARGET})"
true
+9 -9
View File
@@ -52,10 +52,10 @@ write_state() {
local source="${3:-unknown}"
cat > "$STATE_FILE" <<EOF
LAST_GENERATE_STATUS=$status
LAST_GENERATE_REASON=$reason
LAST_CONFIG_SOURCE=$source
LAST_GENERATE_AT=$(date -Iseconds)
LAST_GENERATE_STATUS=$status
LAST_GENERATE_REASON=$reason
LAST_CONFIG_SOURCE=$source
LAST_GENERATE_AT=$(date -Iseconds)
EOF
}
@@ -138,7 +138,7 @@ apply_controller_to_config() {
upsert_yaml_kv_local "$file" "external-controller" "$EXTERNAL_CONTROLLER"
if [ ! -f "$ui_src/index.html" ]; then
echo "[ERROR] UI not found: $ui_src/index.html" >&2
echo "[ERROR] UI 未找到: $ui_src/index.html" >&2
exit 1
fi
@@ -208,19 +208,19 @@ main() {
exit 0
fi
echo "[ERROR] auto update disabled and runtime config missing: $RUNTIME_CONFIG" >&2
echo "[错误] 已关闭自动更新,且运行配置缺失: $RUNTIME_CONFIG" >&2
write_state "failed" "runtime_missing" "none"
exit 1
fi
if [ -z "${CLASH_URL:-}" ]; then
echo "[ERROR] CLASH_URL is empty" >&2
echo "[错误] 未设置 CLASH_URL" >&2
write_state "failed" "url_missing" "none"
exit 1
fi
if ! download_subscription; then
echo "[ERROR] failed to download subscription" >&2
echo "[错误] 下载订阅失败" >&2
write_state "failed" "download_failed" "none"
exit 1
fi
@@ -237,7 +237,7 @@ main() {
fi
if [ ! -s "$template_file" ]; then
echo "[ERROR] missing template config file: $template_file" >&2
echo "[错误] 缺少模板配置文件: $template_file" >&2
write_state "failed" "missing_template" "none"
exit 1
fi
+1 -1
View File
@@ -13,7 +13,7 @@ LOG_DIR="$PROJECT_DIR/logs"
CONFIG_DIR="$PROJECT_DIR/config"
if [ "$(id -u)" -ne 0 ]; then
echo "[ERROR] root required to install systemd unit" >&2
echo "[ERROR] 安装 systemd 服务需要 root 权限" >&2
exit 1
fi
+29 -29
View File
@@ -25,7 +25,7 @@ is_port_in_use() {
fi
if [ "$PORT_CHECK_WARNED" -eq 0 ]; then
echo "[WARN] no port check tool found (ss/netstat/lsof)" >&2
echo "[WARN] 未检测到端口检查工具(ss/netstat/lsof)" >&2
PORT_CHECK_WARNED=1
fi
@@ -66,38 +66,38 @@ find_available_port() {
# 解析端口值(核心函数)
# =========================
resolve_port_value() {
local name="$1"
local value="$2"
local resolved
local name="$1"
local value="$2"
local resolved
# auto / 空
if [ -z "$value" ] || [ "$value" = "auto" ]; then
resolved=$(find_available_port) || {
echo "[ERROR] ${name} failed to allocate port" >&2
return 1
}
echo "[WARN] ${name} auto assigned: ${resolved}" >&2
echo "$resolved"
return 0
fi
# auto / 空
if [ -z "$value" ] || [ "$value" = "auto" ]; then
resolved=$(find_available_port) || {
echo "[ERROR] ${name} 端口分配失败" >&2
return 1
}
echo "[WARN] ${name} 自动分配端口: ${resolved}" >&2
echo "$resolved"
return 0
fi
# 非数字
if ! [[ "$value" =~ ^[0-9]+$ ]]; then
echo "[ERROR] invalid port: $value" >&2
return 1
fi
# 非数字
if ! [[ "$value" =~ ^[0-9]+$ ]]; then
echo "[ERROR] 非法端口: $value" >&2
return 1
fi
# 被占用 → 自动替换
if is_port_in_use "$value"; then
resolved=$(find_available_port)
if [ -n "$resolved" ]; then
echo "[WARN] ${name} port ${value} in use, switched to ${resolved}" >&2
echo "$resolved"
return 0
fi
fi
# 被占用 → 自动替换
if is_port_in_use "$value"; then
resolved=$(find_available_port)
if [ -n "$resolved" ]; then
echo "[WARN] ${name} 端口 ${value} 已被占用,已切换为 ${resolved}" >&2
echo "$resolved"
return 0
fi
fi
echo "$value"
echo "$value"
}
# =========================
+20 -26
View File
@@ -3,13 +3,13 @@ set -euo pipefail
# 作用:
# - 根据 OS/ARCH 选择 tools/subconverter/<platform>/subconverter
# - 生成稳定入口 tools/subconverter/subconverter(软链优先,失败则复制)
# -(可选)以 daemon 模式启动本地 subconverter(HTTP 服务)
# - 导出统一变量给后续脚本使用:
# - 生成稳定入口 tools/subconverter/subconverter(优先软链接,失败则复制)
# -(可选)以守护进程模式启动本地 subconverter(HTTP 服务)
# - 导出统一变量供后续脚本使用:
# SUBCONVERTER_BIN / SUBCONVERTER_READY / SUBCONVERTER_URL
#
# 设计原则:
# - 永不 exit 1(不可用就 Ready=false,主流程继续)
# - 永不 exit 1(不可用时标记 Ready=false,主流程继续)
# - 不阻塞 start.sh(快速启动,不等待健康检查)
Server_Dir="${Server_Dir:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
@@ -18,24 +18,24 @@ Temp_Dir="${Temp_Dir:-$Server_Dir/temp}"
mkdir -p "$Temp_Dir"
SC_DIR="$Server_Dir/tools/subconverter"
SC_LINK="$SC_DIR/subconverter" # 稳定入口(最终用于启动/调用)
SC_LINK="$SC_DIR/subconverter" # 稳定入口(统一调用路径)
Subconverter_Bin="$SC_LINK"
Subconverter_Ready=false
# 配置项(可放 .env)
# 配置项(可写入 .env)
SUBCONVERTER_MODE="${SUBCONVERTER_MODE:-daemon}" # daemon | off
SUBCONVERTER_HOST="${SUBCONVERTER_HOST:-127.0.0.1}"
SUBCONVERTER_PORT="${SUBCONVERTER_PORT:-25500}"
SUBCONVERTER_URL="${SUBCONVERTER_URL:-http://${SUBCONVERTER_HOST}:${SUBCONVERTER_PORT}}"
# pref.ini:不存在就从示例生成
# pref.ini:不存在则从示例生成
SUBCONVERTER_PREF="${SUBCONVERTER_PREF:-$SC_DIR/pref.ini}"
PREF_EXAMPLE_INI="$SC_DIR/pref.example.ini"
PID_FILE="$Temp_Dir/subconverter.pid"
log() { echo "[subc] $*"; }
warn() { echo "[subc][WARN] $*" >&2; }
warn() { echo "[subc][警告] $*" >&2; }
detect_os() {
local u
@@ -58,7 +58,6 @@ detect_arch() {
}
pick_platform_bin() {
# 你的仓库结构:tools/subconverter/linux-amd64/subconverter 等
local os="$1" arch="$2"
local p="$SC_DIR/${os}-${arch}/subconverter"
if [ -f "$p" ]; then
@@ -78,7 +77,7 @@ make_stable_link_or_copy() {
# 清理旧入口
rm -f "$SC_LINK" 2>/dev/null || true
# 软链优先,不支持则复制
# 优先创建软链接,失败则复制
if ln -s "$src" "$SC_LINK" 2>/dev/null; then
:
else
@@ -93,12 +92,12 @@ is_port_listening() {
if command -v ss >/dev/null 2>&1; then
ss -lnt | awk '{print $4}' | grep -q ":${port}\$" && return 0
fi
# 兜底:ss 不存在就不判断(返回 false)
# 无检测工具则不判断
return 1
}
main() {
# 0) 用户显式关闭
# 0) 用户主动关闭
if [ "$SUBCONVERTER_MODE" = "off" ]; then
Subconverter_Ready=false
export Subconverter_Bin Subconverter_Ready SUBCONVERTER_BIN SUBCONVERTER_READY SUBCONVERTER_URL
@@ -112,7 +111,7 @@ main() {
arch="$(detect_arch)"
if [ "$os" = "unsupported" ]; then
warn "Unsupported OS: $(uname -s). Skip subconverter."
warn "不支持的操作系统: $(uname -s),跳过 subconverter"
Subconverter_Ready=false
export Subconverter_Bin Subconverter_Ready SUBCONVERTER_BIN SUBCONVERTER_READY SUBCONVERTER_URL
true
@@ -120,7 +119,7 @@ main() {
fi
if [ "$arch" = "unknown" ]; then
warn "Unsupported arch: $(uname -m). Skip subconverter."
warn "不支持的架构: $(uname -m),跳过 subconverter"
Subconverter_Ready=false
export Subconverter_Bin Subconverter_Ready SUBCONVERTER_BIN SUBCONVERTER_READY SUBCONVERTER_URL
true
@@ -129,16 +128,16 @@ main() {
platform_bin="$(pick_platform_bin "$os" "$arch")"
if [ -z "$platform_bin" ]; then
warn "No subconverter binary found at: $SC_DIR/${os}-${arch}/subconverter"
warn "未找到 subconverter 二进制: $SC_DIR/${os}-${arch}/subconverter"
Subconverter_Ready=false
export Subconverter_Bin Subconverter_Ready SUBCONVERTER_BIN SUBCONVERTER_READY SUBCONVERTER_URL
true
return 0
fi
# 2) 生成稳定入口 tools/subconverter/subconverter
# 2) 创建稳定入口
if ! make_stable_link_or_copy "$platform_bin"; then
warn "Failed to create stable entry: $SC_LINK"
warn "创建稳定入口失败: $SC_LINK"
Subconverter_Ready=false
export Subconverter_Bin Subconverter_Ready SUBCONVERTER_BIN SUBCONVERTER_READY SUBCONVERTER_URL
true
@@ -147,45 +146,40 @@ main() {
Subconverter_Bin="$SC_LINK"
Subconverter_Ready=true
log "Resolved platform bin: ${os}-${arch} -> $Subconverter_Bin"
log "已解析平台二进制: ${os}-${arch} -> $Subconverter_Bin"
# 3) pref.ini 生成(仅当准备启用 daemon)
# 3) 生成 pref.ini(仅 daemon 模式)
if [ "$Subconverter_Ready" = "true" ] && [ "$SUBCONVERTER_MODE" = "daemon" ]; then
if [ ! -f "$SUBCONVERTER_PREF" ] && [ -f "$PREF_EXAMPLE_INI" ]; then
cp -f "$PREF_EXAMPLE_INI" "$SUBCONVERTER_PREF"
fi
fi
# 4) daemon 启动(只在需要时)
# 4) 启动 daemon
if [ "$Subconverter_Ready" = "true" ] && [ "$SUBCONVERTER_MODE" = "daemon" ]; then
# pid 存活则认为已启动
if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE" 2>/dev/null)" 2>/dev/null; then
:
else
# 端口已监听则不重复起(可能是之前启动的)
if is_port_listening "$SUBCONVERTER_PORT"; then
:
else
(
cd "$SC_DIR"
# 注意:subconverter 读取 base/rules/snippets 等目录,必须在其目录下启动更稳
nohup "$Subconverter_Bin" -f "$SUBCONVERTER_PREF" >/dev/null 2>&1 &
echo $! > "$PID_FILE"
)
# 给一点点启动时间(不要长等,避免阻塞)
sleep 0.2
fi
fi
fi
# 5) 统一导出(给后续脚本用)
# 5) 导出变量
export Subconverter_Bin
export Subconverter_Ready
export SUBCONVERTER_BIN="$Subconverter_Bin"
export SUBCONVERTER_READY="$Subconverter_Ready"
export SUBCONVERTER_URL
# 永不失败
true
}
+11 -11
View File
@@ -20,19 +20,19 @@ for arg in "$@"; do
--foreground) FOREGROUND=true ;;
--daemon) DAEMON=true ;;
*)
echo "[ERROR] Unknown arg: $arg" >&2
echo "[ERROR] 未知参数: $arg" >&2
exit 2
;;
esac
done
if [ "$FOREGROUND" = true ] && [ "$DAEMON" = true ]; then
echo "[ERROR] Cannot use both --foreground and --daemon" >&2
echo "[ERROR] 不能同时使用 --foreground 和 --daemon" >&2
exit 2
fi
if [ "$FOREGROUND" = false ] && [ "$DAEMON" = false ]; then
echo "[ERROR] Must specify --foreground or --daemon" >&2
echo "[ERROR] 必须指定 --foreground 或 --daemon" >&2
exit 2
fi
@@ -40,12 +40,12 @@ fi
# 基础校验
# =========================
if [ ! -s "$CONFIG_FILE" ]; then
echo "[ERROR] runtime config not found: $CONFIG_FILE" >&2
echo "[错ERROR误] 未找到运行配置文件: $CONFIG_FILE" >&2
exit 2
fi
if grep -q '\${' "$CONFIG_FILE"; then
echo "[ERROR] unresolved placeholder found in $CONFIG_FILE" >&2
echo "[ERROR] 配置文件存在未替换变量: $CONFIG_FILE" >&2
exit 2
fi
@@ -65,15 +65,15 @@ source "$PROJECT_DIR/scripts/service_lib.sh"
CLASH_BIN="$(resolve_clash_bin "$PROJECT_DIR" "${CpuArch:-}")"
if [ ! -x "$CLASH_BIN" ]; then
echo "[ERROR] clash binary not executable: $CLASH_BIN" >&2
echo "[ERROR] Clash 二进制不可执行: $CLASH_BIN" >&2
exit 2
fi
# =========================
# config 测试(唯一一次)
# 配置校验(仅执行一次)
# =========================
if ! "$CLASH_BIN" -t -f "$CONFIG_FILE" -d "$RUNTIME_DIR" >/dev/null 2>&1; then
echo "[ERROR] clash config test failed: $CONFIG_FILE" >&2
echo "[ERROR] Clash 配置校验失败: $CONFIG_FILE" >&2
write_run_state "failed" "config-test"
exit 2
fi
@@ -87,13 +87,13 @@ if [ "$FOREGROUND" = true ]; then
fi
# =========================
# 后台模式(script)
# 后台模式(脚本)
# =========================
cleanup_dead_pid
if is_script_running; then
pid="$(read_pid 2>/dev/null || true)"
echo "[INFO] clash already running, pid=${pid:-unknown}"
echo "[INFO] Clash 已在运行,pid=${pid:-未知}"
exit 0
fi
@@ -104,4 +104,4 @@ echo "$pid" > "$PID_FILE"
write_run_state "running" "script" "$pid"
echo "[OK] Clash started in script mode, pid=$pid"
echo "[SUCCESS] 已以脚本模式启动 Clash,pid=$pid"
+13 -13
View File
@@ -16,8 +16,8 @@ for arg in "$@"; do
PURGE=true
;;
*)
echo "[ERROR] Unknown arg: $arg" >&2
echo "Usage: uninstall.sh [--purge]" >&2
echo "[ERROR] 未知参数: $arg" >&2
echo "用法: uninstall.sh [--purge]" >&2
exit 2
;;
esac
@@ -29,11 +29,11 @@ warn() { log "\033[33m[WARN]\033[0m $*"; }
err() { log "\033[31m[ERROR]\033[0m $*"; }
if [ "$(id -u)" -ne 0 ]; then
err "需要 root 权限执行卸载"
err "卸载需要 root 权限"
exit 1
fi
echo "[INFO] uninstalling clash-for-linux..."
echo "[INFO] 正在卸载 clash-for-linux..."
# =========================
# 停止服务
@@ -52,12 +52,12 @@ if [ -f "$PID_FILE" ]; then
PID="$(cat "$PID_FILE" 2>/dev/null || true)"
if [ -n "${PID:-}" ] && kill -0 "$PID" 2>/dev/null; then
echo "[INFO] stopping pid=$PID"
echo "[INFO] 正在停止进程 pid=$PID"
kill "$PID" 2>/dev/null || true
sleep 1
if kill -0 "$PID" 2>/dev/null; then
echo "[WARN] force kill -9 $PID"
echo "[WARN] 强制结束进程 -9 $PID"
kill -9 "$PID" 2>/dev/null || true
fi
fi
@@ -70,7 +70,7 @@ fi
# =========================
if [ -f "$UNIT_PATH" ]; then
rm -f "$UNIT_PATH"
ok "removed systemd unit"
ok "已移除 systemd 服务"
fi
if command -v systemctl >/dev/null 2>&1; then
@@ -84,7 +84,7 @@ fi
rm -f "$CLASHCTL_LINK" >/dev/null 2>&1 || true
rm -f "$PROFILED_FILE" >/dev/null 2>&1 || true
ok "removed command + env"
ok "已清理命令入口及环境变量"
# =========================
# 删除安装目录
@@ -92,14 +92,14 @@ ok "removed command + env"
if [ "$PURGE" = true ]; then
if [ -d "$INSTALL_DIR" ]; then
rm -rf "$INSTALL_DIR"
ok "removed install dir: $INSTALL_DIR"
ok "已删除安装目录: $INSTALL_DIR"
else
warn "install dir not found: $INSTALL_DIR"
warn "未找到安装目录: $INSTALL_DIR"
fi
else
warn "install dir preserved: $INSTALL_DIR"
echo "run with --purge to remove it"
warn "安装目录已保留: $INSTALL_DIR"
echo "如需彻底删除,请执行:bash uninstall.sh --purge"
fi
echo
ok "uninstall complete"
ok "卸载完成"