Repository: nelvko/clash-for-linux-install
Branch: master
Commit: 39ca338dbd19
Files: 26
Total size: 54.1 KB
Directory structure:
gitextract_4jtgepcj/
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ ├── config.yml
│ │ ├── feat_report.yml
│ │ └── q&a.yml
│ └── workflows/
│ └── stale-issues.yml
├── .gitignore
├── .shellcheckrc
├── LICENSE
├── README.md
├── install.sh
├── resources/
│ ├── Country.mmdb
│ ├── mixin.yaml
│ ├── profiles/
│ │ └── .gitkeep
│ └── profiles.yaml
├── scripts/
│ ├── cmd/
│ │ ├── clashctl.fish
│ │ ├── clashctl.sh
│ │ └── common.sh
│ ├── init/
│ │ ├── OpenRC.sh
│ │ ├── SysVinit.sh
│ │ ├── runit.sh
│ │ └── systemd.sh
│ └── preflight.sh
└── uninstall.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
end_of_line = lf
indent_style = space
indent_size = 2
================================================
FILE: .gitattributes
================================================
* text=auto eol=lf
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: ['https://afdian.com/a/nelvko']# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug 反馈
description: "提交项目 Bug 报告"
title: "[Bug] "
labels: ["bug"]
body:
- type: checkboxes
id: ensure
attributes:
label: 前置确认
description: 在提交之前,请勾选以下选项以确认您已完成必要的自助排查
options:
- label: 我确认这是项目 **Bug**, 而非操作不当或使用不清楚。否则请使用其他 Issue 模板或移步[讨论区](https://github.com/nelvko/clash-for-linux-install/discussions)
required: true
- label: 我已查阅 [常见问题](https://github.com/nelvko/clash-for-linux-install/wiki/FAQ),但仍未找到解决方案
required: true
- label: 我已使用关键字查询相关 [Issues](https://github.com/nelvko/clash-for-linux-install/issues?q=is%3Aissue),但仍未找到解决方案
required: true
- label: 我已确认本地与远程仓库最新提交的哈希一致。 (`git rev-parse --short HEAD`)
required: true
- type: input
id: branch
attributes:
label: 使用的分支
placeholder: master / ...
validations:
required: true
- type: input
id: commit_id
attributes:
label: Commit ID
description: |
请提供当前 Commit 哈希:`git rev-parse --short HEAD`
validations:
required: true
- type: input
id: shell
attributes:
label: Shell 环境
placeholder: bash / zsh / ...
validations:
required: true
- type: textarea
id: system
attributes:
label: 系统信息
description: |
请提供系统相关信息,可任选以下命令输出之一:
```bash
hostnamectl
lsb_release -a
uname -a
```
render: shell
validations:
required: true
- type: textarea
id: desc
attributes:
label: 描述 & 复现
description: |
请详细描述:
1. 操作步骤
2. 期望结果
3. 实际表现
4. ~~必要时~~最好附上截图
**请勿泄露订阅、IP 等敏感信息**
# placeholder: |
validations:
required: true
- type: textarea
id: logs
attributes:
label: 日志
description: 请提供相关日志输出或截图
render: shell
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
================================================
FILE: .github/ISSUE_TEMPLATE/feat_report.yml
================================================
name: 功能建议
description: 为该项目提出建议
title: "[Feature] "
labels: ["enhancement"]
body:
- type: textarea
attributes:
label: 描述
description: 请提供对于该功能的详细描述。
validations:
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/q&a.yml
================================================
name: Q&A
description: "问题反馈与解答"
title: "[Q&A] "
labels: []
body:
- type: checkboxes
id: ensure
attributes:
label: 前置确认
description: 大部分使用问题可以在下述选项中得到解决,在提交问题前,请先进行自助排查。
options:
- label: 我已查阅 [常见问题](https://github.com/nelvko/clash-for-linux-install/wiki/FAQ),但仍未找到解决方案
required: true
- label: 我已使用关键字查询相关 [Issues](https://github.com/nelvko/clash-for-linux-install/issues?q=is%3Aissue),但仍未找到解决方案
required: true
- type: input
id: branch
attributes:
label: 使用的分支
placeholder: master / ...
validations:
required: true
- type: input
id: commit_id
attributes:
label: Commit ID
description: |
请提供当前 Commit 哈希:`git rev-parse --short HEAD`
validations:
required: true
- type: input
id: shell
attributes:
label: Shell 环境
placeholder: bash / zsh / ...
validations:
required: true
- type: textarea
id: desc
attributes:
label: 问题描述
description: |
~~必要时~~最好附上截图。
**请勿泄露订阅、IP 等敏感信息**
validations:
required: true
================================================
FILE: .github/workflows/stale-issues.yml
================================================
name: Close inactive issues
on:
schedule:
- cron: "0 9 * * 1-5"
workflow_dispatch: # 允许手动触发
jobs:
close-issues:
runs-on: ubuntu-latest
permissions:
issues: write
actions: write
steps:
- uses: actions/stale@v9
with:
exempt-issue-labels: "bug,documentation,enhancement"
days-before-issue-stale: 7
days-before-issue-close: 7
stale-issue-label: "stale"
stale-issue-message: "长时间未活跃,将在 7 天后自动关闭。若仍需讨论,请回复以保持活跃。"
close-issue-message: "已自动关闭,如未解决,可重新打开。"
repo-token: ${{ secrets.GITHUB_TOKEN }} # 自动生成
================================================
FILE: .gitignore
================================================
.idea
.vscode
config.yaml*
resources/zip/*
!resources/zip/dist.zip
resources/dist/
test.sh
!.gitkeep
================================================
FILE: .shellcheckrc
================================================
disable=SC1091
disable=SC2155
disable=SC2296
disable=SC2153
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2024-2026 nelvko
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Linux 一键安装 Clash




## ✨ 功能特性
- 支持一键安装 `mihomo` 与 `clash` 代理内核。
- 兼容 `root` 与普通用户环境。
- 适配主流 `Linux` 发行版,并兼容 `AutoDL` 等容器化环境。
- 自动检测端口占用情况,在冲突时随机分配可用端口。
- 自动识别系统架构与初始化系统,下载匹配的内核与依赖,并生成对应的服务管理配置。
- 在需要时调用 [subconverter](https://github.com/tindy2013/subconverter) 进行本地订阅转换。
## 🚀 一键安装
在终端中执行以下命令即可完成安装:
```bash
git clone --branch master --depth 1 https://gh-proxy.org/https://github.com/nelvko/clash-for-linux-install.git \
&& cd clash-for-linux-install \
&& bash install.sh
```
- 上述命令使用了[加速前缀](https://gh-proxy.org/),如失效可更换其他[可用链接](https://ghproxy.link/)。
- 可通过 `.env` 文件或脚本参数自定义安装选项。
- 没有订阅?[click me](https://次元.net/auth/register?code=oUbI)
## ⌨️ 命令一览
```bash
Usage:
clashctl COMMAND [OPTIONS]
Commands:
on 开启代理
off 关闭代理
status 内核状况
proxy 系统代理
ui Web 面板
secret Web 密钥
sub 订阅管理
upgrade 升级内核
tun Tun 模式
mixin Mixin 配置
Global Options:
-h, --help 显示帮助信息
```
💡`clashon` 同 `clashctl on`,`Tab` 补全更方便!
### 优雅启停
```bash
$ clashon
😼 已开启代理环境
$ clashoff
😼 已关闭代理环境
```
- 在启停代理内核的同时,同步设置系统代理。
- 亦可通过 `clashproxy` 单独控制系统代理。
### Web 控制台
```bash
$ clashui
╔═══════════════════════════════════════════════╗
║ 😼 Web 控制台 ║
║═══════════════════════════════════════════════║
║ ║
║ 🔓 注意放行端口:9090 ║
║ 🏠 内网:http://192.168.0.1:9090/ui ║
║ 🌏 公网:http://8.8.8.8:9090/ui ║
║ ☁️ 公共:http://board.zash.run.place ║
║ ║
╚═══════════════════════════════════════════════╝
$ clashsecret mysecret
😼 密钥更新成功,已重启生效
$ clashsecret
😼 当前密钥:mysecret
```
- 可通过浏览器打开 `Web` 控制台进行可视化操作,例如切换节点、查看日志等。
- 默认使用 [zashboard](https://github.com/Zephyruso/zashboard) 作为控制台前端,如需更换可自行配置。
- 若需将控制台暴露到公网,建议定期更换访问密钥,或通过 `SSH` 端口转发方式进行安全访问。
### `Mixin` 配置
```bash
$ clashmixin
😼 查看 Mixin 配置
$ clashmixin -e
😼 编辑 Mixin 配置
$ clashmixin -c
😼 查看原始订阅配置
$ clashmixin -r
😼 查看运行时配置
```
- 通过 `Mixin` 自定义的配置内容会与原始订阅进行深度合并,且 `Mixin` 具有最高优先级,最终生成内核启动时加载的运行时配置。
- `Mixin` 支持以前置、后置或覆盖的方式,对原始订阅中的规则、节点及策略组进行新增或修改。
### 升级内核
```bash
$ clashupgrade
😼 请求内核升级...
{"status":"ok"}
😼 内核升级成功
```
- 升级过程由代理内核自动完成;如需查看详细的升级日志,可添加 `-v` 参数。
- 建议通过 `clashmixin` 为 `github` 配置代理规则,以避免因网络问题导致请求失败。
### 管理订阅
```bash
$ clashsub -h
Usage:
clashsub COMMAND [OPTIONS]
Commands:
add <url> 添加订阅
ls 查看订阅
del <id> 删除订阅
use <id> 使用订阅
update [id] 更新订阅
log 订阅日志
Options:
update:
--auto 配置自动更新
--convert 使用订阅转换
```
- 支持添加本地订阅,例如:`file:///root/clashctl/resources/config.yaml`
- 当订阅链接解析失败或包含特殊字符时,请使用引号包裹以避免被错误解析。
- 自动更新任务可通过 `crontab -e` 进行修改和管理。
### `Tun` 模式
```bash
$ clashtun
😾 Tun 状态:关闭
$ clashtun on
😼 Tun 模式已开启
```
- 作用:实现本机及 `Docker` 等容器的所有流量路由到 `clash` 代理、DNS 劫持等。
- 原理:[clash-verge-rev](https://www.clashverge.dev/guide/term.html#tun)、 [clash.wiki](https://clash.wiki/premium/tun-device.html)。
- 注意事项:[#100](https://github.com/nelvko/clash-for-linux-install/issues/100#issuecomment-2782680205)
## 🗑️ 卸载
```bash
bash uninstall.sh
```
## 📖 常见问题
👉 [Wiki · FAQ](https://github.com/nelvko/clash-for-linux-install/wiki/FAQ)
## 🔗 引用
- [clash](https://clash.wiki/)
- [mihomo](https://github.com/MetaCubeX/mihomo)
- [subconverter](https://github.com/tindy2013/subconverter)
- [yq](https://github.com/mikefarah/yq)
- [zashboard](https://github.com/Zephyruso/zashboard)
## ⭐ Star History
<a href="https://www.star-history.com/#nelvko/clash-for-linux-install&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=nelvko/clash-for-linux-install&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=nelvko/clash-for-linux-install&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=nelvko/clash-for-linux-install&type=Date" />
</picture>
</a>
## 🙏 Thanks
[@鑫哥](https://github.com/TrackRay)
## ⚠️ 特别声明
1. 编写本项目主要目的为学习和研究 `Shell` 编程,不得将本项目中任何内容用于违反国家/地区/组织等的法律法规或相关规定的其他用途。
2. 本项目保留随时对免责声明进行补充或更改的权利,直接或间接使用本项目内容的个人或组织,视为接受本项目的特别声明。
================================================
FILE: install.sh
================================================
#!/usr/bin/env bash
. scripts/cmd/clashctl.sh
. scripts/preflight.sh
_valid
_parse_args "$@"
_prepare_zip
_detect_init
_okcat "安装内核:$KERNEL_NAME by ${INIT_TYPE}"
_okcat '📦' "安装路径:$CLASH_BASE_DIR"
/bin/cp -rf . "$CLASH_BASE_DIR"
touch "$CLASH_CONFIG_BASE"
_set_envs
_is_regular_sudo && chown -R "$SUDO_USER" "$CLASH_BASE_DIR"
_install_service
_apply_rc
_merge_config
_detect_proxy_port
clashui
clashsecret "$(_get_random_val)" >/dev/null
clashsecret
_okcat '🎉' 'enjoy 🎉'
clashctl
_valid_config "$CLASH_CONFIG_BASE" && CLASH_CONFIG_URL="file://$CLASH_CONFIG_BASE"
_quit "clashsub add $CLASH_CONFIG_URL && clashsub use 1"
================================================
FILE: resources/mixin.yaml
================================================
_custom:
system-proxy:
enable: true
mixed-port: 7890
external-controller: "0.0.0.0:9090"
external-ui: dist
external-ui-url: https://github.com/Zephyruso/zashboard/releases/latest/download/dist.zip
secret:
allow-lan: false # 若开启务必设置用户验证以防暴露公网后被滥用
authentication:
# - "username:password" # 用户验证(clashon 会自动填充)
# prefix:前置插入
# suffix:后置插入
# override:根据 name 匹配并替换原项
rules:
prefix:
- DOMAIN,api64.ipify.org,DIRECT # 用于 clashui 获取真实公网 IP
# - DOMAIN-KEYWORD,github,proxy # 用于 clashupgrade 升级内核
suffix:
proxies:
prefix:
suffix:
override:
proxy-groups:
prefix:
suffix:
override:
# tun 配置
tun:
enable: false
stack: system
auto-route: true
auto-redir: true # clash
auto-redirect: true # mihomo
auto-detect-interface: true
dns-hijack:
- any:53
- tcp://any:53
strict-route: true
route-exclude-address:
- 1.1.1.1/32 # 用于 clashui 获取真实内网 IP
- 127.0.0.1/32
exclude-interface:
- docker0 # 避免无法访问容器内服务
- podman0 # 详见:https://github.com/nelvko/clash-for-linux-install/issues/100#issuecomment-2995667368
# DNS 配置
dns:
enable: true
listen: 0.0.0.0:1053
enhanced-mode: fake-ip
nameserver:
- 114.114.114.114
- 8.8.8.8
================================================
FILE: resources/profiles/.gitkeep
================================================
================================================
FILE: resources/profiles.yaml
================================================
# 当前使用的订阅
use:
# 订阅列表
profiles:
================================================
FILE: scripts/cmd/clashctl.fish
================================================
set fn_arr \
clashui \
clashstatus \
clashsecret \
clashtun \
clashmixin \
clashsub \
clashlog \
clashupgrade \
clashhelp
set -gx fish_version $FISH_VERSION
for fn in $fn_arr
eval "
function $fn
bash -i -c '$fn \"\$@\"' -- \$argv
end
"
end
function clashctl
if test -z "$argv"
clashhelp
return
end
set suffix $argv[1]
set argv $argv[2..-1]
switch $suffix
case on
clashon $argv
case off
clashoff $argv
case proxy
clashproxy $argv
case '*'
clash"$suffix" $argv
end
end
function clashon
bash -i -c 'clashon; sudo tee /var/proxy >/dev/null <<EOF
export http_proxy=$http_proxy
export https_proxy=$http_proxy
export HTTP_PROXY=$http_proxy
export HTTPS_PROXY=$http_proxy
export all_proxy=$all_proxy
export ALL_PROXY=$all_proxy
export no_proxy=$no_proxy
export NO_PROXY=$no_proxy
EOF'
clashproxy on
end
function clashoff
bash -i -c 'clashoff'
clashproxy off
end
function clashproxy
if test (count $argv) -eq 0
echo "Usage: clashproxy [on|off]"
return 1
end
switch $argv[1]
case on
source /var/proxy
echo '已开启系统代理'
case off
set -e \
http_proxy \
https_proxy \
HTTP_PROXY \
HTTPS_PROXY \
all_proxy \
ALL_PROXY \
no_proxy \
NO_PROXY
echo '已关闭系统代理'
end
end
================================================
FILE: scripts/cmd/clashctl.sh
================================================
#!/usr/bin/env bash
THIS_SCRIPT_DIR=$(dirname "$(readlink -f "${BASH_SOURCE:-${(%):-%N}}")")
. "$THIS_SCRIPT_DIR/common.sh"
_set_system_proxy() {
local mixed_port=$("$BIN_YQ" '.mixed-port // ""' "$CLASH_CONFIG_RUNTIME")
local http_port=$("$BIN_YQ" '.port // ""' "$CLASH_CONFIG_RUNTIME")
local socks_port=$("$BIN_YQ" '.socks-port // ""' "$CLASH_CONFIG_RUNTIME")
local auth=$("$BIN_YQ" '.authentication[0] // ""' "$CLASH_CONFIG_RUNTIME")
[ -n "$auth" ] && auth=$auth@
local bind_addr=$(_get_bind_addr)
local http_proxy_addr="http://${auth}${bind_addr}:${http_port:-${mixed_port}}"
local socks_proxy_addr="socks5h://${auth}${bind_addr}:${socks_port:-${mixed_port}}"
local no_proxy_addr="localhost,127.0.0.1,::1"
export http_proxy=$http_proxy_addr
export HTTP_PROXY=$http_proxy
export https_proxy=$http_proxy
export HTTPS_PROXY=$https_proxy
export all_proxy=$socks_proxy_addr
export ALL_PROXY=$all_proxy
export no_proxy=$no_proxy_addr
export NO_PROXY=$no_proxy
}
_unset_system_proxy() {
unset http_proxy
unset https_proxy
unset HTTP_PROXY
unset HTTPS_PROXY
unset all_proxy
unset ALL_PROXY
unset no_proxy
unset NO_PROXY
}
_detect_proxy_port() {
local mixed_port=$("$BIN_YQ" '.mixed-port // ""' "$CLASH_CONFIG_RUNTIME")
local http_port=$("$BIN_YQ" '.port // ""' "$CLASH_CONFIG_RUNTIME")
local socks_port=$("$BIN_YQ" '.socks-port // ""' "$CLASH_CONFIG_RUNTIME")
[ -z "$mixed_port" ] && [ -z "$http_port" ] && [ -z "$socks_port" ] && mixed_port=7890
local newPort count=0
local port_list=(
"mixed_port|mixed-port"
"http_port|port"
"socks_port|socks-port"
)
clashstatus >&/dev/null && local isActive='true'
for entry in "${port_list[@]}"; do
local var_name="${entry%|*}"
local yaml_key="${entry#*|}"
eval "local var_val=\${$var_name}"
[ -n "$var_val" ] && _is_port_used "$var_val" && [ "$isActive" != "true" ] && {
newPort=$(_get_random_port)
((count++))
_failcat '🎯' "端口冲突:[$yaml_key] $var_val 🎲 随机分配 $newPort"
"$BIN_YQ" -i ".${yaml_key} = $newPort" "$CLASH_CONFIG_MIXIN"
}
done
((count)) && _merge_config
}
function clashon() {
_detect_proxy_port
clashstatus >&/dev/null || placeholder_start
clashstatus >&/dev/null || {
_failcat '启动失败: 执行 clashlog 查看日志'
return 1
}
clashproxy >/dev/null && _set_system_proxy
_okcat '已开启代理环境'
}
watch_proxy() {
[ -z "$http_proxy" ] && {
# [[ "$0" == -* ]] && { # 登录式shell
[[ $- == *i* ]] && { # 交互式shell
placeholder_watch_proxy
}
}
}
function clashoff() {
clashstatus >&/dev/null && {
placeholder_stop >/dev/null
clashstatus >&/dev/null && _tunstatus >&/dev/null && {
_tunoff || _error_quit "请先关闭 Tun 模式"
}
placeholder_stop >/dev/null
clashstatus >&/dev/null && {
_failcat '代理环境关闭失败'
return 1
}
}
_unset_system_proxy
_okcat '已关闭代理环境'
}
clashrestart() {
clashoff >/dev/null
clashon
}
function clashproxy() {
case "$1" in
-h | --help)
cat <<EOF
- 查看系统代理状态
clashproxy
- 开启系统代理
clashproxy on
- 关闭系统代理
clashproxy off
EOF
return 0
;;
on)
clashstatus >&/dev/null || {
_failcat "$KERNEL_NAME 未运行,请先执行 clashon"
return 1
}
"$BIN_YQ" -i '._custom.system-proxy.enable = true' "$CLASH_CONFIG_MIXIN"
_set_system_proxy
_okcat '已开启系统代理'
;;
off)
"$BIN_YQ" -i '._custom.system-proxy.enable = false' "$CLASH_CONFIG_MIXIN"
_unset_system_proxy
_okcat '已关闭系统代理'
;;
*)
local system_proxy_enable=$("$BIN_YQ" '._custom.system-proxy.enable' "$CLASH_CONFIG_MIXIN" 2>/dev/null)
case $system_proxy_enable in
true)
_okcat "系统代理:开启
$(env | grep -i 'proxy=')"
;;
*)
_failcat "系统代理:关闭"
;;
esac
;;
esac
}
function clashstatus() {
placeholder_status "$@"
placeholder_is_active >&/dev/null
}
function clashlog() {
placeholder_log "$@"
}
function clashui() {
_detect_ext_addr
clashstatus >&/dev/null || clashon >/dev/null
local query_url='api64.ipify.org' # ifconfig.me
local public_ip=$(curl -s --noproxy "*" --location --max-time 2 $query_url)
local public_address="http://${public_ip:-公网}:${EXT_PORT}/ui"
local local_ip=$EXT_IP
local local_address="http://${local_ip}:${EXT_PORT}/ui"
printf "\n"
printf "╔═══════════════════════════════════════════════╗\n"
printf "║ %s ║\n" "$(_okcat 'Web 控制台')"
printf "║═══════════════════════════════════════════════║\n"
printf "║ ║\n"
printf "║ 🔓 注意放行端口:%-5s ║\n" "$EXT_PORT"
printf "║ 🏠 内网:%-31s ║\n" "$local_address"
printf "║ 🌏 公网:%-31s ║\n" "$public_address"
printf "║ ☁️ 公共:%-31s ║\n" "$URL_CLASH_UI"
printf "║ ║\n"
printf "╚═══════════════════════════════════════════════╝\n"
printf "\n"
}
_merge_config() {
cat "$CLASH_CONFIG_RUNTIME" >"$CLASH_CONFIG_TEMP" 2>/dev/null
# shellcheck disable=SC2016
"$BIN_YQ" eval-all '
########################################
# Load Files #
########################################
select(fileIndex==0) as $config |
select(fileIndex==1) as $mixin |
########################################
# Deep Merge #
########################################
$mixin |= del(._custom) |
(($config // {}) * $mixin) as $runtime |
$runtime |
########################################
# Rules #
########################################
.rules = (
($mixin.rules.prefix // []) +
($config.rules // []) +
($mixin.rules.suffix // [])
) |
########################################
# Proxies #
########################################
.proxies = (
($mixin.proxies.prefix // []) +
(
($config.proxies // []) as $configList |
($mixin.proxies.override // []) as $overrideList |
$configList | map(
. as $configItem |
(
$overrideList[] | select(.name == $configItem.name)
) // $configItem
)
) +
($mixin.proxies.suffix // [])
) |
########################################
# ProxyGroups #
########################################
.proxy-groups = (
($mixin.proxy-groups.prefix // []) +
(
($config.proxy-groups // []) as $configList |
($mixin.proxy-groups.override // []) as $overrideList |
$configList | map(
. as $configItem |
(
$overrideList[] | select(.name == $configItem.name)
) // $configItem
)
) +
($mixin.proxy-groups.suffix // [])
)
' "$CLASH_CONFIG_BASE" "$CLASH_CONFIG_MIXIN" >"$CLASH_CONFIG_RUNTIME"
_valid_config "$CLASH_CONFIG_RUNTIME" || {
cat "$CLASH_CONFIG_TEMP" >"$CLASH_CONFIG_RUNTIME"
_error_quit "验证失败:请检查 Mixin 配置"
}
}
_merge_config_restart() {
_merge_config
placeholder_stop >/dev/null
clashstatus >&/dev/null && _tunstatus >&/dev/null && {
_tunoff || _error_quit "请先关闭 Tun 模式"
}
placeholder_stop >/dev/null
sleep 0.1
placeholder_start >/dev/null
sleep 0.1
}
_get_secret() {
"$BIN_YQ" '.secret // ""' "$CLASH_CONFIG_RUNTIME"
}
function clashsecret() {
case "$1" in
-h | --help)
cat <<EOF
- 查看 Web 密钥
clashsecret
- 修改 Web 密钥
clashsecret <new_secret>
EOF
return 0
;;
esac
case $# in
0)
_okcat "当前密钥:$(_get_secret)"
;;
1)
"$BIN_YQ" -i ".secret = \"$1\"" "$CLASH_CONFIG_MIXIN" || {
_failcat "密钥更新失败,请重新输入"
return 1
}
_merge_config_restart
_okcat "密钥更新成功,已重启生效"
;;
*)
_failcat "密钥不要包含空格或使用引号包围"
;;
esac
}
_tunstatus() {
local tun_status=$("$BIN_YQ" '.tun.enable' "${CLASH_CONFIG_RUNTIME}")
case $tun_status in
true)
_okcat 'Tun 状态:启用'
;;
*)
_failcat 'Tun 状态:关闭'
;;
esac
}
_tunoff() {
_tunstatus >/dev/null || return 0
sudo placeholder_stop
clashstatus >&/dev/null || {
"$BIN_YQ" -i '.tun.enable = false' "$CLASH_CONFIG_MIXIN"
_merge_config
clashon >/dev/null
_okcat "Tun 模式已关闭"
return 0
}
_tunstatus >&/dev/null && _failcat "Tun 模式关闭失败"
}
_sudo_restart() {
sudo placeholder_stop
placeholder_sudo_start
sleep 0.5
}
_tunon() {
_tunstatus 2>/dev/null && return 0
sudo placeholder_stop
"$BIN_YQ" -i '.tun.enable = true' "$CLASH_CONFIG_MIXIN"
_merge_config
placeholder_sudo_start
sleep 0.5
clashstatus >&/dev/null || _error_quit "Tun 模式开启失败"
local fail_msg="Start TUN listening error|unsupported kernel version"
local ok_msg="Tun adapter listening at|TUN listening iface"
clashlog | grep -E -m1 -qs "$fail_msg" && {
[ "$KERNEL_NAME" = 'mihomo' ] && {
"$BIN_YQ" -i '.tun.auto-redirect = false' "$CLASH_CONFIG_MIXIN"
_merge_config
_sudo_restart
}
clashlog | grep -E -m1 -qs "$ok_msg" || {
clashlog | grep -E -m1 "$fail_msg"
_tunoff >&/dev/null
_error_quit '系统内核版本不支持 Tun 模式'
}
}
_okcat "Tun 模式已开启"
}
function clashtun() {
case "$1" in
-h | --help)
cat <<EOF
- 查看 Tun 状态
clashtun
- 开启 Tun 模式
clashtun on
- 关闭 Tun 模式
clashtun off
EOF
return 0
;;
on)
_tunon
;;
off)
_tunoff
;;
*)
_tunstatus
;;
esac
}
function clashmixin() {
case "$1" in
-h | --help)
cat <<EOF
- 查看 Mixin 配置:$CLASH_CONFIG_MIXIN
clashmixin
- 编辑 Mixin 配置
clashmixin -e
- 查看原始订阅配置:$CLASH_CONFIG_BASE
clashmixin -c
- 查看运行时配置:$CLASH_CONFIG_RUNTIME
clashmixin -r
EOF
return 0
;;
-e)
vim "$CLASH_CONFIG_MIXIN" && {
_merge_config_restart && _okcat "配置更新成功,已重启生效"
}
;;
-r)
less "$CLASH_CONFIG_RUNTIME"
;;
-c)
less "$CLASH_CONFIG_BASE"
;;
*)
less "$CLASH_CONFIG_MIXIN"
;;
esac
}
function clashupgrade() {
for arg in "$@"; do
case $arg in
-h | --help)
cat <<EOF
Usage:
clashupgrade [OPTIONS]
Options:
-v, --verbose 输出内核升级日志
-r, --release 升级至稳定版
-a, --alpha 升级至测试版
-h, --help 显示帮助信息
EOF
return 0
;;
-v | --verbose)
local log_flag=true
;;
-r | --release)
channel="release"
;;
-a | --alpha)
channel="alpha"
;;
*)
channel=""
;;
esac
done
_detect_ext_addr
clashstatus >&/dev/null || clashon >/dev/null
_okcat '⏳' "请求内核升级..."
[ "$log_flag" = true ] && {
log_cmd=(placeholder_follow_log)
("${log_cmd[@]}" &)
}
local res=$(
curl -X POST \
--silent \
--noproxy "*" \
--location \
-H "Authorization: Bearer $(_get_secret)" \
"http://${EXT_IP}:${EXT_PORT}/upgrade?channel=$channel"
)
[ "$log_flag" = true ] && pkill -9 -f "${log_cmd[*]}"
grep '"status":"ok"' <<<"$res" && {
_okcat "内核升级成功"
return 0
}
grep 'already using latest version' <<<"$res" && {
_okcat "已是最新版本"
return 0
}
_failcat "内核升级失败,请检查网络或稍后重试"
}
function clashsub() {
case "$1" in
add)
shift
_sub_add "$@"
;;
del)
shift
_sub_del "$@"
;;
list | ls | '')
shift
_sub_list "$@"
;;
use)
shift
_sub_use "$@"
;;
update)
shift
_sub_update "$@"
;;
log)
shift
_sub_log "$@"
;;
-h | --help | *)
cat <<EOF
clashsub - Clash 订阅管理工具
Usage:
clashsub COMMAND [OPTIONS]
Commands:
add <url> 添加订阅
ls 查看订阅
del <id> 删除订阅
use <id> 使用订阅
update [id] 更新订阅
log 订阅日志
Options:
update:
--auto 配置自动更新
--convert 使用订阅转换
EOF
;;
esac
}
_sub_add() {
local url=$1
[ -z "$url" ] && {
echo -n "$(_okcat '✈️ ' '请输入要添加的订阅链接:')"
read -r url
[ -z "$url" ] && _error_quit "订阅链接不能为空"
}
_get_url_by_id "$id" >/dev/null && _error_quit "该订阅链接已存在"
_download_config "$CLASH_CONFIG_TEMP" "$url"
_valid_config "$CLASH_CONFIG_TEMP" || _error_quit "订阅无效,请检查:
原始订阅:${CLASH_CONFIG_TEMP}.raw
转换订阅:$CLASH_CONFIG_TEMP
转换日志:$BIN_SUBCONVERTER_LOG"
local id=$("$BIN_YQ" '.profiles // [] | (map(.id) | max) // 0 | . + 1' "$CLASH_PROFILES_META")
local profile_path="${CLASH_PROFILES_DIR}/${id}.yaml"
mv "$CLASH_CONFIG_TEMP" "$profile_path"
"$BIN_YQ" -i "
.profiles = (.profiles // []) +
[{
\"id\": $id,
\"path\": \"$profile_path\",
\"url\": \"$url\"
}]
" "$CLASH_PROFILES_META"
_logging_sub "➕ 已添加订阅:[$id] $url"
_okcat '🎉' "订阅已添加:[$id] $url"
}
_sub_del() {
local id=$1
[ -z "$id" ] && {
echo -n "$(_okcat '✈️ ' '请输入要删除的订阅 id:')"
read -r id
[ -z "$id" ] && _error_quit "订阅 id 不能为空"
}
local profile_path url
profile_path=$(_get_path_by_id "$id") || _error_quit "订阅 id 不存在,请检查"
url=$(_get_url_by_id "$id")
use=$("$BIN_YQ" '.use // ""' "$CLASH_PROFILES_META")
[ "$use" = "$id" ] && _error_quit "删除失败:订阅 $id 正在使用中,请先切换订阅"
/usr/bin/rm -f "$profile_path"
"$BIN_YQ" -i "del(.profiles[] | select(.id == \"$id\"))" "$CLASH_PROFILES_META"
_logging_sub "➖ 已删除订阅:[$id] $url"
_okcat '🎉' "订阅已删除:[$id] $url"
}
_sub_list() {
"$BIN_YQ" "$CLASH_PROFILES_META"
}
_sub_use() {
"$BIN_YQ" -e '.profiles // [] | length == 0' "$CLASH_PROFILES_META" >&/dev/null &&
_error_quit "当前无可用订阅,请先添加订阅"
local id=$1
[ -z "$id" ] && {
clashsub ls
echo -n "$(_okcat '✈️ ' '请输入要使用的订阅 id:')"
read -r id
[ -z "$id" ] && _error_quit "订阅 id 不能为空"
}
local profile_path url
profile_path=$(_get_path_by_id "$id") || _error_quit "订阅 id 不存在,请检查"
url=$(_get_url_by_id "$id")
cat "$profile_path" >"$CLASH_CONFIG_BASE"
_merge_config_restart
"$BIN_YQ" -i ".use = $id" "$CLASH_PROFILES_META"
_logging_sub "🔥 订阅已切换为:[$id] $url"
_okcat '🔥' '订阅已生效'
}
_get_path_by_id() {
"$BIN_YQ" -e ".profiles[] | select(.id == \"$1\") | .path" "$CLASH_PROFILES_META" 2>/dev/null
}
_get_url_by_id() {
"$BIN_YQ" -e ".profiles[] | select(.id == \"$1\") | .url" "$CLASH_PROFILES_META" 2>/dev/null
}
_sub_update() {
local arg is_convert
for arg in "$@"; do
case $arg in
--auto)
command -v crontab >/dev/null || _error_quit "未检测到 crontab 命令,请先安装 cron 服务"
crontab -l | grep -qs 'clashsub update' || {
(
crontab -l 2>/dev/null
echo "0 0 */2 * * $SHELL -i -c 'clashsub update'"
) | crontab -
}
_okcat "已设置定时更新订阅"
return 0
;;
--convert)
is_convert=true
shift
;;
esac
done
local id=$1
[ -z "$id" ] && id=$("$BIN_YQ" '.use // 1' "$CLASH_PROFILES_META")
local url profile_path
url=$(_get_url_by_id "$id") || _error_quit "订阅 id 不存在,请检查"
profile_path=$(_get_path_by_id "$id")
_okcat "✈️ " "更新订阅:[$id] $url"
[ "$is_convert" = true ] && {
_download_convert_config "$CLASH_CONFIG_TEMP" "$url"
}
[ "$is_convert" != true ] && {
_download_config "$CLASH_CONFIG_TEMP" "$url"
}
_valid_config "$CLASH_CONFIG_TEMP" || {
_logging_sub "❌ 订阅更新失败:[$id] $url"
_error_quit "订阅无效:请检查:
原始订阅:${CLASH_CONFIG_TEMP}.raw
转换订阅:$CLASH_CONFIG_TEMP
转换日志:$BIN_SUBCONVERTER_LOG"
}
_logging_sub "✅ 订阅更新成功:[$id] $url"
cat "$CLASH_CONFIG_TEMP" >"$profile_path"
use=$("$BIN_YQ" '.use // ""' "$CLASH_PROFILES_META")
[ "$use" = "$id" ] && clashsub use "$use" && return
_okcat '订阅已更新'
}
_logging_sub() {
echo "$(date +"%Y-%m-%d %H:%M:%S") $1" >>"${CLASH_PROFILES_LOG}"
}
_sub_log() {
tail <"${CLASH_PROFILES_LOG}" "$@"
}
function clashctl() {
case "$1" in
on)
shift
clashon
;;
off)
shift
clashoff
;;
ui)
shift
clashui
;;
status)
shift
clashstatus "$@"
;;
log)
shift
clashlog "$@"
;;
proxy)
shift
clashproxy "$@"
;;
tun)
shift
clashtun "$@"
;;
mixin)
shift
clashmixin "$@"
;;
secret)
shift
clashsecret "$@"
;;
sub)
shift
clashsub "$@"
;;
upgrade)
shift
clashupgrade "$@"
;;
*)
(($#)) && shift
clashhelp "$@"
;;
esac
}
clashhelp() {
cat <<EOF
Usage:
clashctl COMMAND [OPTIONS]
Commands:
on 开启代理
off 关闭代理
proxy 系统代理
status 内核状态
ui 面板地址
sub 订阅管理
log 内核日志
tun Tun 模式
mixin Mixin 配置
secret Web 密钥
upgrade 升级内核
Global Options:
-h, --help 显示帮助信息
For more help on how to use clashctl, head to https://github.com/nelvko/clash-for-linux-install
EOF
}
================================================
FILE: scripts/cmd/common.sh
================================================
#!/usr/bin/env bash
# shellcheck disable=SC2034
. "$(dirname "$(dirname "$THIS_SCRIPT_DIR")")/.env"
CLASH_RESOURCES_DIR="${CLASH_BASE_DIR}/resources"
CLASH_CONFIG_BASE="${CLASH_RESOURCES_DIR}/config.yaml"
CLASH_CONFIG_MIXIN="${CLASH_RESOURCES_DIR}/mixin.yaml"
CLASH_CONFIG_RUNTIME="${CLASH_RESOURCES_DIR}/runtime.yaml"
CLASH_CONFIG_TEMP="${CLASH_RESOURCES_DIR}/temp.yaml"
BIN_BASE_DIR="${CLASH_BASE_DIR}/bin"
BIN_KERNEL="${BIN_BASE_DIR}/$KERNEL_NAME"
BIN_YQ="${BIN_BASE_DIR}/yq"
BIN_SUBCONVERTER_DIR="${BIN_BASE_DIR}/subconverter"
BIN_SUBCONVERTER="${BIN_SUBCONVERTER_DIR}/subconverter"
BIN_SUBCONVERTER_START="$BIN_SUBCONVERTER"
BIN_SUBCONVERTER_STOP="pkill -9 -f $BIN_SUBCONVERTER"
BIN_SUBCONVERTER_CONFIG="$BIN_SUBCONVERTER_DIR/pref.yml"
BIN_SUBCONVERTER_LOG="${BIN_SUBCONVERTER_DIR}/latest.log"
CLASH_PROFILES_DIR="${CLASH_RESOURCES_DIR}/profiles"
CLASH_PROFILES_META="${CLASH_RESOURCES_DIR}/profiles.yaml"
CLASH_PROFILES_LOG="${CLASH_RESOURCES_DIR}/profiles.log"
_is_port_used() {
local port=$1
{ ss -tunl 2>/dev/null || netstat -tunl; } | grep -qs ":${port}\b"
}
_get_random_port() {
local randomPort=$(shuf -i 1024-65535 -n 1)
! _is_port_used "$randomPort" && { echo "$randomPort" && return; }
_get_random_port
}
_get_bind_addr() {
local allowLan bindAddr
bindAddr=$("$BIN_YQ" '.bind-address // "*"' "$CLASH_CONFIG_RUNTIME")
allowLan=$("$BIN_YQ" '.allow-lan // false' "$CLASH_CONFIG_RUNTIME")
case $allowLan in
true)
[ "$bindAddr" = "*" ] && bindAddr=$(_get_local_ip)
;;
false)
bindAddr=127.0.0.1
;;
esac
echo "$bindAddr"
}
_get_local_ip() {
local local_ip=$(ip route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}')
[ -z "$local_ip" ] && local_ip=$(hostname -I | awk '{print $1}')
echo "$local_ip"
}
function _detect_ext_addr() {
local ext_addr=$("$BIN_YQ" '.external-controller // ""' "$CLASH_CONFIG_RUNTIME")
local ext_ip=${ext_addr%%:*}
EXT_IP=$ext_ip
EXT_PORT=${ext_addr##*:}
[ "$ext_ip" = '0.0.0.0' ] && EXT_IP=$(_get_local_ip)
_is_port_used "$EXT_PORT" && {
curl -s --noproxy "*" -H "Authorization: Bearer $(_get_secret)" "127.0.0.1:${EXT_PORT}" | grep -qs "${KERNEL_NAME}" && return 0
local newPort=$(_get_random_port)
_failcat '🎯' "端口冲突:[external-controller] ${EXT_PORT} 🎲 随机分配 $newPort"
EXT_PORT=$newPort
"$BIN_YQ" -i ".external-controller = \"$ext_ip:$newPort\"" "$CLASH_CONFIG_MIXIN"
_merge_config
}
}
_color_log() {
local color="$1"
local msg="$2"
local hex="${color#\#}"
local r=$((16#${hex:0:2}))
local g=$((16#${hex:2:2}))
local b=$((16#${hex:4:2}))
local color_code="\033[38;2;${r};${g};${b}m"
local reset_code="\033[0m"
printf "%b%s%b\n" "$color_code" "$msg" "$reset_code"
}
function _okcat() {
local color=#c8d6e5
local emoji=😼
[ $# -gt 1 ] && emoji=$1 && shift
local msg="${emoji} $1"
_color_log "$color" "$msg"
return 0
}
function _failcat() {
local color=#fd79a8
local emoji=😾
[ $# -gt 1 ] && emoji=$1 && shift
local msg="${emoji} $1"
_color_log "$color" "$msg" >&2
return 1
}
function _error_quit() {
[ $# -gt 0 ] && {
local color=#f92f60
local emoji=📢
[ $# -gt 1 ] && emoji=$1 && shift
local msg="${emoji} $1"
_color_log "$color" "$msg"
}
exec $SHELL -i
}
function _valid_config() {
local config="$1"
[[ ! -e "$config" || "$(wc -l <"$config")" -lt 1 ]] && return 1
local test_cmd test_log
test_cmd=("$BIN_KERNEL" -d "$(dirname "$config")" -f "$config" -t)
test_log=$("${test_cmd[@]}") || {
"${test_cmd[@]}"
grep -qs "unsupport proxy type" <<<"$test_log" && {
local prefix="检测到订阅中包含不受支持的代理协议"
[ "$KERNEL_NAME" = "clash" ] && _error_quit "${prefix}, 推荐安装使用 mihomo 内核"
_error_quit "${prefix}, 请检查并升级内核版本"
}
}
}
function _download_config() {
local dest=$1
local url=$2
[ "${url:0:4}" = 'file' ] || _okcat '⏳' '正在下载...'
_download_raw_config "$dest" "$url" || return 1
_okcat '🍃' '验证订阅配置...'
_valid_config "$dest" || {
_failcat '🍂' "验证失败:尝试订阅转换..."
cat "$dest" >"${dest}.raw"
_download_convert_config "$dest" "$url"
}
}
_download_raw_config() {
local dest=$1
local url=$2
curl \
--silent \
--show-error \
--fail \
--insecure \
--location \
--max-time 5 \
--retry 1 \
--user-agent "$CLASH_SUB_UA" \
--output "$dest" \
"$url" ||
wget \
--no-verbose \
--no-check-certificate \
--timeout 5 \
--tries 1 \
--user-agent "$CLASH_SUB_UA" \
--output-document "$dest" \
"$url"
}
_download_convert_config() {
local dest=$1
local url=$2
local flag
[ "${url:0:4}" = 'file' ] && return 0
_start_convert
local convert_url=$(
target='clash'
base_url="http://127.0.0.1:${BIN_SUBCONVERTER_PORT}/sub"
curl \
--get \
--silent \
--show-error \
--location \
--output /dev/null \
--data-urlencode "target=$target" \
--data-urlencode "url=$url" \
--write-out '%{url_effective}' \
"$base_url"
)
curl --user-agent "$CLASH_SUB_UA" --silent --output "$dest" "$convert_url"
flag=$?
_stop_convert
return $flag
}
_detect_subconverter_port() {
BIN_SUBCONVERTER_PORT=$("$BIN_YQ" '.server.port' "$BIN_SUBCONVERTER_CONFIG")
_is_port_used "$BIN_SUBCONVERTER_PORT" && {
local newPort=$(_get_random_port)
_failcat '🎯' "端口冲突:[subconverter] ${BIN_SUBCONVERTER_PORT} 🎲 随机分配:$newPort"
BIN_SUBCONVERTER_PORT=$newPort
"$BIN_YQ" -i ".server.port = $newPort" "$BIN_SUBCONVERTER_CONFIG" 2>/dev/null
}
}
_start_convert() {
_detect_subconverter_port
local check_cmd="curl http://localhost:${BIN_SUBCONVERTER_PORT}/version"
$check_cmd >&/dev/null && return 0
("$BIN_SUBCONVERTER_START" >&"$BIN_SUBCONVERTER_LOG" &)
local start=$(date +%s)
while ! $check_cmd >&/dev/null; do
sleep 0.5s
local now=$(date +%s)
[ $((now - start)) -gt 2 ] && _error_quit "订阅转换服务未启动,请检查日志:$BIN_SUBCONVERTER_LOG"
done
}
_stop_convert() {
$BIN_SUBCONVERTER_STOP >/dev/null
}
_set_env() {
local key=$1
local value=$2
local env_path="${CLASH_BASE_DIR}/.env"
grep -qE "^${key}=" "$env_path" && {
value=${value//&/\\&}
sed -i "s|^${key}=.*|${key}=${value}|" "$env_path"
return $?
}
echo "${key}=${value}" >>"$env_path"
}
================================================
FILE: scripts/init/OpenRC.sh
================================================
#!/sbin/openrc-run
description="placeholder_kernel_desc"
command="placeholder_cmd_path"
command_args="placeholder_cmd_args"
pidfile="placeholder_pid_file"
output_log="placeholder_log_file"
error_log="placeholder_log_file"
command_background=true
================================================
FILE: scripts/init/SysVinit.sh
================================================
### BEGIN INIT INFO
# Provides: placeholder_kernel_name
# Required-Start: $network $local_fs $remote_fs
# Required-Stop: $network $local_fs $remote_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: placeholder_kernel_desc
# Description: placeholder_kernel_desc
### END INIT INFO
pidfile="placeholder_pid_file"
logfile="placeholder_log_file"
cmd="placeholder_cmd_full"
case "$1" in
start)
$0 status >&/dev/null && exit 0
$cmd >&$logfile &
echo $! >$pidfile
;;
stop)
pid=$(cat $pidfile 2>/dev/null)
[ -n "$pid" ] && kill -9 "$pid"
rm -f $pidfile
;;
restart | reload)
$0 stop
sleep 0.5
$0 start
;;
status)
pid=$(cat $pidfile 2>/dev/null)
isStart=$(ps ax | awk '{ print $1 }' | grep -e "^${pid}$")
[ -n "$isStart" ] && {
echo "placeholder_kernel_name is running with PID: $pid"
exit 0
}
echo "placeholder_kernel_name is not running."
exit 1
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
;;
esac
================================================
FILE: scripts/init/runit.sh
================================================
#!/bin/sh
exec placeholder_cmd_full >placeholder_log_file 2>&1
================================================
FILE: scripts/init/systemd.sh
================================================
[Unit]
Description=placeholder_kernel_desc
After=network.target NetworkManager.service systemd-networkd.service iwd.service
[Service]
Type=simple
LimitNPROC=500
LimitNOFILE=1000000
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
Restart=always
ExecStartPre=/usr/bin/sleep 1s
ExecStart=placeholder_cmd_full
ExecReload=/bin/kill -HUP $MAINPID
[Install]
WantedBy=multi-user.target
================================================
FILE: scripts/preflight.sh
================================================
#!/usr/bin/env bash
RESOURCES_BASE_DIR=".${CLASH_RESOURCES_DIR#"$CLASH_BASE_DIR"}"
ZIP_BASE_DIR=".${CLASH_RESOURCES_DIR#"$CLASH_BASE_DIR"}/zip"
SCRIPT_BASE_DIR='scripts'
SCRIPT_INIT_DIR="${SCRIPT_BASE_DIR}/init"
SCRIPT_CMD_DIR="${SCRIPT_BASE_DIR}/cmd"
SCRIPT_CMD_FISH="${SCRIPT_CMD_DIR}/clashctl.fish"
CLASH_CMD_DIR="${CLASH_BASE_DIR}/$SCRIPT_CMD_DIR"
FILE_LOG="/var/log/${KERNEL_NAME}.log"
FILE_PID="/run/${KERNEL_NAME}.pid"
_valid_required() {
local required_cmds=("xz" "pgrep" "curl" "tar" 'unzip')
local missing=()
for cmd in "${required_cmds[@]}"; do
command -v "$cmd" >&/dev/null || missing+=("$cmd")
done
[ "${#missing[@]}" -gt 0 ] && _error_quit "请先安装以下命令:${missing[*]}"
}
_valid() {
_valid_required
[ -d "$CLASH_BASE_DIR" ] && _error_quit "请先执行卸载脚本,以清除安装路径:$CLASH_BASE_DIR"
local msg="${CLASH_BASE_DIR}:当前路径不可用,请在 .env 中更换安装路径。"
mkdir -p "$CLASH_BASE_DIR" || _error_quit "$msg"
_is_regular_sudo && [[ $CLASH_BASE_DIR == /root* ]] && _error_quit "$msg"
[ -z "$ZSH_VERSION" ] && [ -z "$BASH_VERSION" ] && _error_quit "仅支持:bash、zsh 执行"
}
_parse_args() {
for arg in "$@"; do
case $arg in
mihomo)
KERNEL_NAME=mihomo
;;
clash)
KERNEL_NAME=clash
;;
http*)
CLASH_CONFIG_URL=$arg
;;
esac
done
}
_prepare_zip() {
_load_zip >&/dev/null
local required_zips=()
case "${KERNEL_NAME}" in
clash)
[ ! -f "$ZIP_CLASH" ] && required_zips+=("clash")
;;
mihomo | *)
[ ! -f "$ZIP_MIHOMO" ] && required_zips+=("mihomo")
;;
esac
[ ! -f "$ZIP_YQ" ] && required_zips+=("yq")
[ ! -f "$ZIP_SUBCONVERTER" ] && required_zips+=("subconverter")
_download_zip "${required_zips[@]}"
case "${KERNEL_NAME}" in
clash)
ZIP_KERNEL="$ZIP_CLASH"
;;
mihomo | *)
ZIP_KERNEL="$ZIP_MIHOMO"
;;
esac
BIN_KERNEL="${BIN_BASE_DIR}/$KERNEL_NAME"
_unzip_zip
}
_load_zip() {
ZIP_CLASH=$(echo "${ZIP_BASE_DIR}"/clash*)
ZIP_MIHOMO=$(echo "${ZIP_BASE_DIR}"/mihomo*)
ZIP_YQ=$(echo "${ZIP_BASE_DIR}"/yq*)
ZIP_SUBCONVERTER=$(echo "${ZIP_BASE_DIR}"/subconverter*)
}
_download_zip() {
(($#)) || return 0
local url_clash url_mihomo url_yq url_subconverter
local arch=$(uname -m)
case "$arch" in
x86_64)
local flags=$(grep -m1 '^flags' /proc/cpuinfo)
local level=v1
grep -qw sse4_2 <<<"$flags" && grep -qw popcnt <<<"$flags" && level=v2
grep -qw avx2 <<<"$flags" && grep -qw fma <<<"$flags" && level=v3
VERSION_MIHOMO=${level}-$VERSION_MIHOMO
url_clash=https://downloads.clash.wiki/ClashPremium/clash-linux-amd64-2023.08.17.gz
url_mihomo=https://github.com/MetaCubeX/mihomo/releases/download/${VERSION_MIHOMO##*-}/mihomo-linux-amd64-${VERSION_MIHOMO}.gz
url_yq=https://github.com/mikefarah/yq/releases/download/${VERSION_YQ}/yq_linux_amd64.tar.gz
url_subconverter=https://github.com/tindy2013/subconverter/releases/download/${VERSION_SUBCONVERTER}/subconverter_linux64.tar.gz
;;
*86*)
url_clash=https://downloads.clash.wiki/ClashPremium/clash-linux-386-2023.08.17.gz
url_mihomo=https://github.com/MetaCubeX/mihomo/releases/download/${VERSION_MIHOMO##*-}/mihomo-linux-386-${VERSION_MIHOMO}.gz
url_yq=https://github.com/mikefarah/yq/releases/download/${VERSION_YQ}/yq_linux_386.tar.gz
url_subconverter=https://github.com/tindy2013/subconverter/releases/download/${VERSION_SUBCONVERTER}/subconverter_linux32.tar.gz
;;
armv*)
url_clash=https://downloads.clash.wiki/ClashPremium/clash-linux-armv5-2023.08.17.gz
url_mihomo=https://github.com/MetaCubeX/mihomo/releases/download/${VERSION_MIHOMO##*-}/mihomo-linux-armv7-${VERSION_MIHOMO}.gz
url_yq=https://github.com/mikefarah/yq/releases/download/${VERSION_YQ}/yq_linux_arm.tar.gz
url_subconverter=https://github.com/tindy2013/subconverter/releases/download/${VERSION_SUBCONVERTER}/subconverter_armv7.tar.gz
;;
aarch64)
url_clash=https://downloads.clash.wiki/ClashPremium/clash-linux-arm64-2023.08.17.gz
url_mihomo=https://github.com/MetaCubeX/mihomo/releases/download/${VERSION_MIHOMO##*-}/mihomo-linux-arm64-${VERSION_MIHOMO}.gz
url_yq=https://github.com/mikefarah/yq/releases/download/${VERSION_YQ}/yq_linux_arm64.tar.gz
url_subconverter=https://github.com/tindy2013/subconverter/releases/download/${VERSION_SUBCONVERTER}/subconverter_aarch64.tar.gz
;;
*)
_error_quit "未知的架构版本:$arch,请自行下载对应版本至 ${ZIP_BASE_DIR} 目录"
;;
esac
local -A urls=(
[clash]="$url_clash"
[mihomo]="$url_mihomo"
[yq]="$url_yq"
[subconverter]="$url_subconverter"
)
local item target_zips=()
_okcat '🖥️ ' "系统架构:$arch $level"
for item in "$@"; do
local url="${urls[$item]}"
local proxy_url="${URL_GH_PROXY:+${URL_GH_PROXY%/}/}${url}"
[ "$item" != 'clash' ] && url="$proxy_url"
_okcat '⏳' "正在下载:${item}:$url"
local target="${ZIP_BASE_DIR}/$(basename "$url")"
curl \
--progress-bar \
--show-error \
--fail \
--insecure \
--location \
--retry 1 \
--output "$target" \
"$url"
target_zips+=("$target")
done
_valid_zip "${target_zips[@]}"
_load_zip >&/dev/null
}
_valid_zip() {
(($#)) || return 1
local zip fail_zips=()
for zip in "$@"; do
gzip -tq "$zip" || unzip -tqq "$zip" || fail_zips+=("$zip")
done
((${#fail_zips[@]})) && _error_quit "文件验证失败:${fail_zips[*]} 请删除后重试,或自行下载对应版本至 ${ZIP_BASE_DIR} 目录"
}
_unzip_zip() {
_valid_zip "$ZIP_KERNEL" "$ZIP_YQ" "$ZIP_SUBCONVERTER" "$ZIP_UI"
/usr/bin/install -D <(gzip -dc "$ZIP_KERNEL") "$BIN_KERNEL"
tar -xf "$ZIP_YQ" -C "${BIN_BASE_DIR}"
/bin/mv -f "${BIN_BASE_DIR}"/yq_* "${BIN_BASE_DIR}/yq"
tar -xf "$ZIP_SUBCONVERTER" -C "$BIN_BASE_DIR"
/bin/cp "$BIN_SUBCONVERTER_DIR/pref.example.yml" "$BIN_SUBCONVERTER_CONFIG"
unzip -oqq "$ZIP_UI" -d "$RESOURCES_BASE_DIR" 2>/dev/null || tar -xf "$ZIP_UI" -C "$RESOURCES_BASE_DIR"
}
# shellcheck disable=SC2206
_detect_init() {
[ -z "$INIT_TYPE" ] && INIT_TYPE=$(readlink /proc/1/exe)
grep -qsE "docker|kubepods|containerd|podman|lxc" /proc/1/cgroup && INIT_TYPE='nohup'
_is_root || {
INIT_TYPE='nohup'
FILE_LOG="${CLASH_RESOURCES_DIR}/${KERNEL_NAME}.log"
FILE_PID="${CLASH_RESOURCES_DIR}/${KERNEL_NAME}.pid"
}
service_log=(less '<' $FILE_LOG)
service_follow_log=(tail -f -n 0 $FILE_LOG)
service_watch_proxy=(clashon)
_is_regular_sudo && {
service_watch_proxy=(_failcat "'未检测到代理变量,可执行 clashon 开启代理环境'")
_SUDO=sudo
}
case "${INIT_TYPE}" in
*systemd)
service_log=($_SUDO journalctl -u "$KERNEL_NAME")
service_follow_log=("${service_log[@]}" -q -f -n 0)
_systemd
;;
*init)
_sysvinit
;;
*busybox)
command -v openrc-init >&/dev/null && _openrc
;;
*openrc*)
_openrc
;;
*runit)
_runit
;;
nohup | *)
INIT_TYPE='nohup'
_nohup
;;
esac
INIT_TYPE=$(basename "$INIT_TYPE")
}
_openrc() {
service_src="${SCRIPT_INIT_DIR}/OpenRC.sh"
service_target="/etc/init.d/$KERNEL_NAME"
service_enable=(rc-update add "$KERNEL_NAME" default)
service_disable=(rc-update del "$KERNEL_NAME" default)
service_start=(rc-service "$KERNEL_NAME" start)
service_stop=(rc-service "$KERNEL_NAME" stop)
service_restart=(rc-service "$KERNEL_NAME" restart)
service_status=(rc-service "$KERNEL_NAME" status)
service_is_active=(rc-service "$KERNEL_NAME" status)
}
_runit() {
service_src="${SCRIPT_INIT_DIR}/runit.sh"
service_target="/etc/sv/${KERNEL_NAME}/run"
service_del=(rm -rf "/etc/sv/${KERNEL_NAME:-mihomo}")
service_reload=(sleep 2)
service_enable=(ln -s "$(dirname "$service_target")" "/etc/runit/runsvdir/default/${KERNEL_NAME}")
service_disable=(rm -f "/etc/runit/runsvdir/current/${KERNEL_NAME}")
service_start=(sv up "$KERNEL_NAME")
service_stop=(sv down "$KERNEL_NAME")
service_restart=(sv restart "$KERNEL_NAME")
service_status=(sv status "$KERNEL_NAME")
service_is_active=(sv status "$KERNEL_NAME" \| grep -qs '^run')
}
_sysvinit() {
service_src="${SCRIPT_INIT_DIR}/SysVinit.sh"
service_target="/etc/init.d/$KERNEL_NAME"
command -v chkconfig >&/dev/null && {
service_add=(chkconfig --add "$KERNEL_NAME")
service_del=(chkconfig --del "$KERNEL_NAME")
service_enable=(chkconfig "$KERNEL_NAME" on)
service_disable=(chkconfig "$KERNEL_NAME" off)
}
command -v update-rc.d >&/dev/null && {
service_add=(update-rc.d "$KERNEL_NAME" defaults)
service_del=(update-rc.d "$KERNEL_NAME" remove)
service_enable=(update-rc.d "$KERNEL_NAME" enable)
service_disable=(update-rc.d "$KERNEL_NAME" disable)
}
service_start=(service "$KERNEL_NAME" start)
service_stop=(service "$KERNEL_NAME" stop)
service_restart=(service "$KERNEL_NAME" restart)
service_status=(service "$KERNEL_NAME" status)
service_is_active=(service "$KERNEL_NAME" status)
}
# shellcheck disable=SC2206
_systemd() {
service_src="${SCRIPT_INIT_DIR}/systemd.sh"
service_target="/etc/systemd/system/${KERNEL_NAME}.service"
service_reload=($_SUDO systemctl daemon-reload)
service_enable=($_SUDO systemctl enable "$KERNEL_NAME")
service_disable=($_SUDO systemctl disable "$KERNEL_NAME")
service_start=($_SUDO systemctl start "$KERNEL_NAME")
service_stop=($_SUDO systemctl stop "$KERNEL_NAME")
service_restart=($_SUDO systemctl restart "$KERNEL_NAME")
service_status=($_SUDO systemctl status "$KERNEL_NAME")
service_is_active=($_SUDO systemctl is-active "$KERNEL_NAME")
}
_nohup() {
service_enable=(false)
service_disable=(false)
service_start=('(' nohup "$BIN_KERNEL" -d "$CLASH_RESOURCES_DIR" -f "$CLASH_CONFIG_RUNTIME" '>\&' "$FILE_LOG" '\&' ')')
service_sudo_start=(sudo nohup "$BIN_KERNEL" -d "$CLASH_RESOURCES_DIR" -f "$CLASH_CONFIG_RUNTIME" '>\&' "$FILE_LOG" '\&')
service_status=(pgrep -fa "$BIN_KERNEL")
service_is_active=(pgrep -fa "$BIN_KERNEL")
service_stop=(pkill -9 -f "$BIN_KERNEL")
}
_install_service() {
local kernel_desc="$KERNEL_NAME Daemon, A[nother] Clash Kernel."
local cmd_path="${BIN_KERNEL}"
local cmd_arg="-d ${CLASH_RESOURCES_DIR} -f ${CLASH_CONFIG_RUNTIME}"
local cmd_full="${BIN_KERNEL} -d ${CLASH_RESOURCES_DIR} -f ${CLASH_CONFIG_RUNTIME}"
[ -n "$service_src" ] && {
/usr/bin/install -D -m +x "$service_src" "$service_target"
((${#service_add[@]})) && "${service_add[@]}"
sed -i \
-e "s#placeholder_cmd_path#$cmd_path#g" \
-e "s#placeholder_cmd_args#$cmd_arg#g" \
-e "s#placeholder_cmd_full#$cmd_full#g" \
-e "s#placeholder_log_file#$FILE_LOG#g" \
-e "s#placeholder_pid_file#$FILE_PID#g" \
-e "s#placeholder_kernel_name#$KERNEL_NAME#g" \
-e "s#placeholder_kernel_desc#$kernel_desc#g" \
"$service_target"
}
[ "$INIT_TYPE" != "nohup" ] && service_sudo_start=("${service_start[@]}")
sed -i \
-e "s#placeholder_start#${service_start[*]}#g" \
-e "s#placeholder_sudo_start#${service_sudo_start[*]}#g" \
-e "s#placeholder_status#${service_status[*]}#g" \
-e "s#placeholder_is_active#${service_is_active[*]}#g" \
-e "s#placeholder_stop#${service_stop[*]}#g" \
-e "s#placeholder_log#${service_log[*]}#g" \
-e "s#placeholder_follow_log#${service_follow_log[*]}#g" \
-e "s#placeholder_watch_proxy#${service_watch_proxy[*]}#g" \
"$CLASH_CMD_DIR/clashctl.sh" "$CLASH_CMD_DIR/common.sh"
"${service_enable[@]}" >&/dev/null && _okcat '🚀' '已设置开机自启'
((${#service_reload[@]})) && "${service_reload[@]}"
}
_uninstall_service() {
_detect_init
"${service_disable[@]}" >&/dev/null
((${#service_del[@]})) && "${service_del[@]}"
rm -f "$service_target"
((${#service_reload[@]})) && "${service_reload[@]}"
}
_detect_rc() {
local home=$HOME
_is_regular_sudo && home=$(awk -F: -v user="$SUDO_USER" '$1==user{print $6}' /etc/passwd)
command -v bash >&/dev/null && {
SHELL_RC_BASH="${home}/.bashrc"
}
command -v zsh >&/dev/null && {
SHELL_RC_ZSH="${home}/.zshrc"
}
command -v fish >&/dev/null && {
SHELL_RC_FISH="${home}/.config/fish/conf.d/clashctl.fish"
}
start_flag="# clashctl START"
end_flag="# clashctl END"
}
_apply_rc() {
_detect_rc
local source_clashctl=". $CLASH_CMD_DIR/clashctl.sh"
# shellcheck disable=SC2086
tee -a "$SHELL_RC_BASH" $SHELL_RC_ZSH >/dev/null <<EOF
$start_flag
# 加载 clashctl 命令
$source_clashctl
# 自动开启代理环境
watch_proxy
$end_flag
EOF
[ -n "$SHELL_RC_FISH" ] && /usr/bin/install "$SCRIPT_CMD_FISH" "$SHELL_RC_FISH"
$source_clashctl
}
_revoke_rc() {
_detect_rc
sed -i --follow-symlinks "/$start_flag/,/$end_flag/d" "$SHELL_RC_BASH" "$SHELL_RC_ZSH" 2>/dev/null
[ -n "$SHELL_RC_FISH" ] && rm -f "$SHELL_RC_FISH" 2>/dev/null
}
_set_envs() {
_set_env INIT_TYPE "$INIT_TYPE"
_set_env KERNEL_NAME "$KERNEL_NAME"
_set_env CLASH_BASE_DIR "$CLASH_BASE_DIR"
_set_env VERSION_MIHOMO "$VERSION_MIHOMO"
}
_get_random_val() {
cat /dev/urandom | tr -dc 'a-zA-Z0-9' | head -c 6
}
_is_regular_sudo() {
_is_root && [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != 'root' ]
}
_is_root() {
[ "$(id -u)" -eq 0 ]
}
_quit() {
_is_regular_sudo && exec su "$SUDO_USER"
exec "$SHELL" -i -c "$*"
}
================================================
FILE: uninstall.sh
================================================
#!/usr/bin/env bash
. .env
. "$CLASH_BASE_DIR/scripts/cmd/clashctl.sh" 2>/dev/null
. scripts/preflight.sh
pgrep -f "$BIN_KERNEL" -u 0 >/dev/null && ! _is_root && _error_quit "请先关闭 Tun 模式"
clashoff 2>/dev/null
_uninstall_service
_revoke_rc
command -v crontab >&/dev/null && crontab -l | grep -v "clashsub" | crontab -
/usr/bin/rm -rf "$CLASH_BASE_DIR"
echo '✨' '已卸载,相关配置已清除'
_quit
gitextract_4jtgepcj/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yml │ │ ├── config.yml │ │ ├── feat_report.yml │ │ └── q&a.yml │ └── workflows/ │ └── stale-issues.yml ├── .gitignore ├── .shellcheckrc ├── LICENSE ├── README.md ├── install.sh ├── resources/ │ ├── Country.mmdb │ ├── mixin.yaml │ ├── profiles/ │ │ └── .gitkeep │ └── profiles.yaml ├── scripts/ │ ├── cmd/ │ │ ├── clashctl.fish │ │ ├── clashctl.sh │ │ └── common.sh │ ├── init/ │ │ ├── OpenRC.sh │ │ ├── SysVinit.sh │ │ ├── runit.sh │ │ └── systemd.sh │ └── preflight.sh └── uninstall.sh
Condensed preview — 26 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (60K chars).
[
{
"path": ".editorconfig",
"chars": 70,
"preview": "root = true\n[*]\nend_of_line = lf\nindent_style = space\nindent_size = 2\n"
},
{
"path": ".gitattributes",
"chars": 19,
"preview": "* text=auto eol=lf\n"
},
{
"path": ".github/FUNDING.yml",
"chars": 952,
"preview": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [u"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yml",
"chars": 1858,
"preview": "name: Bug 反馈\ndescription: \"提交项目 Bug 报告\"\ntitle: \"[Bug] \"\nlabels: [\"bug\"]\nbody:\n - type: checkboxes\n id: ensure\n at"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 27,
"preview": "blank_issues_enabled: false"
},
{
"path": ".github/ISSUE_TEMPLATE/feat_report.yml",
"chars": 205,
"preview": "name: 功能建议\ndescription: 为该项目提出建议\ntitle: \"[Feature] \"\nlabels: [\"enhancement\"]\nbody:\n - type: textarea\n attributes:\n "
},
{
"path": ".github/ISSUE_TEMPLATE/q&a.yml",
"chars": 1121,
"preview": "name: Q&A\ndescription: \"问题反馈与解答\"\ntitle: \"[Q&A] \"\nlabels: []\nbody:\n - type: checkboxes\n id: ensure\n attributes:\n "
},
{
"path": ".github/workflows/stale-issues.yml",
"chars": 614,
"preview": "name: Close inactive issues\non:\n schedule:\n - cron: \"0 9 * * 1-5\"\n workflow_dispatch: # 允许手动触发\n\njobs:\n close-issue"
},
{
"path": ".gitignore",
"chars": 104,
"preview": ".idea\n.vscode\nconfig.yaml*\nresources/zip/*\n!resources/zip/dist.zip\nresources/dist/\ntest.sh\n\n\n\n!.gitkeep\n"
},
{
"path": ".shellcheckrc",
"chars": 59,
"preview": "disable=SC1091\ndisable=SC2155\ndisable=SC2296\ndisable=SC2153"
},
{
"path": "LICENSE",
"chars": 1089,
"preview": "MIT License\r\n\r\nCopyright (c) 2024-2026 nelvko\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a"
},
{
"path": "README.md",
"chars": 4638,
"preview": "# Linux 一键安装 Clash\n\n\n![GitHub top"
},
{
"path": "install.sh",
"chars": 629,
"preview": "#!/usr/bin/env bash\n\n. scripts/cmd/clashctl.sh\n. scripts/preflight.sh\n\n_valid\n_parse_args \"$@\"\n\n_prepare_zip\n_detect_ini"
},
{
"path": "resources/mixin.yaml",
"chars": 1197,
"preview": "_custom:\n system-proxy:\n enable: true\n\nmixed-port: 7890\n\nexternal-controller: \"0.0.0.0:9090\"\nexternal-ui: dist\nexter"
},
{
"path": "resources/profiles/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "resources/profiles.yaml",
"chars": 34,
"preview": "# 当前使用的订阅\nuse: \n\n# 订阅列表\nprofiles: "
},
{
"path": "scripts/cmd/clashctl.fish",
"chars": 1509,
"preview": "set fn_arr \\\nclashui \\\nclashstatus \\\nclashsecret \\\nclashtun \\\nclashmixin \\\nclashsub \\\nclashlog \\\nclashupgrade \\\nclashhel"
},
{
"path": "scripts/cmd/clashctl.sh",
"chars": 18297,
"preview": "#!/usr/bin/env bash\n\nTHIS_SCRIPT_DIR=$(dirname \"$(readlink -f \"${BASH_SOURCE:-${(%):-%N}}\")\")\n. \"$THIS_SCRIPT_DIR/common"
},
{
"path": "scripts/cmd/common.sh",
"chars": 6774,
"preview": "#!/usr/bin/env bash\n# shellcheck disable=SC2034\n. \"$(dirname \"$(dirname \"$THIS_SCRIPT_DIR\")\")/.env\"\n\nCLASH_RESOURCES_DIR"
},
{
"path": "scripts/init/OpenRC.sh",
"chars": 246,
"preview": "#!/sbin/openrc-run\n\ndescription=\"placeholder_kernel_desc\"\ncommand=\"placeholder_cmd_path\"\ncommand_args=\"placeholder_cmd_a"
},
{
"path": "scripts/init/SysVinit.sh",
"chars": 969,
"preview": "### BEGIN INIT INFO\n# Provides: placeholder_kernel_name\n# Required-Start: $network $local_fs $remote_fs\n# Required-Stop:"
},
{
"path": "scripts/init/runit.sh",
"chars": 62,
"preview": "#!/bin/sh\nexec placeholder_cmd_full >placeholder_log_file 2>&1"
},
{
"path": "scripts/init/systemd.sh",
"chars": 598,
"preview": "[Unit]\nDescription=placeholder_kernel_desc\nAfter=network.target NetworkManager.service systemd-networkd.service iwd.serv"
},
{
"path": "scripts/preflight.sh",
"chars": 13897,
"preview": "#!/usr/bin/env bash\n\nRESOURCES_BASE_DIR=\".${CLASH_RESOURCES_DIR#\"$CLASH_BASE_DIR\"}\"\n\nZIP_BASE_DIR=\".${CLASH_RESOURCES_DI"
},
{
"path": "uninstall.sh",
"chars": 384,
"preview": "#!/usr/bin/env bash\n. .env\n. \"$CLASH_BASE_DIR/scripts/cmd/clashctl.sh\" 2>/dev/null\n. scripts/preflight.sh\n\npgrep -f \"$BI"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the nelvko/clash-for-linux-install GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 26 files (54.1 KB), approximately 17.8k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.