diff --git a/clashctl b/clashctl index f87e62e..1d114ec 100755 --- a/clashctl +++ b/clashctl @@ -263,6 +263,13 @@ cmd_update() { cmd_status() { local mode running="no" + local service_active="" service_enabled="" + local pid="" + local controller dashboard_url + local config_source generate_status generate_reason generate_at + local run_status run_mode run_pid run_at + local http_port dashboard_port + local secret_exists="no" mode="$(detect_mode)" @@ -271,46 +278,132 @@ cmd_status() { if systemctl is-active --quiet "$SERVICE_NAME"; then running="yes" fi + service_active="$(systemctl is-active "$SERVICE_NAME" 2>/dev/null || true)" + service_enabled="$(systemctl is-enabled "$SERVICE_NAME" 2>/dev/null || true)" + ;; + systemd-installed) + running="no" + service_active="$(systemctl is-active "$SERVICE_NAME" 2>/dev/null || true)" + service_enabled="$(systemctl is-enabled "$SERVICE_NAME" 2>/dev/null || true)" ;; script) if is_script_running; then running="yes" fi + pid="$(read_pid 2>/dev/null || true)" ;; none) running="no" ;; esac - echo "=== Clash Status ===" - echo "Mode : $mode" - echo "Running : $running" - echo "Config : $RUNTIME_CONFIG" + generate_status="$(read_state_value LAST_GENERATE_STATUS || true)" + generate_reason="$(read_state_value LAST_GENERATE_REASON || true)" + config_source="$(read_state_value LAST_CONFIG_SOURCE || true)" + generate_at="$(read_state_value LAST_GENERATE_AT || true)" - if [ "$mode" = "systemd" ] && service_unit_exists; then - echo "Service : installed" - echo "Active : $(systemctl is-active "$SERVICE_NAME" 2>/dev/null || true)" - echo "Enabled : $(systemctl is-enabled "$SERVICE_NAME" 2>/dev/null || true)" + run_status="$(read_state_value LAST_RUN_STATUS || true)" + run_mode="$(read_state_value LAST_RUN_MODE || true)" + run_pid="$(read_state_value LAST_RUN_PID || true)" + run_at="$(read_state_value LAST_RUN_AT || true)" + + controller="$(read_runtime_config_value "external-controller" || true)" + dashboard_url="$(cmd_ui --raw 2>/dev/null || true)" + http_port="$(http_port_from_config)" + dashboard_port="$(port_from_controller)" + + if [ -n "$(read_runtime_config_value "secret" || true)" ]; then + secret_exists="yes" fi - if [ "$mode" = "script" ]; then - local pid - pid="$(read_pid 2>/dev/null || true)" - echo "PID : ${pid:-unknown}" + echo "=== Clash Status ===" + echo "Project : $PROJECT_DIR" + echo "Mode : $mode" + echo "Running : $running" + echo "Config : $RUNTIME_CONFIG" + + if [ -f "$RUNTIME_CONFIG" ]; then + echo "ConfigExists : yes" + else + echo "ConfigExists : no" fi if [ -f "$STATE_FILE" ]; then - echo "LastStatus : $(read_state_value LAST_GENERATE_STATUS || true)" - echo "LastReason : $(read_state_value LAST_GENERATE_REASON || true)" - echo "LastSource : $(read_state_value LAST_CONFIG_SOURCE || true)" - echo "LastAt : $(read_state_value LAST_GENERATE_AT || true)" + echo "StateFile : $STATE_FILE" + else + echo "StateFile : missing" fi - local controller - controller="$(read_runtime_config_value "external-controller" || true)" - if [ -n "${controller:-}" ]; then - echo "Dashboard : $(cmd_ui --raw)" + case "$mode" in + systemd|systemd-installed) + echo "Service : installed" + echo "Active : ${service_active:-unknown}" + echo "Enabled : ${service_enabled:-unknown}" + ;; + script) + echo "Service : script" + echo "PID : ${pid:-unknown}" + ;; + none) + echo "Service : none" + ;; + esac + + echo "Generate : ${generate_status:-unknown}" + if [ -n "${generate_reason:-}" ]; then + echo "GenReason : $generate_reason" fi + if [ -n "${config_source:-}" ]; then + echo "ConfigSource : $config_source" + fi + if [ -n "${generate_at:-}" ]; then + echo "GeneratedAt : $generate_at" + fi + + if [ -n "${run_status:-}" ]; then + echo "RunStatus : $run_status" + fi + if [ -n "${run_mode:-}" ]; then + echo "RunMode : $run_mode" + fi + if [ -n "${run_pid:-}" ]; then + echo "RunPID : $run_pid" + fi + if [ -n "${run_at:-}" ]; then + echo "RunAt : $run_at" + fi + + echo "ProxyPort : $http_port" + echo "DashPort : $dashboard_port" + + if [ -n "${controller:-}" ]; then + echo "Controller : $controller" + else + echo "Controller : 127.0.0.1:9090 (fallback)" + fi + + if [ -n "${dashboard_url:-}" ]; then + echo "Dashboard : $dashboard_url" + fi + + echo "Secret : $secret_exists" + + echo + case "$running" in + yes) + ok "status summary: running" + ;; + no) + if [ "$mode" = "systemd-installed" ]; then + warn "status summary: installed but not running" + else + warn "status summary: not running" + fi + ;; + *) + warn "status summary: unknown" + ;; + esac } doctor_ok() { diff --git a/config/Country.mmdb b/config/Country.mmdb new file mode 100644 index 0000000..6be679f Binary files /dev/null and b/config/Country.mmdb differ diff --git a/config/mixin.d/README.md b/config/mixin.d/README.md new file mode 100644 index 0000000..6bb5cf2 --- /dev/null +++ b/config/mixin.d/README.md @@ -0,0 +1,9 @@ +# Mixin 配置目录 + +将额外的 Clash YAML 配置放在此目录下,脚本会按文件名排序后依次拼接到生成的 `config.yaml` 末尾。 + +如需手动指定顺序或使用自定义路径,请在 `.env` 中设置: + +```bash +export CLASH_MIXIN_PATHS='conf/mixin.d/base.yaml,conf/mixin.d/rules.yaml' +``` diff --git a/config/templete.yaml b/config/templete.yaml new file mode 100644 index 0000000..0b57ae4 --- /dev/null +++ b/config/templete.yaml @@ -0,0 +1,31 @@ +# HTTP 代理端口 +port: CLASH_HTTP_PORT_PLACEHOLDER + +# SOCKS5 代理端口 +socks-port: CLASH_SOCKS_PORT_PLACEHOLDER + +# Linux 和 macOS 的 redir 代理端口 +redir-port: CLASH_REDIR_PORT_PLACEHOLDER + +# 监听IP地址 +bind-address: CLASH_LISTEN_IP_PLACEHOLDER + +# 允许局域网的连接 +allow-lan: CLASH_ALLOW_LAN_PLACEHOLDER + +# 规则模式:Rule(规则) / Global(全局代理)/ Direct(全局直连) +mode: rule + +# 设置日志输出级别 (默认级别:silent,即不输出任何内容,以避免因日志内容过大而导致程序内存溢出)。 +# 5 个级别:silent / info / warning / error / debug。级别越高日志输出量越大,越倾向于调试,若需要请自行开启。 +log-level: silent +# Clash 的 RESTful API +external-controller: 'EXTERNAL_CONTROLLER_PLACEHOLDER' + +# RESTful API 的口令 +secret: 'b&ZlKTte5OnEt2Sn' + +# 您可以将静态网页资源(如 clash-dashboard)放置在一个目录中,clash 将会服务于 `RESTful API/ui` +# 参数应填写配置目录的相对路径或绝对路径。 +# external-ui: /code/clash-dashboard + diff --git a/install.sh b/install.sh index 5c2d3f5..8735b10 100755 --- a/install.sh +++ b/install.sh @@ -470,7 +470,10 @@ if [ -f "$Install_Dir/clashoff" ]; then fi # ========================= -# 友好收尾输出(闭环) +# 友好收尾输出 +# - 不再强调瞬时 active / inactive +# - 统一引导到 clashctl +# - 兼容 systemd / 非 systemd # ========================= section "安装完成" @@ -478,28 +481,37 @@ ok "Clash for Linux 已安装至: $(path "${Install_Dir}")" log "📦 安装目录:$(path "${Install_Dir}")" log "👤 运行用户:${Service_User}:${Service_Group}" -log "🔧 服务名称:${Service_Name}.service" +log "🧩 管理入口:$(cmd "clashctl")" if command -v systemctl >/dev/null 2>&1; then - section "服务状态" - - se="${Service_Enabled:-unknown}" - ss="${Service_Started:-unknown}" - - [[ "$se" == "enabled" ]] && se_colored="$(good "$se")" || se_colored="$(bad "$se")" - [[ "$ss" == "active" ]] && ss_colored="$(good "$ss")" || ss_colored="$(bad "$ss")" - - log "🧷 开机自启:${se_colored}" - log "🟢 服务状态:${ss_colored}" - - log "" - log "${C_BOLD}常用命令:${C_NC}" - log " $(cmd "systemctl status ${Service_Name}.service")" - log " $(cmd "systemctl restart ${Service_Name}.service")" + log "🔧 服务名称:${Service_Name}.service" +else + log "🔧 运行模式:非 systemd 环境(将使用脚本模式兜底)" fi # ========================= -# Dashboard / Secret +# 安装结果 +# 不在这里渲染瞬时运行态,避免误导 +# ========================= +section "安装结果" + +ok "核心文件已就位" +ok "运行入口已收敛为 clashctl" + +if [[ -x "/usr/local/bin/clashctl" ]]; then + ok "命令已可用:$(cmd "clashctl")" +else + warn "未检测到 /usr/local/bin/clashctl,请确认安装脚本是否已完成链接" +fi + +log "" +log "${C_BOLD}推荐下一步:${C_NC}" +log " 1. $(cmd "clashctl generate") # 生成运行配置" +log " 2. $(cmd "clashctl start") # 启动 Clash" +log " 3. $(cmd "clashctl doctor") # 健康检查" + +# ========================= +# 控制面板 / Secret # ========================= section "控制面板" @@ -534,16 +546,24 @@ fi CONF_DIR="$Install_Dir/conf" TEMP_DIR="$Install_Dir/temp" +RUNTIME_DIR="$Install_Dir/runtime" SECRET_VAL="" SECRET_FILE="" +read_secret_safe() { + local f="$1" + [[ -f "$f" ]] || return 1 + read_secret_from_config "$f" 2>/dev/null || true +} + for _ in {1..15}; do for f in \ + "$RUNTIME_DIR/config.yaml" \ "$TEMP_DIR/config.yaml" \ "$CONF_DIR/config.yaml" do - SECRET_VAL="$(read_secret_from_config "$f" 2>/dev/null || true)" + SECRET_VAL="$(read_secret_safe "$f")" if [[ -n "$SECRET_VAL" ]]; then SECRET_FILE="$f" break 2 @@ -555,16 +575,14 @@ done dash="http://${api_host}:${api_port}/ui" log "🌐 Dashboard:$(url "$dash")" -SHOW_FILE="${SECRET_FILE:-$CONF_DIR/config.yaml}" +SHOW_FILE="${SECRET_FILE:-$RUNTIME_DIR/config.yaml}" if [[ -n "$SECRET_VAL" ]]; then - MASKED="${SECRET_VAL}" - log "🔐 Secret:${C_YELLOW}${MASKED}${C_NC}" - # log " 查看完整 Secret:$(cmd "sed -nE 's/^[[:space:]]*secret:[[:space:]]*//p' \"$SHOW_FILE\" | head -n 1")" + log "🔐 Secret:${C_YELLOW}${SECRET_VAL}${C_NC}" else - log "🔐 Secret:${C_YELLOW}启动中暂未读到(稍后再试)${C_NC}" - log " 稍后查看:$(cmd "sed -nE 's/^[[:space:]]*secret:[[:space:]]*//p' \"$CONF_DIR/config.yaml\" | head -n 1")" - log " 也可检查运行态:$(cmd "sed -nE 's/^[[:space:]]*secret:[[:space:]]*//p' \"$TEMP_DIR/config.yaml\" | head -n 1")" + log "🔐 Secret:${C_YELLOW}暂未读取到${C_NC}" + log " 启动后可查看:$(cmd "sed -nE 's/^[[:space:]]*secret:[[:space:]]*//p' \"$RUNTIME_DIR/config.yaml\" | head -n 1")" + log " 或检查旧路径:$(cmd "sed -nE 's/^[[:space:]]*secret:[[:space:]]*//p' \"$TEMP_DIR/config.yaml\" | head -n 1")" fi # ========================= @@ -580,20 +598,41 @@ else warn "订阅地址未配置(必须)" log "" log "配置订阅地址:" - log " $(cmd "bash -c 'echo \"CLASH_URL=<订阅地址>\" > ${ENV_FILE}'")" + log " $(cmd "bash -c 'printf \"%s\n\" \"CLASH_URL=<订阅地址>\" > \"${ENV_FILE}\"'")" log "" - log "配置完成后重启服务:" - log " $(cmd "systemctl restart ${Service_Name}.service")" + log "配置完成后执行:" + log " 1. $(cmd "clashctl generate")" + log " 2. $(cmd "clashctl start")" + log " 3. $(cmd "clashctl doctor")" fi # ========================= -# 下一步 +# 常用命令(统一只教 clashctl) # ========================= -section "下一步开启代理(可选)" +section "常用命令" -log " $(cmd "clashctl on") # 开启当前终端代理" -log " $(cmd "clashctl off") # 关闭当前终端代理" -log " $(cmd "clashctl status") # 查看状态" +log " $(cmd "clashctl status") # 查看运行状态" +log " $(cmd "clashctl logs") # 查看最近日志" +log " $(cmd "clashctl logs -f") # 持续追踪日志" +log " $(cmd "clashctl stop") # 停止 Clash" +log " $(cmd "clashctl restart") # 重启 Clash" +log " $(cmd "clashctl doctor") # 健康检查" + +# ========================= +# 终端代理(可选) +# ========================= +section "终端代理(可选)" + +log " $(cmd "clashctl on") # 开启当前终端代理" +log " $(cmd "clashctl off") # 关闭当前终端代理" + +# ========================= +# 旧入口收敛提示 +# ========================= +section "说明" + +log "旧脚本已收敛,请优先使用:$(cmd "clashctl")" +log "不建议继续直接操作 restart.sh / update.sh / 手写 systemctl 命令" # ========================= # 启动后快速诊断 @@ -602,6 +641,7 @@ sleep 1 if command -v journalctl >/dev/null 2>&1; then if journalctl -u "${Service_Name}.service" -n 50 --no-pager 2>/dev/null \ | grep -q "Clash订阅地址不可访问"; then - warn "服务启动异常:订阅不可用,请检查 CLASH_URL(可能过期 / 404 / 被墙)。" + warn "检测到启动异常:订阅不可用,请检查 CLASH_URL(可能过期 / 404 / 被墙)" + log "建议执行:$(cmd "clashctl doctor")" fi fi \ No newline at end of file diff --git a/logs/clash.log b/logs/clash.log new file mode 100644 index 0000000..e69de29 diff --git a/runtime/clash.pid b/runtime/clash.pid new file mode 100644 index 0000000..e69de29 diff --git a/runtime/proxy.env b/runtime/proxy.env new file mode 100644 index 0000000..e69de29 diff --git a/runtime/state.env b/runtime/state.env new file mode 100644 index 0000000..e69de29 diff --git a/scripts/doctor.sh b/scripts/doctor.sh new file mode 100644 index 0000000..e69de29 diff --git a/scripts/logs.sh b/scripts/logs.sh new file mode 100644 index 0000000..e69de29