Skip to content

Instantly share code, notes, and snippets.

@NevadaCities
Last active July 3, 2026 13:59
Show Gist options
  • Select an option

  • Save NevadaCities/655f76b215846f4eb7b7237c2903f138 to your computer and use it in GitHub Desktop.

Select an option

Save NevadaCities/655f76b215846f4eb7b7237c2903f138 to your computer and use it in GitHub Desktop.
使用 macOS 的 launchd 启动 mihomo

使用 macOS 的 launchd 启动 mihomo

本教程是以命令行后台并自启动运行 mihomo,如果需要使用 GUI,本教程可忽略。

admin 用户作为例子,以 mihomo 目录作为工作目录例子,请以实际情况替换


下载 mihomo 文件

先下载 mihomo 对应的二进制文件,移动到对应的工作目录下,例如:/Users/admin/mihomo

然后使用以下命令修改 mihomo 二进制文件的权限,如果需要使用TUN,此步骤是必须的

# mihomo 二进制文件的名称以 mihomo 为例
sudo chown root:admin ./mihomo
sudo chmod +sx ./mihomo

创建服务文件

/Library/LaunchAgents下创建一个com.metacubex.mihomo.plist服务文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <!--服务名称,一定不能重复, 否则无法被成功创建, 系统会告诉你已经有同名的服务-->
    <key>Label</key>
    <string>com.metacubex.mihomo</string>

    <!--程序位置及其参数-->
    <key>ProgramArguments</key>
    <array>
      <string>/Users/william/mihomo/mihomo</string>
      <string>-d</string>
      <string>/Users/william/mihomo</string>
      <string>-f</string>
      <string>/Users/william/mihomo/config.yaml</string>
    </array>

    <!--崩溃后自动重启-->
    <key>KeepAlive</key>
    <true/>

    <!--加载时运行服务-->
    <key>RunAtLoad</key>
    <true/>

    <!--错误日志输出路径-->
    <key>StandardErrorPath</key>
    <string>/Users/william/mihomo/stderr.log</string>

    <!--正常日志输出目录-->
    <key>StandardOutPath</key>
    <string>/Users/william/mihomo/stdout.log</string>

    <!--服务工作路径-->
    <key>WorkingDirectory</key>
    <string>/Users/william/mihomo</string>
  </dict>
</plist>

保存后使用以下命令直接启动 mihomo 服务:

launchctl load -w /Library/LaunchAgents/com.metacubex.mihomo.plist

运行以上命令后,如果没有报错,mihomo 服务就会立即运行。

如果要停止 mihomo 服务,需要使用以下命令:

launchctl unload -w /Library/LaunchAgents/com.metacubex.mihomo.plist

执行后即可停止 mihomo 服务。


最后注意

  1. 修改了 mihomo 二进制文件的权限之后,mihomo 下载的文件都会变成 root 用户,普通用户如果要修改、删除,需要使用 root 用户权限。mihomo 下载的文件一般有:网页控制台的源文件、geoip.dat、geoip.metadb、geosite.dat、country.mmdb、asn.dat 等等。
  2. launchd 运行服务之后如果出错,launchd 是不会在终端显示报错信息的,建议多次检查以上操作是否有遗漏疏忽地方。
  3. 在后期发现使用~/Library/LaunchAgents目录可能会有权限问题,可以移动到/Library/LaunchAgents下。

参考链接:

  1. 使用 launchd 管理 Mac 启动任务与定时任务
  2. macOS 下 Launchd 的介绍和使用
  3. 利用 Launchd 定制 Mac 启动任务
  4. man page plist section 5
  5. macOS 无法开启 tun 模式

使用networksetup命令快速切换 macOS 的系统代理状态

在任意位置下新建enableproxy.sh文件,并写入以下内容:

#!/bin/bash
networksetup -setwebproxystate Wi-Fi on
networksetup -setsecurewebproxystate Wi-Fi on
networksetup -setsocksfirewallproxystate Wi-Fi on

保存后,执行chmod +x enableproxy.sh赋予权限,然后执行sh enableproxy.sh即可快速打开系统代理。


在任意位置下新建disableproxy.sh文件,并写入以下内容:

#!/bin/bash
networksetup -setwebproxystate Wi-Fi off
networksetup -setsecurewebproxystate Wi-Fi off
networksetup -setsocksfirewallproxystate Wi-Fi off

保存后,执行chmod +x disableproxy.sh赋予权限,然后执行sh disableproxy.sh即可快速关闭系统代理。


参考链接:

  1. MacOS v1.3.3自动检测网络出口设备进行设置系统代理,不成功。
@fingerart

fingerart commented Jun 25, 2026

Copy link
Copy Markdown

对上面的流程整理到了一个shell脚本里:

但在我的mac上遇到一个问题,不管怎么调整TUN的配置,开启后始终都无法上网,目前只能只舍弃TUN。

mihomo_macos.sh
#!/usr/bin/env bash
# mihomo macOS 服务管理脚本
#
# 用法: ./mihomo_macos.sh <install|uninstall|start|stop|restart|status|logs|proxy on|proxy off>
# 详细说明运行 ./mihomo_macos.sh help

