diff --git a/.env b/.env index dcc7801..8d12e89 100644 --- a/.env +++ b/.env @@ -31,6 +31,12 @@ export CLASH_SUBSCRIPTION='' # 留空时:脚本可自动生成随机值(若你的启动脚本支持) export CLASH_SECRET='' +# 是否在启动输出中显示完整 Secret(不推荐) +CLASH_SHOW_SECRET=true + +# 是否显示脱敏 Secret(推荐) +CLASH_SHOW_SECRET_MASKED=true + # External Controller(Clash RESTful API) # ⚠️ 安全建议: # - 默认仅监听本机:127.0.0.1:9090 (推荐) diff --git a/conf/config.yaml b/conf/config.yaml index f1b4f85..ab9cb7c 100644 --- a/conf/config.yaml +++ b/conf/config.yaml @@ -1,9 +1,20 @@ mixed-port: 7890 allow-lan: false +bind-address: '*' mode: rule log-level: info +ipv6: true +udp: true + +external-controller: 127.0.0.1:9090 +external-ui: /opt/clash-for-linux/dashboard/public +secret: "" proxies: [] +proxy-groups: [] rules: - MATCH,DIRECT + + + diff --git a/conf/fallback_config.yaml b/conf/fallback_config.yaml index 444a1d1..250c095 100644 --- a/conf/fallback_config.yaml +++ b/conf/fallback_config.yaml @@ -1,9 +1,17 @@ mixed-port: 7890 allow-lan: false +bind-address: '*' mode: rule log-level: info +ipv6: true +udp: true + +external-controller: 127.0.0.1:9090 +external-ui: /opt/clash-for-linux/dashboard/public +secret: "" proxies: [] +proxy-groups: [] rules: - MATCH,DIRECT diff --git a/install.sh b/install.sh index fd9a98e..fc400fa 100755 --- a/install.sh +++ b/install.sh @@ -239,13 +239,29 @@ api_host="${EXTERNAL_CONTROLLER%:*}" if [ -z "$api_host" ] || [ "$api_host" = "$EXTERNAL_CONTROLLER" ]; then api_host="127.0.0.1" fi -echo -e "🌐 Dashboard:http://${api_host}:${api_port}/ui" -# secret 可能在 .env 里是 CLASH_SECRET -if [ -n "${CLASH_SECRET:-}" ]; then - echo -e "🔐 Secret:${CLASH_SECRET}" +# ---- Secret 展示(脱敏)---- +CONF_DIR="${CLASH_INSTALL_DIR:-/opt/clash-for-linux}/conf" +CONF_FILE="$CONF_DIR/config.yaml" + +# 读取 secret(如果 clash 还没生成 config,就先不显示) +SECRET_VAL="" +if [ -f "$CONF_FILE" ]; then + SECRET_VAL="$(awk -F': *' '/^secret:/{print $2; exit}' "$CONF_FILE" | tr -d '"' | tr -d "'" )" +fi + +if [ -n "$SECRET_VAL" ]; then + # 脱敏显示:前4后4 + MASKED="${SECRET_VAL:0:4}****${SECRET_VAL: -4}" + echo "" + echo -e "🌐 Dashboard:http://${api_host}:${api_port}/ui" + echo "🔐 Secret:${MASKED}" + echo " 查看完整 Secret:sudo awk -F': *' '/^secret:/{print \$2; exit}' $CONF_FILE" else - echo -e "🔐 Secret:请查看 .env 或启动日志输出" + echo "" + echo -e "🌐 Dashboard:http://${api_host}:${api_port}/ui" + echo "🔐 Secret:未配置(当前为无鉴权模式,仅限本机访问),可用以下命令查看:" + echo " sudo awk -F': *' '/^secret:/{print \$2; exit}' $CONF_FILE" fi echo diff --git a/start.sh b/start.sh index db2d94d..3b5ba9a 100644 --- a/start.sh +++ b/start.sh @@ -53,15 +53,38 @@ else fi fi -# 获取 CLASH_SECRET 值:优先 .env;否则尝试读取旧 config;否则生成随机数 +# 获取 CLASH_SECRET 值:优先 .env;其次读取旧 config;占位符视为无效;最后生成随机值 Secret="${CLASH_SECRET:-}" + +# 尝试从旧 config.yaml 读取(仅当 .env 未提供) if [ -z "$Secret" ] && [ -f "$Conf_Dir/config.yaml" ]; then - Secret="$(awk -F': ' '/^secret:/{print $2; exit}' "$Conf_Dir/config.yaml" || true)" + Secret="$(awk -F': *' '/^secret:/{gsub(/"/,"",$2); print $2; exit}' "$Conf_Dir/config.yaml" || true)" fi + +# 若读取到的是占位符(如 ${CLASH_SECRET}),视为无效 +if [[ "$Secret" =~ ^\$\{.*\}$ ]]; then + Secret="" +fi + +# 兜底生成随机 secret if [ -z "$Secret" ]; then Secret="$(openssl rand -hex 32)" fi +# 强制写入 secret 到指定配置文件(存在则替换,不存在则追加) +force_write_secret() { + local file="$1" + [ -f "$file" ] || return 0 + + if grep -qE '^[[:space:]]*secret:' "$file"; then + # 替换整行 secret(无论原来是啥,包括 SECRET_PLACEHOLDER / "${CLASH_SECRET}") + sed -i -E "s|^[[:space:]]*secret:.*$|secret: ${Secret}|g" "$file" + else + # 没有 secret 行就追加到文件末尾 + printf "\nsecret: %s\n" "$Secret" >> "$file" + fi +} + # 设置默认值 CLASH_HTTP_PORT="${CLASH_HTTP_PORT:-7890}" CLASH_SOCKS_PORT="${CLASH_SOCKS_PORT:-7891}" @@ -155,6 +178,8 @@ ensure_fallback_config() { exit 1 fi fi + # 强制写入真实 secret + force_write_secret "$Conf_Dir/config.yaml" } SKIP_CONFIG_REBUILD=false @@ -319,11 +344,11 @@ if [ "$SKIP_CONFIG_REBUILD" != "true" ]; then fi # 写入 secret - sed -r -i "/^secret: /s@(secret: ).*@\1${Secret}@g" "$Conf_Dir/config.yaml" || true + force_write_secret "$Conf_Dir/config.yaml" else # 兜底路径:尽量也写入 secret(若 config 里有 secret: 行就替换;没有就追加) if grep -qE '^secret:\s*' "$Conf_Dir/config.yaml" 2>/dev/null; then - sed -r -i "/^secret: /s@(secret: ).*@\1${Secret}@g" "$Conf_Dir/config.yaml" || true + force_write_secret "$Conf_Dir/config.yaml" else echo "secret: ${Secret}" >> "$Conf_Dir/config.yaml" || true fi @@ -337,6 +362,12 @@ if [ ! -s "$Conf_Dir/config.yaml" ]; then exit 1 fi +# 最终护栏:禁止未渲染的占位符进入运行态 +if grep -q '\${' "$Conf_Dir/config.yaml"; then + echo "[ERROR] config.yaml contains unresolved placeholders (\${...}). Please check template rendering." >&2 + exit 1 +fi + echo -e '\n正在启动Clash服务...' Text5="服务启动成功!" Text6="服务启动失败!" @@ -360,7 +391,19 @@ if_success "$Text5" "$Text6" "$ReturnStatus" echo '' if [ "$EXTERNAL_CONTROLLER_ENABLED" = "true" ]; then echo -e "Clash Dashboard 访问地址: http://${EXTERNAL_CONTROLLER}/ui" - echo -e "Secret: ${Secret}" + + SHOW_SECRET="${CLASH_SHOW_SECRET:-false}" + SHOW_SECRET_MASKED="${CLASH_SHOW_SECRET_MASKED:-true}" + + if [ "$SHOW_SECRET" = "true" ]; then + echo -e "Secret: ${Secret}" + elif [ "$SHOW_SECRET_MASKED" = "true" ]; then + # 脱敏:前4后4 + masked="${Secret:0:4}****${Secret: -4}" + echo -e "Secret: ${masked} (set CLASH_SHOW_SECRET=true to show full)" + else + echo -e "Secret: 已生成(未显示)。查看:/opt/clash-for-linux/conf/config.yaml 或 .env" + fi else echo -e "External Controller (Dashboard) 已禁用" fi