mirror of
https://github.com/wnlen/clash-for-linux.git
synced 2026-10-10 04:03:04 +08:00
@@ -60,6 +60,7 @@ bash install.sh
|
||||
clashsecret 🔑 查看或设置 Web 密钥
|
||||
📦 订阅
|
||||
clashctl add ➕ 添加订阅
|
||||
clashctl add local ➕ 从 runtime/subscriptions 导入本地订阅
|
||||
clashctl use 💱 切换订阅
|
||||
clashctl ls 📡 查看订阅列表
|
||||
📌 高级
|
||||
@@ -157,6 +158,33 @@ clashctl sub remove <名称>
|
||||
|
||||
WSL / 普通用户如果无权写入 `/etc/environment`,`clashon` 会自动降级:运行时照常启动,当前 Shell 代理变量生效;系统代理持久接管和开机代理保持不可用。
|
||||
|
||||
### 本地配置导入
|
||||
|
||||
推荐使用交互导入,放置目录为:`$PROJECT_DIR/runtime/subscriptions/`
|
||||
|
||||
```bash
|
||||
clashctl add local
|
||||
# 输入:clash.yaml
|
||||
```
|
||||
|
||||
实际等价于:
|
||||
|
||||
```bash
|
||||
clashctl add "file://$PROJECT_DIR/runtime/subscriptions/clash.yaml"
|
||||
```
|
||||
|
||||
进阶用法:也可以直接使用 `file://` 绝对路径导入:
|
||||
|
||||
```bash
|
||||
clashctl add "file:///绝对路径/clash.yaml" home
|
||||
```
|
||||
|
||||
支持格式:
|
||||
|
||||
- Clash / Mihomo YAML
|
||||
- Base64 订阅
|
||||
- 分享链接(`vmess` / `vless` / `trojan` / `tuic`)
|
||||
|
||||
### 开机接管(内核 + 代理)
|
||||
|
||||
```bash
|
||||
@@ -171,23 +199,37 @@ clashctl boot proxy on|off|status
|
||||
- `boot proxy`:只管理 `/etc/environment` 中的代理持久块(决定开机后是否自动保持代理变量)。
|
||||
- `boot`:整体接管开关,等价于同时编排 runtime + proxy 两层状态。
|
||||
|
||||
### 本地订阅
|
||||
------
|
||||
|
||||
可以先手动下载 Clash 格式的订阅 YAML 文件到本地,再通过 `file://` 添加:
|
||||
## OpenWrt 脚本模式
|
||||
|
||||
格式:`clashctl add "file:///绝对路径/xxx.yaml" 名称`
|
||||
当前提供 OpenWrt 脚本模式兼容,适合 x86_64/amd64 与 aarch64/arm64 软路由或设备。该模式复用现有 `script` 运行后端,不包含 procd 开机自启、LuCI、UCI 或 opkg 包化支持,也不承诺 MIPS 与 armv7 设备可用。
|
||||
|
||||
建议先将项目放到持久化目录,避免放在 `/tmp`、`/run` 等重启会丢失的位置:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/clash-for-linux/subscriptions
|
||||
curl -L "https://example.com/sub.yaml" -o ~/.config/clash-for-linux/subscriptions/home.yaml
|
||||
clashctl add "file://$HOME/.config/clash-for-linux/subscriptions/home.yaml" home
|
||||
clashctl use home
|
||||
clashon
|
||||
cd /root
|
||||
git clone --branch master --depth 1 https://ghfast.top/https://github.com/wnlen/clash-for-linux.git
|
||||
cd clash-for-linux
|
||||
```
|
||||
|
||||
`file://` 只支持 Clash 本地 YAML 配置,不支持 convert 格式订阅。如果订阅源需要转换,请先转换为 Clash YAML 后再保存到本地文件。
|
||||
安装依赖:
|
||||
|
||||
------
|
||||
```bash
|
||||
opkg update
|
||||
opkg install bash curl tar gzip coreutils-readlink unzip
|
||||
```
|
||||
|
||||
安装与手动管理:
|
||||
|
||||
```bash
|
||||
bash install.sh
|
||||
clashon
|
||||
clashctl status
|
||||
clashoff
|
||||
```
|
||||
|
||||
OpenWrt 下 root/system 安装会把 `clashctl`、`clashon`、`clashoff` 等命令入口写入 `/usr/bin`,运行状态、日志和内核二进制仍保存在项目目录的 `runtime/` 下。仅脚本模式不会注册开机自启,设备重启后需要重新执行 `clashon`。
|
||||
|
||||
## 🏗️ 架构设计架构简述
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ Usage:
|
||||
|
||||
🚀 Main Path:
|
||||
add ➕ 添加订阅
|
||||
add local ➕ 从 runtime/subscriptions 导入本地订阅
|
||||
use 💱 切换订阅
|
||||
select 💫 切换节点
|
||||
|
||||
@@ -91,6 +92,7 @@ usage_advanced() {
|
||||
|
||||
🚀 Main Path Reminder:
|
||||
clashctl add [订阅链接] [名称]
|
||||
clashctl add local
|
||||
clashctl use
|
||||
clashon
|
||||
clashctl select
|
||||
@@ -275,7 +277,6 @@ cmd_on() {
|
||||
ui_warn "系统代理持久接管写入失败,仅当前 Shell 生效"
|
||||
ui_next "clashctl doctor"
|
||||
fi
|
||||
ui_blank
|
||||
fi
|
||||
|
||||
load_system_state
|
||||
@@ -2876,6 +2877,7 @@ cmd_ui_help_summary() {
|
||||
printf ' %-18s %s\n' "clashsecret" "🔑 查看或设置 Web 密钥"
|
||||
echo "📦 订阅"
|
||||
printf ' %-18s %s\n' "clashctl add" "➕ 添加订阅"
|
||||
printf ' %-18s %s\n' "clashctl add local" "➕ 从 runtime/subscriptions 导入本地订阅"
|
||||
printf ' %-18s %s\n' "clashctl use" "💱 切换订阅"
|
||||
printf ' %-18s %s\n' "clashctl ls" "📡 查看订阅列表"
|
||||
echo "📌 高级"
|
||||
@@ -5741,12 +5743,36 @@ add_prompt_name() {
|
||||
done
|
||||
}
|
||||
|
||||
add_prompt_local_url() {
|
||||
local input filename local_path
|
||||
|
||||
printf "请输入本地订阅文件名(默认目录:%s/runtime/subscriptions/):" "$PROJECT_DIR" >&2
|
||||
IFS= read -r input || return 1
|
||||
filename="$(add_trim_input "$input")"
|
||||
|
||||
[ -n "${filename:-}" ] || die "本地订阅文件名不能为空"
|
||||
|
||||
local_path="$PROJECT_DIR/runtime/subscriptions/$filename"
|
||||
|
||||
[ -f "$local_path" ] || die "本地订阅文件不存在:$local_path"
|
||||
[ -s "$local_path" ] || die "本地订阅文件为空:$local_path"
|
||||
|
||||
echo "file://$local_path"
|
||||
}
|
||||
|
||||
cmd_add() {
|
||||
local sub_url sub_name sub_fmt
|
||||
|
||||
prepare
|
||||
ensure_add_use_prerequisites
|
||||
|
||||
if [ "${1:-}" = "local" ]; then
|
||||
[ "$#" -eq 1 ] || die_usage "add local 参数不合法" "clashctl add local"
|
||||
sub_url="$(add_prompt_local_url)"
|
||||
cmd_add "$sub_url"
|
||||
return $?
|
||||
fi
|
||||
|
||||
case "$#" in
|
||||
0)
|
||||
sub_url="$(add_prompt_url)"
|
||||
@@ -5766,7 +5792,7 @@ cmd_add() {
|
||||
|
||||
sub_fmt="$(detect_subscription_format "$sub_url")"
|
||||
|
||||
set_subscription "$sub_url" "$sub_fmt" "$sub_name"
|
||||
set_subscription "$sub_url" "$sub_fmt" "$sub_name" "false"
|
||||
set_active_subscription "$sub_name"
|
||||
apply_runtime_change_after_config_mutation
|
||||
print_add_feedback "$sub_name" "$sub_url"
|
||||
|
||||
+152
-12
@@ -380,6 +380,115 @@ copy_local_subscription_yaml() {
|
||||
[ -s "$out_file" ] || die "本地订阅文件为空:$local_path"
|
||||
}
|
||||
|
||||
local_subscription_share_links_to_subconverter_url() {
|
||||
local file="$1"
|
||||
|
||||
awk '
|
||||
{
|
||||
gsub(/\r/, "")
|
||||
sub(/^[[:space:]]+/, "")
|
||||
sub(/[[:space:]]+$/, "")
|
||||
}
|
||||
$0 == "" || $0 ~ /^#/ { next }
|
||||
$0 ~ /^(vmess|vless|trojan|tuic):\/\// {
|
||||
if (out != "") {
|
||||
out = out "|"
|
||||
}
|
||||
out = out $0
|
||||
}
|
||||
END {
|
||||
if (out == "") {
|
||||
exit 1
|
||||
}
|
||||
print out
|
||||
}
|
||||
' "$file"
|
||||
}
|
||||
|
||||
decode_local_subscription_base64() {
|
||||
local src="$1"
|
||||
local out="$2"
|
||||
local compact len mod
|
||||
|
||||
compact="$(mktemp)"
|
||||
rm -f "$compact" 2>/dev/null || true
|
||||
|
||||
tr -d '\r\n\t ' < "$src" | tr '_-' '/+' > "$compact"
|
||||
[ -s "$compact" ] || {
|
||||
rm -f "$compact" 2>/dev/null || true
|
||||
return 1
|
||||
}
|
||||
|
||||
if grep -Eq '[^A-Za-z0-9+/=]' "$compact"; then
|
||||
rm -f "$compact" 2>/dev/null || true
|
||||
return 1
|
||||
fi
|
||||
|
||||
len="$(wc -c < "$compact" | tr -d '[:space:]')"
|
||||
case "$len" in
|
||||
''|*[!0-9]*)
|
||||
rm -f "$compact" 2>/dev/null || true
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
mod=$((len % 4))
|
||||
case "$mod" in
|
||||
0) ;;
|
||||
2) printf '==' >> "$compact" ;;
|
||||
3) printf '=' >> "$compact" ;;
|
||||
*)
|
||||
rm -f "$compact" 2>/dev/null || true
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if base64 --decode "$compact" > "$out" 2>/dev/null || base64 -d "$compact" > "$out" 2>/dev/null; then
|
||||
rm -f "$compact" 2>/dev/null || true
|
||||
[ -s "$out" ]
|
||||
return
|
||||
fi
|
||||
|
||||
rm -f "$compact" 2>/dev/null || true
|
||||
return 1
|
||||
}
|
||||
|
||||
local_subscription_convert_url_from_url() {
|
||||
local url="$1"
|
||||
local local_path decoded_file convert_url
|
||||
|
||||
LOCAL_SUBSCRIPTION_CONVERT_URL=""
|
||||
local_path="$(subscription_file_path_from_url "$url")"
|
||||
|
||||
[ -f "$local_path" ] || die "本地订阅文件不存在:$local_path"
|
||||
[ -r "$local_path" ] || die "本地订阅文件不可读:$local_path"
|
||||
[ -s "$local_path" ] || die "本地订阅文件为空:$local_path"
|
||||
|
||||
if convert_url="$(local_subscription_share_links_to_subconverter_url "$local_path")"; then
|
||||
info "本地订阅识别为分享链接,将通过 subconverter 转换"
|
||||
LOCAL_SUBSCRIPTION_CONVERT_URL="$convert_url"
|
||||
return 0
|
||||
fi
|
||||
|
||||
decoded_file="$(mktemp)"
|
||||
rm -f "$decoded_file" 2>/dev/null || true
|
||||
|
||||
if decode_local_subscription_base64 "$local_path" "$decoded_file"; then
|
||||
if convert_url="$(local_subscription_share_links_to_subconverter_url "$decoded_file")"; then
|
||||
rm -f "$decoded_file" 2>/dev/null || true
|
||||
info "本地订阅识别为 Base64 分享链接,将通过 subconverter 转换"
|
||||
LOCAL_SUBSCRIPTION_CONVERT_URL="$convert_url"
|
||||
return 0
|
||||
fi
|
||||
|
||||
rm -f "$decoded_file" 2>/dev/null || true
|
||||
die "本地订阅不是 Clash YAML,Base64 解码后未发现支持的分享链接:$local_path"
|
||||
fi
|
||||
|
||||
rm -f "$decoded_file" 2>/dev/null || true
|
||||
die "本地订阅不是 Clash YAML,且无法识别为 Base64 或 vmess/vless/trojan/tuic 分享链接:$local_path"
|
||||
}
|
||||
|
||||
download_subscription_yaml() {
|
||||
local url="$1"
|
||||
local out_file="$2"
|
||||
@@ -2004,6 +2113,7 @@ set_subscription() {
|
||||
local url="$1"
|
||||
local fmt="${2:-}"
|
||||
local name="${3:-default}"
|
||||
local should_regenerate="${4:-true}"
|
||||
local file
|
||||
|
||||
[ -n "$url" ] || die "订阅地址不能为空"
|
||||
@@ -2032,7 +2142,9 @@ set_subscription() {
|
||||
}
|
||||
' "$file"
|
||||
|
||||
regenerate_config
|
||||
if [ "$should_regenerate" = "true" ]; then
|
||||
regenerate_config
|
||||
fi
|
||||
}
|
||||
|
||||
set_active_subscription() {
|
||||
@@ -2945,15 +3057,20 @@ convert_subscription_via_subconverter() {
|
||||
local api tmp_file curl_error_file
|
||||
local curl_meta curl_rc http_code effective_url errexit_was_set
|
||||
local log_file
|
||||
local scheme convert_url
|
||||
local validate_ok="false"
|
||||
|
||||
[ -n "${url:-}" ] || return 1
|
||||
|
||||
case "$(subscription_url_scheme "$url")" in
|
||||
scheme="$(subscription_url_scheme "$url")"
|
||||
convert_url="$url"
|
||||
|
||||
case "$scheme" in
|
||||
http|https)
|
||||
;;
|
||||
file)
|
||||
die "convert 格式暂不支持 file:// 本地订阅,请改用 clash 格式"
|
||||
local_subscription_convert_url_from_url "$url"
|
||||
convert_url="$LOCAL_SUBSCRIPTION_CONVERT_URL"
|
||||
;;
|
||||
*)
|
||||
die "不支持的订阅协议:$url"
|
||||
@@ -2962,7 +3079,7 @@ convert_subscription_via_subconverter() {
|
||||
|
||||
case "$fetch_reason" in
|
||||
auto|install|bootstrap|"")
|
||||
if subscription_cache_restore "$url" "convert" "$out_file"; then
|
||||
if [ "$scheme" != "file" ] && subscription_cache_restore "$url" "convert" "$out_file"; then
|
||||
if subscription_yaml_validate "$out_file"; then
|
||||
return 0
|
||||
fi
|
||||
@@ -2982,7 +3099,11 @@ convert_subscription_via_subconverter() {
|
||||
info "正在通过 subconverter 转换订阅"
|
||||
case "$convert_reason" in
|
||||
direct-clash-invalid)
|
||||
info "转换原因:直连订阅已下载,但不是可直接运行的 Clash YAML"
|
||||
if [ "$scheme" = "file" ]; then
|
||||
info "转换原因:本地订阅不是可直接运行的 Clash YAML"
|
||||
else
|
||||
info "转换原因:直连订阅已下载,但不是可直接运行的 Clash YAML"
|
||||
fi
|
||||
;;
|
||||
subscription-type-convert)
|
||||
info "转换原因:订阅类型为 convert"
|
||||
@@ -2993,7 +3114,11 @@ convert_subscription_via_subconverter() {
|
||||
esac
|
||||
info "subconverter 请求:GET $api"
|
||||
info "subconverter 参数:target=clash"
|
||||
info "subconverter 参数:url=$url"
|
||||
if [ "$scheme" = "file" ]; then
|
||||
info "subconverter 参数:url=<本地订阅分享链接内容>"
|
||||
else
|
||||
info "subconverter 参数:url=$convert_url"
|
||||
fi
|
||||
info "subconverter 未发送参数:insert/config/emoji/list(使用 subconverter 默认值)"
|
||||
|
||||
errexit_was_set="false"
|
||||
@@ -3006,7 +3131,7 @@ convert_subscription_via_subconverter() {
|
||||
|
||||
curl_meta="$(curl -sS -L -G "$api" \
|
||||
--data-urlencode "target=clash" \
|
||||
--data-urlencode "url=$url" \
|
||||
--data-urlencode "url=$convert_url" \
|
||||
-o "$tmp_file" \
|
||||
-w '%{http_code}\n%{url_effective}' 2>"$curl_error_file")"
|
||||
curl_rc=$?
|
||||
@@ -3016,7 +3141,13 @@ convert_subscription_via_subconverter() {
|
||||
http_code="$(printf '%s\n' "$curl_meta" | head -n 1)"
|
||||
effective_url="$(printf '%s\n' "$curl_meta" | sed -n '2p')"
|
||||
|
||||
[ -n "${effective_url:-}" ] && info "subconverter 实际请求 URL:$effective_url"
|
||||
if [ -n "${effective_url:-}" ]; then
|
||||
if [ "$scheme" = "file" ]; then
|
||||
info "subconverter 实际请求 URL:<已省略本地订阅分享链接内容>"
|
||||
else
|
||||
info "subconverter 实际请求 URL:$effective_url"
|
||||
fi
|
||||
fi
|
||||
[ -n "${http_code:-}" ] && info "subconverter HTTP 状态码:$http_code"
|
||||
|
||||
if [ "$curl_rc" -ne 0 ] || [ -z "${http_code:-}" ] || [ "$http_code" -lt 200 ] || [ "$http_code" -ge 300 ]; then
|
||||
@@ -3056,7 +3187,7 @@ convert_subscription_via_subconverter() {
|
||||
fi
|
||||
|
||||
mv -f "$tmp_file" "$out_file"
|
||||
subscription_cache_store "$url" "convert" "$out_file" "$api"
|
||||
[ "$scheme" = "file" ] || subscription_cache_store "$url" "convert" "$out_file" "$api"
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -3253,11 +3384,12 @@ fetch_subscription_source() {
|
||||
local name="$1"
|
||||
local out_file="$2"
|
||||
local fetch_reason="${3:-auto}"
|
||||
local url fmt reason
|
||||
local url fmt reason scheme
|
||||
local raw_file candidate_file
|
||||
|
||||
url="$(subscription_url_by_name "$name")"
|
||||
fmt="$(subscription_format_by_name "$name")"
|
||||
scheme="$(subscription_url_scheme "$url")"
|
||||
|
||||
raw_file="$(mktemp)"
|
||||
candidate_file="$(mktemp)"
|
||||
@@ -3301,9 +3433,17 @@ fetch_subscription_source() {
|
||||
fi
|
||||
|
||||
write_subscription_invalid_debug_snapshot "$raw_file"
|
||||
reason="订阅下载成功,但原始配置与转换结果都不能直接运行"
|
||||
if [ "$scheme" = "file" ]; then
|
||||
reason="本地订阅转换成功,但 config 校验失败"
|
||||
else
|
||||
reason="订阅下载成功,但原始配置与转换结果都不能直接运行"
|
||||
fi
|
||||
else
|
||||
reason="订阅下载成功,但原始配置不能直接运行,且转换失败"
|
||||
if [ "$scheme" = "file" ]; then
|
||||
reason="本地订阅不是 Clash YAML,且 subconverter 转换失败"
|
||||
else
|
||||
reason="订阅下载成功,但原始配置不能直接运行,且转换失败"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
reason="订阅下载失败"
|
||||
|
||||
Reference in New Issue
Block a user