set -euo pipefail

# === 配置(可通过环境变量覆盖)===
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

PLIST_LABEL="com.metacubex.mihomo"
PLIST_NAME="${PLIST_LABEL}.plist"
PLIST_INSTALLED="/Library/LaunchAgents/${PLIST_NAME}"
PLIST_TMP="/tmp/${PLIST_NAME}"

# mihomo 二进制:默认从 PATH 查找
MIHOMO_BIN="${MIHOMO_BIN:-$(command -v mihomo 2>/dev/null || true)}"
# 工作目录(含 config.yaml):默认脚本所在目录
MIHOMO_DIR="${MIHOMO_DIR:-${SCRIPT_DIR}}"
MIHOMO_CONFIG="${MIHOMO_CONFIG:-${MIHOMO_DIR}/config.yaml}"
# 日志目录:默认 ~/Library/Logs/mihomo(避免日志写入 iCloud)
LOG_DIR="${LOG_DIR:-${HOME}/Library/Logs/mihomo}"

# 系统代理参数
PROXY_HOST="${PROXY_HOST:-127.0.0.1}"
HTTP_PORT="${HTTP_PORT:-7890}"
SOCKS_PORT="${SOCKS_PORT:-7891}"

# === 工具函数 ===
log()  { printf '\033[1;34m[mihomo]\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*" >&2; }
die()  { printf '\033[1;31m[error]\033[0m %s\n' "$*" >&2; exit 1; }

ensure_binary() {
    [[ -n "$MIHOMO_BIN" ]] || die "未找到 mihomo 二进制。请将 mihomo 加入 PATH,或设置 MIHOMO_BIN=/path/to/mihomo"
    [[ -f "$MIHOMO_BIN" ]] || die "mihomo 不存在: $MIHOMO_BIN"
}

ensure_config() {
    [[ -f "$MIHOMO_CONFIG" ]] || die "config 不存在: $MIHOMO_CONFIG"
}

# 探测当前激活的网络服务名(Wi-Fi / Ethernet ...)
get_active_network_service() {
    local iface svc
    iface=$(route -n get default 2>/dev/null | awk '/interface:/ {print $2}')
    [[ -n "$iface" ]] || die "无法确定默认网络接口(请检查网络连接)"
    svc=$(networksetup -listnetworkserviceorder \
        | grep -B 1 "Device: ${iface})" \
        | head -n 1 \
        | sed -E 's/^\(\*?[0-9]+\) //')
    [[ -n "$svc" ]] || die "无法确定接口 ${iface} 对应的网络服务名"
    echo "$svc"
}

# === 子流程 ===
setup_binary_perms() {
    ensure_binary
    log "设置二进制权限: root:admin +sx → $MIHOMO_BIN"
    sudo chown root:admin "$MIHOMO_BIN"
    sudo chmod +sx "$MIHOMO_BIN"
}

render_plist() {
    mkdir -p "$LOG_DIR"
    log "渲染 plist → $PLIST_TMP"
    cat > "$PLIST_TMP" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>${PLIST_LABEL}</string>

    <key>ProgramArguments</key>
    <array>
      <string>${MIHOMO_BIN}</string>
      <string>-d</string>
      <string>${MIHOMO_DIR}</string>
      <string>-f</string>
      <string>${MIHOMO_CONFIG}</string>
    </array>

    <key>KeepAlive</key>
    <true/>

    <key>RunAtLoad</key>
    <true/>

    <key>StandardErrorPath</key>
    <string>${LOG_DIR}/stderr.log</string>

    <key>StandardOutPath</key>
    <string>${LOG_DIR}/stdout.log</string>

    <key>WorkingDirectory</key>
    <string>${MIHOMO_DIR}</string>
  </dict>
</plist>
EOF
}

install_plist() {
    render_plist
    log "安装 plist → $PLIST_INSTALLED"
    sudo cp "$PLIST_TMP" "$PLIST_INSTALLED"
    sudo chown root:wheel "$PLIST_INSTALLED"
    sudo chmod 644 "$PLIST_INSTALLED"
    rm -f "$PLIST_TMP"
}

proxy_on() {
    local svc; svc=$(get_active_network_service)
    log "启用系统代理(${svc} → http ${PROXY_HOST}:${HTTP_PORT}, socks ${PROXY_HOST}:${SOCKS_PORT}"
    networksetup -setwebproxy            "$svc" "$PROXY_HOST" "$HTTP_PORT"
    networksetup -setsecurewebproxy      "$svc" "$PROXY_HOST" "$HTTP_PORT"
    networksetup -setsocksfirewallproxy  "$svc" "$PROXY_HOST" "$SOCKS_PORT"
    networksetup -setwebproxystate           "$svc" on
    networksetup -setsecurewebproxystate     "$svc" on
    networksetup -setsocksfirewallproxystate "$svc" on
}

proxy_off() {
    local svc; svc=$(get_active_network_service)
    log "关闭系统代理(${svc}"
    networksetup -setwebproxystate           "$svc" off
    networksetup -setsecurewebproxystate     "$svc" off
    networksetup -setsocksfirewallproxystate "$svc" off
}

is_loaded() {
    launchctl list 2>/dev/null | awk -v label="$PLIST_LABEL" '$3==label {found=1} END {exit !found}'
}

# === 子命令 ===
cmd_install() {
    ensure_binary
    ensure_config
    setup_binary_perms
    install_plist
    log "安装完成。运行 '$0 start' 启动服务"
}

cmd_uninstall() {
    log "卸载 mihomo(保留 config 与工作目录)"
    cmd_stop || true
    if [[ -f "$PLIST_INSTALLED" ]]; then
        log "删除 $PLIST_INSTALLED"
        sudo rm -f "$PLIST_INSTALLED"
    fi
    if [[ -d "$LOG_DIR" ]]; then
        log "删除日志目录: $LOG_DIR"
        rm -rf "$LOG_DIR"
    fi
    log "卸载完成(保留: $MIHOMO_DIR"
}

cmd_start() {
    ensure_binary
    ensure_config
    [[ -f "$PLIST_INSTALLED" ]] || die "服务未安装,请先运行: $0 install"
    if is_loaded; then
        warn "服务已加载,跳过 launchctl load"
    else
        log "加载服务"
        launchctl load -w "$PLIST_INSTALLED"
    fi
    proxy_on
    log "已启动"
}

cmd_stop() {
    if [[ -f "$PLIST_INSTALLED" ]] && is_loaded; then
        log "卸载服务"
        launchctl unload -w "$PLIST_INSTALLED" 2>/dev/null || true
    fi
    proxy_off
    log "已停止"
}

cmd_restart() {
    cmd_stop || true
    sleep 1
    cmd_start
}

cmd_status() {
    local pid
    pid=$(launchctl list 2>/dev/null | awk -v label="$PLIST_LABEL" '$3==label {print $1}')
    if [[ -z "$pid" || "$pid" == "-" ]]; then
        warn "服务未运行(label=${PLIST_LABEL}"
    else
        log "服务运行中 PID=${pid}"
    fi

    local svc; svc=$(get_active_network_service)
    log "当前网络服务: $svc"
    networksetup -getwebproxy           "$svc" | sed 's/^/  HTTP   | /'
    networksetup -getsecurewebproxy     "$svc" | sed 's/^/  HTTPS  | /'
    networksetup -getsocksfirewallproxy "$svc" | sed 's/^/  SOCKS  | /'
}

cmd_logs() {
    local out="${LOG_DIR}/stdout.log"
    local err="${LOG_DIR}/stderr.log"
    [[ -f "$out" || -f "$err" ]] || die "日志不存在: $LOG_DIR"
    log "tail -F ${LOG_DIR}/{stdout,stderr}.log  (Ctrl-C 退出)"
    tail -F "$out" "$err"
}

cmd_proxy() {
    case "${1:-}" in
        on)  proxy_on ;;
        off) proxy_off ;;
        *) die "用法: $0 proxy {on|off}" ;;
    esac
}

