Update clashctl

This commit is contained in:
Arvin
2026-03-21 18:07:16 +08:00
parent 16a13b4bef
commit 04d16bca7f

135
clashctl
View File

@ -273,7 +273,7 @@ cmd_stop() {
} }
cmd_update() { cmd_update() {
local branch remote_name dirty local branch remote_name dirty pull_ok=1
if ! has_git_repo; then if ! has_git_repo; then
err "当前目录不是 Git 仓库: $PROJECT_DIR" err "当前目录不是 Git 仓库: $PROJECT_DIR"
@ -288,18 +288,56 @@ cmd_update() {
remote_name="origin" remote_name="origin"
# 保护本地 .env如果你已经把 .env 忽略掉,这段也保留,兜底更稳)
if [ -f "$PROJECT_DIR/.env" ]; then
cp -f "$PROJECT_DIR/.env" "$PROJECT_DIR/.env.bak" 2>/dev/null || true
fi
dirty="$(git -C "$PROJECT_DIR" status --porcelain --untracked-files=no)" dirty="$(git -C "$PROJECT_DIR" status --porcelain --untracked-files=no)"
if [ -n "${dirty:-}" ]; then if [ -n "${dirty:-}" ]; then
err "检测到本地已修改但未提交的文件,已停止更新" echo "[WARN] 检测到本地已修改但未提交的文件,自动切换到安全强制更新模式"
echo "请先提交、stash 或恢复后再执行 clashctl update" >&2 pull_ok=0
fi
if [ "$pull_ok" -eq 1 ]; then
echo "[INFO] pulling latest code from ${remote_name}/${branch} ..."
if ! git -C "$PROJECT_DIR" pull --ff-only "$remote_name" "$branch"; then
echo "[WARN] git pull 失败,自动切换到强制更新模式"
pull_ok=0
fi
fi
if [ "$pull_ok" -eq 0 ]; then
echo "[INFO] fetching latest code from ${remote_name}/${branch} ..."
if ! git -C "$PROJECT_DIR" fetch "$remote_name" "$branch"; then
err "git fetch 失败"
exit 1 exit 1
fi fi
echo "[INFO] pulling latest code from ${remote_name}/${branch} ..." echo "[WARN] resetting local code to ${remote_name}/${branch}"
if ! git -C "$PROJECT_DIR" pull --ff-only "$remote_name" "$branch"; then if ! git -C "$PROJECT_DIR" reset --hard "${remote_name}/${branch}"; then
err "git pull 失败" err "git reset --hard 失败"
exit 1 exit 1
fi fi
fi
# 恢复 .env
if [ -f "$PROJECT_DIR/.env.bak" ]; then
mv -f "$PROJECT_DIR/.env.bak" "$PROJECT_DIR/.env" 2>/dev/null || true
fi
# 修复脚本权限,避免 203/EXEC
echo "[INFO] fixing executable permissions ..."
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 ..."
if has_systemd; then
systemctl stop clash-for-linux.service 2>/dev/null || true
sleep 1
fi
echo "[INFO] regenerating config ..." echo "[INFO] regenerating config ..."
if ! bash "$PROJECT_DIR/scripts/generate_config.sh"; then if ! bash "$PROJECT_DIR/scripts/generate_config.sh"; then
@ -309,19 +347,35 @@ cmd_update() {
echo "[INFO] restarting service ..." echo "[INFO] restarting service ..."
if has_systemd; then if has_systemd; then
if ! systemctl restart clash-for-linux.service; then if ! systemctl start clash-for-linux.service; then
err "systemd 启失败" err "systemd 启失败"
exit 1 exit 1
fi fi
echo "[INFO] waiting for service to be active ..."
local i state
for i in 1 2 3 4 5 6 7 8 9 10; do
state="$(systemctl is-active clash-for-linux.service 2>/dev/null || true)"
if [ "$state" = "active" ]; then
ok "更新完成"
cmd_status
return 0
fi
sleep 1
done
err "服务启动未就绪"
systemctl status clash-for-linux.service -l --no-pager || true
exit 1
else else
if ! "$PROJECT_DIR/scripts/run_clash.sh" --daemon; then if ! "$PROJECT_DIR/scripts/run_clash.sh" --daemon; then
err "脚本模式启动失败" err "脚本模式启动失败"
exit 1 exit 1
fi fi
fi
ok "更新完成" ok "更新完成"
cmd_status cmd_status
fi
} }
cmd_update_force() { cmd_update_force() {
@ -389,18 +443,53 @@ cmd_update_force() {
cmd_status cmd_status
} }
# === 修复执行权限 ===
fix_exec_permissions() {
local dir="${PROJECT_DIR:-$(pwd)}"
chmod +x "$dir"/scripts/*.sh 2>/dev/null || true
chmod +x "$dir"/bin/* 2>/dev/null || true
# 可选:修复 CRLF防止 Windows 换行)
sed -i 's/\r$//' "$dir"/scripts/*.sh 2>/dev/null || true
}
# === 等待 service 进入 active ===
wait_for_service_active() {
local svc="${1:-clash-for-linux.service}"
local timeout="${2:-10}"
for ((i=0; i<timeout; i++)); do
state=$(systemctl is-active "$svc" 2>/dev/null || true)
if [ "$state" = "active" ]; then
return 0
fi
sleep 1
done
return 1
}
# === 获取实际端口(从 runtime/config.yaml 解析)===
get_actual_ports() {
local cfg="$PROJECT_DIR/runtime/config.yaml"
if [ ! -f "$cfg" ]; then
return
fi
ACTUAL_HTTP_PORT=$(grep -E '^mixed-port:' "$cfg" | awk '{print $2}' | tr -d '\r')
ACTUAL_CTRL_PORT=$(grep -E '^external-controller:' "$cfg" | awk -F':' '{print $NF}' | tr -d '\r')
export ACTUAL_HTTP_PORT ACTUAL_CTRL_PORT
}
cmd_restart() { cmd_restart() {
cmd_generate cmd_generate
cmd_stop "${1:-false}" || true cmd_stop "${1:-false}" || true
cmd_start cmd_start
} }
cmd_update() {
git -C "$PROJECT_DIR" pull
cmd_restart
ok "Project updated"
}
cmd_ui() { cmd_ui() {
local raw="${1:-}" local raw="${1:-}"
local controller host port secret base_url local controller host port secret base_url
@ -1039,7 +1128,12 @@ main() {
cmd_status cmd_status
;; ;;
update) update)
cmd_update shift
cmd_update "$@"
;;
update-force)
shift
cmd_update_force "$@"
;; ;;
generate) generate)
cmd_generate cmd_generate
@ -1058,14 +1152,7 @@ main() {
shift || true shift || true
cmd_sub "${1:-show}" cmd_sub "${1:-show}"
;; ;;
update)
shift
cmd_update "$@"
;;
update-force)
shift
cmd_update_force "$@"
;;
tun) tun)
shift || true shift || true
cmd_tun "${1:-status}" cmd_tun "${1:-status}"