usage() {
    cat <<EOF
用法: $0 <命令>

服务管理:
  install      渲染 plist 并安装到 /Library/LaunchAgents/,设置二进制 setuid 权限
  uninstall    停止服务,移除 plist 和日志(保留 \$MIHOMO_DIR)
  start        加载服务并启用系统代理
  stop         卸载服务并关闭系统代理
  restart      重启服务
  status       查看 launchd 状态与系统代理状态
  logs         tail -F 标准输出/错误日志

系统代理:
  proxy on     仅启用系统代理(不动 mihomo 进程)
  proxy off    仅关闭系统代理

环境变量(可在调用前 export):
  MIHOMO_BIN     mihomo 二进制路径(默认: 从 PATH 查找)
  MIHOMO_DIR     工作目录(默认: 脚本同目录)
  MIHOMO_CONFIG  配置文件(默认: \$MIHOMO_DIR/config.yaml)
  LOG_DIR        日志目录(默认: ~/Library/Logs/mihomo)
  PROXY_HOST     代理主机(默认: 127.0.0.1)
  HTTP_PORT      HTTP/HTTPS 代理端口(默认: 7890)
  SOCKS_PORT     SOCKS 代理端口(默认: 7891)

当前解析:
  MIHOMO_BIN    = ${MIHOMO_BIN:-<未找到,请安装 mihomo 或设置 MIHOMO_BIN>}
  MIHOMO_DIR    = $MIHOMO_DIR
  MIHOMO_CONFIG = $MIHOMO_CONFIG
  LOG_DIR       = $LOG_DIR
EOF
}

main() {
    local cmd="${1:-}"; shift || true
    case "$cmd" in
        install)            cmd_install ;;
        uninstall)          cmd_uninstall ;;
        start)              cmd_start ;;
        stop)               cmd_stop ;;
        restart)            cmd_restart ;;
        status)             cmd_status ;;
        logs)               cmd_logs ;;
        proxy)              cmd_proxy "$@" ;;
        ""|-h|--help|help)  usage ;;
        *) usage; exit 1 ;;
    esac
}

main "$@"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment