{"uuid": "64f7cbb4-57b2-4fcc-a112-28d800b28a95", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2022-24765", "type": "seen", "source": "https://gist.github.com/akccakcctw/8594329efb6ffc46ddf16bba4a0261b6", "content": "#!/usr/bin/env bash\n#\n# setup-mac-ci-tailscale.sh\n#\n# \u628a\u4e00\u53f0\u4e7e\u6de8\u7684 macOS \u4f48\u5efa\u6210\u300c\u53ef\u4ee5 SSH \u9032\u53bb\u7ba1\u7406\u7684 CI server\u300d\uff1a\n#\n#   1. \u88dd Homebrew\uff08\u9023\u5e36 Xcode Command Line Tools\uff09\n#   2. brew install tailscale \u2014\u2014 \u958b\u6e90 CLI \u7248\uff0c\u6c92\u6709 GUI App\u3001\u6c92\u6709 system extension\n#   3. tailscaled \u8a3b\u518a\u6210 LaunchDaemon\uff0c\u958b\u6a5f\u5373\u8d77\uff0c\u4e0d\u9700\u8981\u4efb\u4f55\u4eba\u767b\u5165\u5716\u5f62\u4ecb\u9762\n#   4. \u7528 auth key \u52a0\u5165 tailnet\n#   5. \u5efa\u7acb\u5c08\u7528\u7684 CI \u5e33\u865f\uff08\u9078\u7528\uff09\n#   6. \u88dd\u958b\u767c\u5de5\u5177\uff1abrew formula \uff0b mise \u7ba1\u7406\u7684 node\n#   7. \u958b\u555f macOS \u5167\u5efa sshd\u3001\u88dd\u4e0a\u516c\u9470\u3001\u95dc\u6389\u5bc6\u78bc\u767b\u5165\n#   8. \u95dc\u6389\u7761\u7720\u3001\u958b\u555f\u65b7\u96fb\u5f8c\u81ea\u52d5\u958b\u6a5f\n#\n# \u70ba\u4ec0\u9ebc\u662f brew \u800c\u4e0d\u662f\u5b98\u7db2\u4e0b\u8f09\u7684 .pkg\uff1a\n#   \u5b98\u7db2 macOS .pkg \u88dd\u7684\u662f GUI App\uff0c\u5b83\u9760 network system extension \u904b\u4f5c\uff0c\n#   \u800c system extension \u7b2c\u4e00\u6b21\u8981\u4eba\u5230 \u7cfb\u7d71\u8a2d\u5b9a \u624b\u52d5\u6309\u5141\u8a31\uff0c\u9084\u5e36\u4e00\u500b LoginItem\u3002\n#   CI server \u8981\u7684\u662f\u300c\u6c92\u4eba\u767b\u5165\u4e5f\u5728\u7dda\u300d\uff0c\u6240\u4ee5\u8d70\u958b\u6e90 CLI \u7248\u3002\n#   pkgs.tailscale.com \u7684 static tarball \u53ea\u6709 Linux\uff0cmacOS \u6c92\u6709\u90a3\u500b\u6a94\u3002\n#\n# \u9019\u652f script \u53ef\u4ee5\u91cd\u8907\u57f7\u884c\uff1a\u88dd\u6a5f\u3001\u5347\u7d1a\u3001\u4fee\u5fa9\u90fd\u8dd1\u540c\u4e00\u652f\u3002\n#\n# \u7528\u6cd5\uff08\u7528\u4e00\u822c admin \u5e33\u865f\u8dd1\uff0c\u4e0d\u8981 sudo \u2014\u2014 Homebrew \u62d2\u7d55\u4ee5 root \u5b89\u88dd\uff09\uff1a\n#\n#   ./setup-mac-ci-tailscale.sh \\\n#     --hostname ci-mac-01 \\\n#     --tags tag:ci \\\n#     --ci-user ci \\\n#     --ssh-key-url https://github.com/YOUR_GITHUB_ID.keys\n#\n# auth key \u5efa\u8b70\u7528\u74b0\u5883\u8b8a\u6578\u50b3\uff0c\u4e0d\u8981\u7559\u5728 shell history\uff1a\n#\n#   TS_AUTHKEY=tskey-auth-xxxx ./setup-mac-ci-tailscale.sh --hostname ci-mac-01 ...\n#\n# \u9078\u9805\uff1a\n#   --hostname NAME       \u9019\u53f0\u6a5f\u5668\u5728 tailnet \u88e1\u7684\u540d\u5b57\uff08\u5fc5\u586b\uff09\n#   --ci-user NAME        \u5efa\u7acb\u9019\u500b\u5e33\u865f\u4f86\u8dd1 CI \u4e26\u63a5\u53d7 SSH\uff08\u4e0d\u7d66\u5c31\u7528\u57f7\u884c\u8005\u81ea\u5df1\u7684\u5e33\u865f\uff09\n#   --ci-password PASS    CI \u5e33\u865f\u5bc6\u78bc\uff0c\u4e5f\u53ef\u7528\u74b0\u5883\u8b8a\u6578 CI_PASSWORD\u3002\u7d66\u4e86 --ci-user \u5c31\u5fc5\u586b\n#   --ci-fullname NAME    CI \u5e33\u865f\u7684\u986f\u793a\u540d\u7a31\uff0c\u9810\u8a2d \"CI Runner\"\n#   --brew-formulas \"...\" \u8981\u88dd\u7684 brew formula\uff0c\u7a7a\u767d\u5206\u9694\u3002\u9810\u8a2d \"git gh jq mise\"\n#   --node-version VER    \u7528 mise \u88dd\u7684 global node \u7248\u672c\uff0c\u4f8b\u5982 22.13.1\u3002\u4e0d\u7d66\u5c31\u4e0d\u88dd node\n#   --no-tools            \u5b8c\u5168\u4e0d\u88dd\u958b\u767c\u5de5\u5177\n#   --tags TAGS           ACL tag\uff0c\u4f8b\u5982 tag:ci\u3002\u5e36\u4e86 tag\uff0ckey expiry \u6703\u81ea\u52d5\u505c\u7528\n#   --authkey KEY         auth key\uff0c\u4e5f\u53ef\u7528\u74b0\u5883\u8b8a\u6578 TS_AUTHKEY\n#   --operator USER       \u54ea\u500b\u5e33\u865f\u53ef\u4ee5\u514d sudo \u8dd1 tailscale\uff08\u9810\u8a2d\uff1a\u57f7\u884c\u9019\u652f script \u7684\u4eba\uff09\n#   --ssh-key \"ssh-ed25519 AAAA...\"   \u76f4\u63a5\u7d66\u4e00\u628a\u516c\u9470\uff0c\u53ef\u91cd\u8907\n#   --ssh-key-url URL     \u5f9e\u7db2\u5740\u6293\u516c\u9470\uff0c\u4f8b\u5982 https://github.com/USER.keys\n#   --accept-dns          \u8b93 Tailscale \u63a5\u7ba1 DNS\uff08\u9810\u8a2d\u4e0d\u63a5\u7ba1\uff09\n#   --tailscale-ssh       \u958b\u555f Tailscale SSH\u3002\u9810\u8a2d\u4e0d\u958b \u2014\u2014 \u5b83\u6703\u6514\u622a port 22 \u4e26\u84cb\u6389\n#                         \u7cfb\u7d71 sshd\uff0c\u516c\u9470\u90a3\u689d\u8def\u6703\u5931\u6548\uff0c\u800c\u5931\u6557\u8a0a\u606f\u4e0d\u6703\u63d0\u793a\u9019\u4ef6\u4e8b\u3002\n#                         \u8981\u7528\u7684\u8a71 tailnet policy \u5fc5\u9808\u6709\u5c0d\u61c9\u7684 \"ssh\" \u898f\u5247\u3002\n#   --skip-ssh            \u5b8c\u5168\u4e0d\u52d5 SSH \u8a2d\u5b9a\n#   --skip-power          \u4e0d\u6539\u96fb\u6e90\u8a2d\u5b9a\n#   --skip-brew           \u5df2\u7d93\u6709 brew \u8ddf tailscale\uff0c\u53ea\u505a\u8a2d\u5b9a\n#\nset -euo pipefail\n\nreadonly SSHD_DROPIN=\"/etc/ssh/sshd_config.d/100-ci-hardening.conf\"\nreadonly MARKER=\"# managed by setup-mac-ci-tailscale.sh\"\nreadonly SYS_TAILSCALED=\"/usr/local/bin/tailscaled\"\n# \u65b0\u6a5f\u5668\u4e00\u5f8b arm64\uff0cHomebrew prefix \u56fa\u5b9a\nreadonly BREW_PREFIX=\"/opt/homebrew\"\n# ssh host 'cmd' \u662f\u975e\u4e92\u52d5\u3001\u975e\u767b\u5165 shell\uff0czsh \u53ea\u8b80 .zshenv\u3002\n# \u5beb\u5728 .zprofile \u7684\u8a71 CI job \u5b8c\u5168\u627e\u4e0d\u5230 brew \u8207 mise\uff0c\u6240\u4ee5\u4e00\u5f8b\u5beb .zshenv\u3002\nreadonly SHELLENV_LINE=\"eval \\\"\\$(/opt/homebrew/bin/brew shellenv)\\\"\nexport PATH=\\\"\\$HOME/.local/share/mise/shims:\\$PATH\\\"\nexport COREPACK_ENABLE_DOWNLOAD_PROMPT=0\"\n\n# ---------- \u53c3\u6578 ----------\nTS_HOSTNAME=\"\"\nTAGS=\"\"\nCI_USER=\"\"\nCI_PASSWORD=\"${CI_PASSWORD:-}\"\nCI_FULLNAME=\"CI Runner\"\nBREW_FORMULAS=\"git gh jq mise\"\nNODE_VERSION=\"\"\nSKIP_TOOLS=\"false\"\nAUTHKEY=\"${TS_AUTHKEY:-}\"\nOPERATOR=\"\"\nSSH_KEYS=()\nSSH_KEY_URLS=()\nACCEPT_DNS=\"false\"\nTAILSCALE_SSH=\"false\"\nSKIP_SSH=\"false\"\nSKIP_POWER=\"false\"\nSKIP_BREW=\"false\"\n\nusage() { sed -n '3,58p' \"$0\" | sed 's/^#\\{1,\\} \\{0,1\\}//'; exit \"${1:-0}\"; }\n\nwhile [[ $# -gt 0 ]]; do\n  case \"$1\" in\n    --hostname)         TS_HOSTNAME=\"$2\"; shift 2 ;;\n    --ci-user)          CI_USER=\"$2\"; shift 2 ;;\n    --ci-password)      CI_PASSWORD=\"$2\"; shift 2 ;;\n    --ci-fullname)      CI_FULLNAME=\"$2\"; shift 2 ;;\n    --brew-formulas)    BREW_FORMULAS=\"$2\"; shift 2 ;;\n    --node-version)     NODE_VERSION=\"$2\"; shift 2 ;;\n    --no-tools)         SKIP_TOOLS=\"true\"; shift ;;\n    --tags)             TAGS=\"$2\"; shift 2 ;;\n    --authkey)          AUTHKEY=\"$2\"; shift 2 ;;\n    --operator)         OPERATOR=\"$2\"; shift 2 ;;\n    --ssh-key)          SSH_KEYS+=(\"$2\"); shift 2 ;;\n    --ssh-key-url)      SSH_KEY_URLS+=(\"$2\"); shift 2 ;;\n    --accept-dns)       ACCEPT_DNS=\"true\"; shift ;;\n    --tailscale-ssh)    TAILSCALE_SSH=\"true\"; shift ;;\n    --no-tailscale-ssh) TAILSCALE_SSH=\"false\"; shift ;;\n    --skip-ssh)         SKIP_SSH=\"true\"; shift ;;\n    --skip-power)       SKIP_POWER=\"true\"; shift ;;\n    --skip-brew)        SKIP_BREW=\"true\"; shift ;;\n    -h|--help)          usage 0 ;;\n    *) printf 'unknown option: %s\\n' \"$1\" &gt;&amp;2; usage 1 ;;\n  esac\ndone\n\nstep() { printf '\\n\\033[1;34m==&gt; %s\\033[0m\\n' \"$*\"; }\ninfo() { printf '    %s\\n' \"$*\"; }\nwarn() { printf '\\033[1;33m[warn]\\033[0m %s\\n' \"$*\" &gt;&amp;2; }\ndie()  { printf '\\033[1;31m[error]\\033[0m %s\\n' \"$*\" &gt;&amp;2; exit 1; }\n\n# ---------- \u524d\u7f6e\u6aa2\u67e5 ----------\nstep \"\u524d\u7f6e\u6aa2\u67e5\"\n\n[[ \"$(uname -s)\" == \"Darwin\" ]] || die \"\u9019\u652f script \u53ea\u80fd\u5728 macOS \u4e0a\u8dd1\u3002\"\n[[ \"$(id -u)\" -ne 0 ]] || die \"\u4e0d\u8981\u7528 sudo \u8dd1\u9019\u652f\u3002Homebrew \u62d2\u7d55\u4ee5 root \u5b89\u88dd\u3002\n       \u7528\u4e00\u822c admin \u5e33\u865f\u57f7\u884c\uff0cscript \u6703\u5728\u9700\u8981\u7684\u5730\u65b9\u81ea\u5df1\u547c\u53eb sudo\u3002\"\n\nCALLER=\"$(id -un)\"\nCALLER_HOME=\"$HOME\"\nid -Gn \"$CALLER\" | tr ' ' '\\n' | grep -qx admin \\\n  || die \"$CALLER \u4e0d\u5728 admin \u7fa4\u7d44\uff0c\u6c92\u8fa6\u6cd5 sudo\u3002\"\ninfo \"\u57f7\u884c\u8005\uff1a${CALLER}\uff08${CALLER_HOME}\uff09\"\n\n[[ -n \"$TS_HOSTNAME\" ]] || die \"\u7f3a --hostname\u3002\u9019\u662f\u9019\u53f0\u6a5f\u5668\u5728 tailnet \u88e1\u7684\u540d\u5b57\u3002\"\n[[ -n \"$AUTHKEY\" ]] || die \"\u7f3a auth key\u3002\u7528 --authkey \u6216\u74b0\u5883\u8b8a\u6578 TS_AUTHKEY \u50b3\u5165\u3002\n       \u7522\u751f\u4f4d\u7f6e\uff1ahttps://login.tailscale.com/admin/settings/keys\"\n[[ \"$AUTHKEY\" == tskey-* ]] || warn \"auth key \u4e0d\u662f tskey- \u958b\u982d\uff0c\u78ba\u8a8d\u4e00\u4e0b\u6c92\u8cbc\u932f\u3002\"\n\nif [[ -n \"$CI_USER\" &amp;&amp; -z \"$CI_PASSWORD\" ]]; then\n  die \"\u7d66\u4e86 --ci-user $CI_USER \u5c31\u5fc5\u9808\u7d66\u5bc6\u78bc\u3002\n       \u7528 --ci-password\uff0c\u6216\u74b0\u5883\u8b8a\u6578 CI_PASSWORD\uff08\u4e0d\u6703\u7559\u5728 shell history\uff09\u3002\n       \u4e0d\u81ea\u52d5\u7522\u751f\uff1a\u5e33\u865f\u6709 sudo\uff0c\u5bc6\u78bc\u8981\u7531\u4f60\u6c7a\u5b9a\u4e26\u5b58\u9032\u5bc6\u78bc\u7ba1\u7406\u5668\u3002\"\nfi\n\nif [[ \"$SKIP_SSH\" != \"true\" &amp;&amp; ${#SSH_KEYS[@]} -eq 0 &amp;&amp; ${#SSH_KEY_URLS[@]} -eq 0 ]]; then\n  die \"\u6c92\u6709\u7d66\u516c\u9470\u3002\u8981\u8a2d SSH \u5c31\u7d66 --ssh-key \u6216 --ssh-key-url\uff0c\u4e0d\u7136\u52a0 --skip-ssh \u8df3\u904e\u3002\n       \uff08\u6c92\u6709\u516c\u9470\u537b\u95dc\u6389\u5bc6\u78bc\u767b\u5165 = \u628a\u81ea\u5df1\u9396\u5728\u9580\u5916\uff0c\u6240\u4ee5\u9019\u88e1\u76f4\u63a5\u64cb\u4e0b\u4f86\u3002\uff09\"\nfi\n\n# \u5148\u8981\u4e00\u6b21 sudo\uff0c\u7136\u5f8c\u5728\u80cc\u666f\u7e8c\u547d\uff0c\u4e2d\u9014\u4e0d\u6703\u518d\u554f\u5bc6\u78bc\nstep \"\u53d6\u5f97 sudo \u6b0a\u9650\"\nsudo -v || die \"\u62ff\u4e0d\u5230 sudo\u3002\"\nwhile true; do sudo -n true; sleep 50; kill -0 \"$$\" 2&gt;/dev/null || exit; done 2&gt;/dev/null &amp;\nSUDO_KEEPALIVE_PID=$!\ncleanup() { kill \"$SUDO_KEEPALIVE_PID\" 2&gt;/dev/null || true; }\ntrap cleanup EXIT\n\n# ---------- Homebrew ----------\nif [[ \"$SKIP_BREW\" == \"true\" ]]; then\n  step \"\u7565\u904e Homebrew \u8207 tailscale \u5b89\u88dd\uff08--skip-brew\uff09\"\n  [[ -x \"${BREW_PREFIX}/bin/brew\" ]] &amp;&amp; eval \"$(\"${BREW_PREFIX}/bin/brew\" shellenv)\"\nelse\n  step \"\u5b89\u88dd Homebrew\"\n  if [[ -x \"${BREW_PREFIX}/bin/brew\" ]]; then\n    info \"\u5df2\u5b58\u5728\uff1a$(\"${BREW_PREFIX}/bin/brew\" --version | head -1)\"\n  else\n    info \"\u6c92\u6709 brew\uff0c\u958b\u59cb\u5b89\u88dd\uff08\u6703\u4e00\u4f75\u88dd Xcode Command Line Tools\uff0c\u9700\u8981\u4e00\u6bb5\u6642\u9593\uff09\"\n    NONINTERACTIVE=1 /bin/bash -c \\\n      \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\" \\\n      || die \"Homebrew \u5b89\u88dd\u5931\u6557\u3002\"\n  fi\n\n  eval \"$(\"${BREW_PREFIX}/bin/brew\" shellenv)\"\n\n  # \u8b93\u4e4b\u5f8c\u6bcf\u6b21\u767b\u5165 shell \u90fd\u627e\u5f97\u5230 brew\n  for profile in \"${CALLER_HOME}/.zshenv\" \"${CALLER_HOME}/.bash_profile\"; do\n    touch \"$profile\"\n    if ! grep -qF 'mise/shims' \"$profile\"; then\n      printf '\\n%s\\n%s\\n' \"$MARKER\" \"$SHELLENV_LINE\" &gt;&gt; \"$profile\"\n      info \"\u5df2\u628a brew \uff0b mise shims \u5beb\u9032 $(basename \"$profile\")\"\n    fi\n  done\n\n  step \"\u5b89\u88dd tailscale\uff08\u958b\u6e90 CLI \u7248\uff09\"\n  if brew list --formula tailscale &gt;/dev/null 2&gt;&amp;1; then\n    info \"\u5df2\u5b89\u88dd\uff0c\u6aa2\u67e5\u66f4\u65b0\"\n    brew upgrade tailscale || info \"\u5df2\u662f\u6700\u65b0\"\n  else\n    brew install tailscale\n  fi\n  info \"$(tailscale version | head -1)\"\nfi\n\ncommand -v tailscale &gt;/dev/null 2&gt;&amp;1 || die \"\u627e\u4e0d\u5230 tailscale\u3002brew \u5b89\u88dd\u53ef\u80fd\u5931\u6557\u4e86\u3002\"\nBREW_TAILSCALED=\"$(brew --prefix)/bin/tailscaled\"\n[[ -x \"$BREW_TAILSCALED\" ]] || die \"\u627e\u4e0d\u5230 $BREW_TAILSCALED\"\n\n# ---------- \u8a3b\u518a\u6210 LaunchDaemon ----------\nstep \"\u628a tailscaled \u8a3b\u518a\u6210 LaunchDaemon\"\n\n# install-system-daemon \u9810\u8a2d\u6703\u628a binary \u8907\u88fd\u5230 /usr/local/bin/tailscaled\uff0c\n# \u4f46\u5b83\u660e\u78ba\u5730\u4e0d\u8986\u84cb symlink\u3002\u5148\u505a\u6210 symlink\uff0c\u4e4b\u5f8c brew upgrade \u5c31\u6703\u76f4\u63a5\u751f\u6548\uff0c\n# \u4e0d\u5fc5\u6bcf\u6b21\u91cd\u8dd1 install-system-daemon\u3002\nif [[ -L \"$SYS_TAILSCALED\" &amp;&amp; \"$(readlink \"$SYS_TAILSCALED\")\" == \"$BREW_TAILSCALED\" ]]; then\n  info \"$SYS_TAILSCALED \u5df2\u6307\u5411 brew \u7684\u7248\u672c\"\nelse\n  sudo mkdir -p /usr/local/bin\n  sudo rm -f \"$SYS_TAILSCALED\"\n  sudo ln -s \"$BREW_TAILSCALED\" \"$SYS_TAILSCALED\"\n  info \"$SYS_TAILSCALED -&gt; $BREW_TAILSCALED\"\nfi\n\nsudo \"$BREW_TAILSCALED\" install-system-daemon\ninfo \"\u5df2\u8f09\u5165 /Library/LaunchDaemons/com.tailscale.tailscaled.plist\"\n\nfor _ in $(seq 1 30); do\n  [[ -S /var/run/tailscaled.socket ]] &amp;&amp; break\n  sleep 1\ndone\n[[ -S /var/run/tailscaled.socket ]] \\\n  || die \"tailscaled \u6c92\u6709\u8d77\u4f86\u3002\u770b\uff1asudo launchctl print system/com.tailscale.tailscaled\"\ninfo \"daemon \u5df2\u5c31\u7dd2\"\n\n# ---------- CI \u5e33\u865f ----------\n# \u4e0d\u7d66 --ci-user \u5c31\u6cbf\u7528\u57f7\u884c\u8005\u81ea\u5df1\u7684\u5e33\u865f\u3002\nSSH_USER=\"$CALLER\"\nSSH_HOME=\"$CALLER_HOME\"\n\nif [[ -n \"$CI_USER\" ]]; then\n  step \"\u5efa\u7acb CI \u5e33\u865f\uff1a$CI_USER\"\n\n  if id -u \"$CI_USER\" &gt;/dev/null 2&gt;&amp;1; then\n    info \"\u5e33\u865f\u5df2\u5b58\u5728\uff0c\u4e0d\u91cd\u5efa\"\n  else\n    # \u8a3b\uff1a-password \u8d70\u547d\u4ee4\u5217\uff0c\u5bc6\u78bc\u6703\u77ed\u66ab\u51fa\u73fe\u5728 ps\u3002\u88dd\u6a5f\u7576\u4e0b\u53ea\u6709\u64cd\u4f5c\u8005\u5728\u5834\uff0c\n    # \u9019\u500b\u4ea4\u63db\u53ef\u4ee5\u63a5\u53d7\u3002\u4e0d\u63a5\u53d7\u7684\u8a71\u7528 --ci-password - \u8b93 sysadminctl \u4e92\u52d5\u8a62\u554f\u3002\n    sudo sysadminctl -addUser \"$CI_USER\" \\\n      -fullName \"$CI_FULLNAME\" \\\n      -password \"$CI_PASSWORD\" \\\n      -home \"/Users/${CI_USER}\" \\\n      -shell /bin/zsh \\\n      -admin \\\n      || die \"\u5efa\u7acb\u5e33\u865f $CI_USER \u5931\u6557\u3002\"\n    info \"\u5df2\u5efa\u7acb\uff08-admin\uff0c\u53ef\u4ee5 sudo\uff09\"\n  fi\n\n  sudo createhomedir -c -u \"$CI_USER\" &gt;/dev/null 2&gt;&amp;1 || true\n\n  # Homebrew \u662f\u57f7\u884c\u9019\u652f script \u7684 admin \u5e33\u865f\u88dd\u7684\uff0cprefix \u9810\u8a2d\u4e0d\u7d66 group \u5beb\u5165\u6b0a\uff0c\n  # \u6240\u4ee5 CI \u5e33\u865f\u6703\u649e \"directories are not writable by your user\"\u3002\n  # \u5169\u500b\u5e33\u865f\u90fd\u5728 admin \u7fa4\u7d44\uff0c\u6253\u958b group \u5beb\u5165\u6b0a\u5c31\u597d \u2014\u2014 \u4e0d\u7528 chown\uff0c\n  # chown \u6703\u8b93\u88dd\u6a5f\u7684 admin \u5e33\u865f\u53cd\u904e\u4f86\u4e0d\u80fd\u7528 brew\u3002\n  if [[ -d \"$BREW_PREFIX\" ]]; then\n    sudo chgrp -R admin \"$BREW_PREFIX\"\n    sudo chmod -R g+w \"$BREW_PREFIX\"\n    info \"\u5df2\u958b\u653e $BREW_PREFIX \u7d66 admin \u7fa4\u7d44\u5beb\u5165\uff08${CI_USER} \u624d\u7528\u5f97\u4e86 brew\uff09\"\n\n    # \u5149\u6709\u5beb\u5165\u6b0a\u9084\u4e0d\u5920\uff1a$BREW_PREFIX \u672c\u8eab\u662f git repo\uff0c\u64c1\u6709\u8005\u4e0d\u662f CI \u5e33\u865f\uff0c\n    # git 2.35.2 \u8d77\u6703\u62d2\u7d55\u64cd\u4f5c\uff08dubious ownership\uff0cCVE-2022-24765 \u7684\u4fee\u88dc\uff09\u3002\n    # brew update \u6703\u628a\u90a3\u500b\u932f\u8aa4\u541e\u6389\uff0c\u53ea\u5410\u51fa\u8aa4\u5c0e\u6027\u7684\n    #   Error: update-report should not be called directly!\n    # \u5b8c\u5168\u4e0d\u63d0\u771f\u6b63\u7684\u539f\u56e0\u3002\u7528 safe.directory \u958b\u4f8b\u5916\uff0c\u53ea\u5f71\u97ff CI \u5e33\u865f\u81ea\u5df1\u3002\n    # \u4e0d\u7528 chown\uff1a\u90a3\u6703\u628a\u540c\u4e00\u500b\u554f\u984c\u53cd\u904e\u4f86\u4e1f\u7d66\u88dd\u6a5f\u7684 admin \u5e33\u865f\u3002\n    for repo in \"$BREW_PREFIX\" \"${BREW_PREFIX}/Library/Taps\"/*/*; do\n      [[ -d \"${repo}/.git\" ]] || continue\n      if sudo -u \"$CI_USER\" -H git config --global --get-all safe.directory 2&gt;/dev/null \\\n           | grep -qxF \"$repo\"; then\n        continue\n      fi\n      sudo -u \"$CI_USER\" -H git config --global --add safe.directory \"$repo\"\n      info \"git safe.directory += $repo\"\n    done\n  fi\n\n  SSH_USER=\"$CI_USER\"\n  SSH_HOME=\"$(dscl . -read \"/Users/${CI_USER}\" NFSHomeDirectory 2&gt;/dev/null | awk '{print $2}')\"\n  [[ -d \"$SSH_HOME\" ]] || die \"\u627e\u4e0d\u5230 $CI_USER \u7684\u5bb6\u76ee\u9304\u3002\"\n  info \"\u5bb6\u76ee\u9304\uff1a$SSH_HOME\"\n\n  # \u8b93 CI job \u627e\u5f97\u5230 brew \u8207 mise\u3002.zshenv \u662f\u95dc\u9375\uff1assh host 'cmd' \u53ea\u8b80\u9019\u4e00\u4efd\u3002\n  for profile in \"${SSH_HOME}/.zshenv\" \"${SSH_HOME}/.bash_profile\"; do\n    sudo -u \"$CI_USER\" touch \"$profile\"\n    if ! sudo -u \"$CI_USER\" grep -qF 'mise/shims' \"$profile\" 2&gt;/dev/null; then\n      printf '\\n%s\\n%s\\n' \"$MARKER\" \"$SHELLENV_LINE\" \\\n        | sudo -u \"$CI_USER\" tee -a \"$profile\" &gt;/dev/null\n      info \"\u5df2\u628a brew \uff0b mise shims \u5beb\u9032 ${CI_USER} \u7684 $(basename \"$profile\")\"\n    fi\n  done\nfi\n\n# macOS \u6709\u4e00\u5c64\u8ddf sshd_config \u7121\u95dc\u7684\u5e33\u865f\u767d\u540d\u55ae\uff1acom.apple.access_ssh\u3002\n# \u9810\u8a2d\u5b83\u5de2\u72c0\u5305\u8457 admin \u7fa4\u7d44\uff0c\u6240\u4ee5\u975e admin \u5e33\u865f\u516c\u9470\u5168\u5c0d\u4e5f\u6703\u88ab\u62d2\uff0c\n# \u800c\u932f\u8aa4\u8a0a\u606f\u53ea\u6709 Permission denied (publickey)\uff0c\u770b\u4e0d\u51fa\u771f\u56e0\u3002\nif [[ \"$SKIP_SSH\" != \"true\" ]]; then\n  if dseditgroup -o checkmember -m \"$SSH_USER\" com.apple.access_ssh &gt;/dev/null 2&gt;&amp;1; then\n    info \"$SSH_USER \u5df2\u5728 com.apple.access_ssh \u767d\u540d\u55ae\u5167\"\n  else\n    sudo dseditgroup -o edit -a \"$SSH_USER\" -t user com.apple.access_ssh \\\n      || die \"\u628a $SSH_USER \u52a0\u9032 com.apple.access_ssh \u5931\u6557\u3002\"\n    info \"\u5df2\u628a $SSH_USER \u52a0\u9032 com.apple.access_ssh \u767d\u540d\u55ae\"\n  fi\nfi\n\n# ---------- \u958b\u767c\u5de5\u5177 ----------\nif [[ \"$SKIP_TOOLS\" == \"true\" || \"$SKIP_BREW\" == \"true\" ]]; then\n  step \"\u7565\u904e\u958b\u767c\u5de5\u5177\u5b89\u88dd\"\nelse\n  step \"\u5b89\u88dd\u958b\u767c\u5de5\u5177\"\n\n  for formula in $BREW_FORMULAS; do\n    if brew list --formula \"$formula\" &gt;/dev/null 2&gt;&amp;1; then\n      info \"\u5df2\u88dd\uff1a$formula\"\n    else\n      info \"\u5b89\u88dd\uff1a$formula\"\n      brew install \"$formula\" || warn \"$formula \u5b89\u88dd\u5931\u6557\uff0c\u7e7c\u7e8c\u3002\"\n    fi\n  done\n\n  if [[ -n \"$NODE_VERSION\" ]]; then\n    command -v mise &gt;/dev/null 2&gt;&amp;1 \\\n      || die \"\u8981\u88dd node \u4f46\u6c92\u6709 mise\u3002\u628a mise \u7559\u5728 --brew-formulas \u88e1\u3002\"\n\n    # \u8a9e\u8a00 runtime \u88dd\u5728 CI \u5e33\u865f\u540d\u4e0b\uff0c\u4e0d\u662f\u5168\u57df \u2014\u2014 \u6bcf\u500b repo \u624d\u80fd\u7528\n    # .node-version / .nvmrc \u91d8\u81ea\u5df1\u7684\u7248\u672c\u3002\n    MISE_CONFIG_DIR=\"${SSH_HOME}/.config/mise\"\n    MISE_CONFIG=\"${MISE_CONFIG_DIR}/config.toml\"\n    sudo -u \"$SSH_USER\" mkdir -p \"$MISE_CONFIG_DIR\"\n    if [[ ! -f \"$MISE_CONFIG\" ]]; then\n      # idiomatic_version_file_enable_tools \u8b93 mise \u8a8d\u5f97 repo \u88e1\u7684\n      # .node-version \u8207 .nvmrc\uff1bcorepack enable \u8b93 pnpm / yarn \u8ddf\u8457 node \u8d70\u3002\n      printf '%s\\n[settings]\\nidiomatic_version_file_enable_tools = [\"node\"]\\n' \"$MARKER\" \\\n        | sudo -u \"$SSH_USER\" tee \"$MISE_CONFIG\" &gt;/dev/null\n      info \"\u5df2\u5efa\u7acb $MISE_CONFIG\"\n    fi\n\n    info \"\u4ee5 $SSH_USER \u7684\u8eab\u5206\u5b89\u88dd node@${NODE_VERSION}\uff08\u9700\u8981\u5e7e\u5206\u9418\uff09\"\n    sudo -u \"$SSH_USER\" -H mise use -g \"node@${NODE_VERSION}\" \\\n      || die \"mise \u5b89\u88dd node@${NODE_VERSION} \u5931\u6557\u3002\"\n    # Node 22.13.1 \u5167\u5efa\u7684 corepack 0.30.0 \u5e36\u8457\u904e\u671f\u7684 npm registry \u7c3d\u7ae0\u91d1\u9470\uff0c\n    # \u6293 pnpm \u6703\u70b8\u5728 \"Cannot find matching keyid\"\u3002\u5148\u5347\u7d1a\u518d enable\u3002\n    sudo -u \"$SSH_USER\" -H mise exec \"node@${NODE_VERSION}\" -- \\\n      npm install -g corepack@latest &gt;/dev/null 2&gt;&amp;1 \\\n      || warn \"corepack \u5347\u7d1a\u5931\u6557\uff0cpnpm \u53ef\u80fd\u6703\u649e\u7c3d\u7ae0\u9a57\u8b49\u932f\u8aa4\u3002\"\n    sudo -u \"$SSH_USER\" -H mise exec \"node@${NODE_VERSION}\" -- corepack enable \\\n      || warn \"corepack enable \u5931\u6557\uff0cpnpm / yarn \u8981\u81ea\u5df1\u8655\u7406\u3002\"\n\n    info \"node: $(sudo -u \"$SSH_USER\" -H mise exec \"node@${NODE_VERSION}\" -- node --version 2&gt;/dev/null || echo '\u9a57\u4e0d\u5230')\"\n  else\n    info \"\u6c92\u7d66 --node-version\uff0c\u4e0d\u88dd node\"\n  fi\nfi\n\n# ---------- \u52a0\u5165 tailnet ----------\nstep \"\u52a0\u5165 tailnet\"\n\nbuild_up_args() {\n  UP_ARGS=(\n    --authkey=\"$AUTHKEY\"\n    --hostname=\"$TS_HOSTNAME\"\n    --accept-routes=false\n    --accept-dns=\"$ACCEPT_DNS\"\n  )\n  [[ -n \"$TAGS\" ]] &amp;&amp; UP_ARGS+=(--advertise-tags=\"$TAGS\")\n  [[ \"$1\" == \"with-ssh\" ]] &amp;&amp; UP_ARGS+=(--ssh)\n}\n\nif [[ \"$TAILSCALE_SSH\" == \"true\" ]]; then\n  build_up_args with-ssh\n  info \"tailscale up\uff08\u542b --ssh\uff09\"\n  if ! sudo tailscale up \"${UP_ARGS[@]}\"; then\n    warn \"\u5e36 --ssh \u7684 tailscale up \u5931\u6557\uff0c\u6539\u7528\u4e0d\u5e36 --ssh \u518d\u8a66\u4e00\u6b21\u3002\"\n    warn \"\u4e4b\u5f8c\u53ea\u80fd\u9760\u7cfb\u7d71 sshd \u9023\u7dda\uff0c\u9019\u4ecd\u7136\u662f\u901a\u7684\u3002\"\n    TAILSCALE_SSH=\"false\"\n    build_up_args no-ssh\n    sudo tailscale up \"${UP_ARGS[@]}\" || die \"tailscale up \u5931\u6557\u3002\"\n  fi\nelse\n  build_up_args no-ssh\n  info \"tailscale up\uff08\u4e0d\u542b --ssh\uff09\"\n  sudo tailscale up \"${UP_ARGS[@]}\" || die \"tailscale up \u5931\u6557\u3002\"\nfi\n\nOP=\"${OPERATOR:-$SSH_USER}\"\nsudo tailscale set --operator=\"$OP\"\ninfo \"operator = ${OP}\uff08\u8a72\u5e33\u865f\u8dd1 tailscale \u6307\u4ee4\u514d sudo\uff09\"\n\nTS_IP=\"$(tailscale ip -4 2&gt;/dev/null | head -1 || true)\"\ninfo \"tailnet IP\uff1a${TS_IP:-\u53d6\u4e0d\u5230}\"\n\n# ---------- SSH ----------\nif [[ \"$SKIP_SSH\" == \"true\" ]]; then\n  step \"\u7565\u904e SSH \u8a2d\u5b9a\uff08--skip-ssh\uff09\"\nelse\n  step \"\u8a2d\u5b9a\u7cfb\u7d71 sshd\"\n\n  KEYS_TMP=\"$(mktemp)\"\n  trap 'cleanup; rm -f \"$KEYS_TMP\"' EXIT\n\n  for url in ${SSH_KEY_URLS+\"${SSH_KEY_URLS[@]}\"}; do\n    info \"\u6293\u516c\u9470\uff1a$url\"\n    curl -fsSL --retry 3 \"$url\" &gt;&gt; \"$KEYS_TMP\" || die \"\u6293\u4e0d\u5230 $url\"\n  done\n  for key in ${SSH_KEYS+\"${SSH_KEYS[@]}\"}; do\n    printf '%s\\n' \"$key\" &gt;&gt; \"$KEYS_TMP\"\n  done\n\n  # \u53ea\u7559\u770b\u8d77\u4f86\u771f\u7684\u662f\u516c\u9470\u7684\u884c\uff0c\u907f\u514d\u628a HTML \u932f\u8aa4\u9801\u5beb\u9032 authorized_keys\n  grep -E '^(ssh-(rsa|ed25519|dss)|ecdsa-sha2-|sk-ssh-|sk-ecdsa-)' \"$KEYS_TMP\" \\\n    &gt; \"${KEYS_TMP}.clean\" || true\n  mv \"${KEYS_TMP}.clean\" \"$KEYS_TMP\"\n\n  KEY_COUNT=\"$(grep -c . \"$KEYS_TMP\" || true)\"\n  [[ \"${KEY_COUNT:-0}\" -gt 0 ]] \\\n    || die \"\u4e00\u628a\u6709\u6548\u7684\u516c\u9470\u90fd\u6c92\u6709\uff0c\u4e2d\u6b62\u3002\u95dc\u6389\u5bc6\u78bc\u767b\u5165\u4e4b\u524d\u4e00\u5b9a\u8981\u5148\u6709\u9470\u5319\u3002\"\n  info \"\u53d6\u5f97 $KEY_COUNT \u628a\u516c\u9470\uff0c\u5c07\u88dd\u5230 $SSH_USER \u540d\u4e0b\"\n\n  SSH_DIR=\"${SSH_HOME}/.ssh\"\n  AUTH_KEYS=\"${SSH_DIR}/authorized_keys\"\n  sudo -u \"$SSH_USER\" mkdir -p \"$SSH_DIR\"\n  sudo -u \"$SSH_USER\" touch \"$AUTH_KEYS\"\n  while IFS= read -r key; do\n    [[ -z \"$key\" ]] &amp;&amp; continue\n    label=\"$(printf '%s' \"$key\" | awk '{print $NF}')\"\n    if sudo grep -qxF \"$key\" \"$AUTH_KEYS\"; then\n      info \"\u5df2\u5b58\u5728\uff1a$label\"\n    else\n      printf '%s\\n' \"$key\" | sudo -u \"$SSH_USER\" tee -a \"$AUTH_KEYS\" &gt;/dev/null\n      info \"\u52a0\u5165\uff1a$label\"\n    fi\n  done &lt; \"$KEYS_TMP\"\n\n  sudo chown -R \"${SSH_USER}:staff\" \"$SSH_DIR\"\n  sudo chmod 700 \"$SSH_DIR\"\n  sudo chmod 600 \"$AUTH_KEYS\"\n  info \"authorized_keys \u4f4d\u7f6e\uff1a$AUTH_KEYS\"\n\n  # systemsetup -setremotelogin \u5728\u8fd1\u7248 macOS \u9700\u8981 Full Disk Access\uff0c\n  # \u76f4\u63a5\u64cd\u4f5c launchd \u4e0d\u9700\u8981\uff0c\u6240\u4ee5\u8d70\u9019\u689d\u3002\n  info \"\u555f\u7528 com.openssh.sshd\"\n  sudo launchctl enable system/com.openssh.sshd 2&gt;/dev/null || true\n  sudo launchctl bootstrap system /System/Library/LaunchDaemons/ssh.plist 2&gt;/dev/null || true\n\n  HARDENING=\"${MARKER}\nPasswordAuthentication no\nKbdInteractiveAuthentication no\nPermitRootLogin no\nPubkeyAuthentication yes\nUsePAM yes\"\n\n  if grep -qE '^[[:space:]]*Include[[:space:]]+/etc/ssh/sshd_config\\.d/' /etc/ssh/sshd_config 2&gt;/dev/null; then\n    sudo mkdir -p /etc/ssh/sshd_config.d\n    printf '%s\\n' \"$HARDENING\" | sudo tee \"$SSHD_DROPIN\" &gt;/dev/null\n    sudo chmod 644 \"$SSHD_DROPIN\"\n    info \"\u5beb\u5165 $SSHD_DROPIN\"\n  elif sudo grep -qF \"$MARKER\" /etc/ssh/sshd_config; then\n    info \"/etc/ssh/sshd_config \u5df2\u6709\u672c script \u7684\u5340\u584a\uff0c\u4e0d\u91cd\u8907\u52a0\"\n  else\n    sudo cp /etc/ssh/sshd_config \"/etc/ssh/sshd_config.bak.$(date +%Y%m%d%H%M%S)\"\n    printf '\\n%s\\n' \"$HARDENING\" | sudo tee -a /etc/ssh/sshd_config &gt;/dev/null\n    info \"\u9644\u52a0\u8a2d\u5b9a\u5230 /etc/ssh/sshd_config\uff08\u539f\u6a94\u5df2\u5099\u4efd\uff09\"\n  fi\n\n  # \u8a2d\u5b9a\u5beb\u58de\u5c31\u4e0d\u91cd\u8f09 sshd\uff0c\u514d\u5f97\u628a\u81ea\u5df1\u9396\u5728\u5916\u9762\n  if sudo /usr/sbin/sshd -t 2&gt;/dev/null; then\n    sudo launchctl kickstart -k system/com.openssh.sshd 2&gt;/dev/null || true\n    info \"sshd \u8a2d\u5b9a\u9a57\u904e\u4e26\u5df2\u91cd\u8f09\"\n  else\n    warn \"sshd -t \u9a57\u4e0d\u904e\uff0c\u6c92\u6709\u91cd\u8f09 sshd\u3002\u8dd1 sudo /usr/sbin/sshd -t \u770b\u932f\u5728\u54ea\u3002\"\n  fi\nfi\n\n# ---------- \u96fb\u6e90 ----------\nif [[ \"$SKIP_POWER\" == \"true\" ]]; then\n  step \"\u7565\u904e\u96fb\u6e90\u8a2d\u5b9a\uff08--skip-power\uff09\"\nelse\n  step \"\u96fb\u6e90\u8a2d\u5b9a\uff1a\u4e0d\u7761\u3001\u65b7\u96fb\u5f8c\u81ea\u52d5\u958b\u6a5f\"\n  sudo pmset -a sleep 0        2&gt;/dev/null || warn \"pmset sleep 0 \u5931\u6557\"\n  sudo pmset -a disablesleep 1 2&gt;/dev/null || warn \"pmset disablesleep 1 \u5931\u6557\uff08\u684c\u6a5f\u6c92\u6709\u9019\u500b\u9375\uff0c\u6b63\u5e38\uff09\"\n  sudo pmset -a womp 1         2&gt;/dev/null || warn \"pmset womp 1 \u5931\u6557\"\n  sudo pmset -a autorestart 1  2&gt;/dev/null || warn \"pmset autorestart 1 \u5931\u6557\"\n  sudo pmset -a displaysleep 10 2&gt;/dev/null || true\n  info \"\u5df2\u5957\u7528\uff08\u87a2\u5e55\u4ecd\u6703\u95dc\uff0c\u7cfb\u7d71\u4e0d\u7761\uff09\"\nfi\n\n# ---------- \u6536\u5c3e ----------\nstep \"\u5b8c\u6210\"\ntailscale status || true\n\nprintf '\\n'\nprintf '  \u6a5f\u5668\u540d\u7a31   : %s\\n' \"$TS_HOSTNAME\"\nprintf '  SSH \u5e33\u865f   : %s\\n' \"$SSH_USER\"\nprintf '  tailnet IP : %s\\n' \"${TS_IP:-\u53d6\u4e0d\u5230}\"\nprintf '  \u7248\u672c       : %s\\n' \"$(tailscale version | head -1)\"\nprintf '\\n  \u5f9e\u53e6\u4e00\u53f0\u5df2\u5728 tailnet \u7684\u6a5f\u5668\u9023\u9032\u4f86\uff1a\\n'\n[[ \"$TAILSCALE_SSH\" == \"true\" ]] &amp;&amp; \\\nprintf '    tailscale ssh %s@%s      # \u8d70 tailnet \u8eab\u5206 + ACL\uff0c\u4e0d\u9700\u8981 authorized_keys\\n' \"$SSH_USER\" \"$TS_HOSTNAME\"\nprintf '    ssh %s@%s        # \u7cfb\u7d71 sshd\uff0c\u8d70 authorized_keys\\n' \"$SSH_USER\" \"${TS_IP:-}\"\nif [[ \"$TAILSCALE_SSH\" == \"true\" ]]; then\n  printf '\\n\\033[1;33m  Tailscale SSH \u5df2\u958b\u555f\uff0c\u5b83\u6703\u6514\u622a port 22 \u4e26\u84cb\u6389\u7cfb\u7d71 sshd\u3002\\033[0m\\n'\n  printf '  tailnet policy \u6c92\u6709\u5c0d\u61c9\u7684 \"ssh\" \u898f\u5247\u7684\u8a71\uff0c\u5169\u689d\u8def\u90fd\u6703\u88ab\u62d2\uff0c\u8a0a\u606f\u662f\\n'\n  printf '    tailnet policy does not permit you to SSH to this node\\n'\n  printf '  \u7576\u5834\u8981\u7528\u516c\u9470\u9032\u4f86\u7684\u8a71\uff1asudo tailscale set --ssh=false\\n'\nfi\n\nif [[ \"$SKIP_SSH\" != \"true\" ]]; then\n  printf '\\n\\033[1;33m  \u96e2\u958b\u9019\u53f0\u6a5f\u5668\u4e4b\u524d\uff0c\u5148\u958b\u53e6\u4e00\u500b\u8996\u7a97\u78ba\u8a8d SSH \u771f\u7684\u9023\u5f97\u9032\u4f86\u3002\\033[0m\\n'\n  printf '  \u5bc6\u78bc\u767b\u5165\u5df2\u7d93\u95dc\u6389\uff0c\u516c\u9470\u6c92\u8a2d\u5c0d\u5c31\u9032\u4e0d\u4f86\u4e86\u3002\\n'\nfi\n\nprintf '\\n  \u9084\u6c92\u505a\u3001\u8981\u81ea\u5df1\u6c7a\u5b9a\u7684\uff1a\\n'\nprintf '    - FileVault\uff1a\u958b\u8457\u7684\u8a71\u91cd\u958b\u6a5f\u9700\u8981\u6709\u4eba\u5230\u5834\u89e3\u9396\u3002\u9060\u7aef\u91cd\u958b\u7528 sudo fdesetup authrestart\\n'\nprintf '    - \u5230 admin console \u78ba\u8a8d\u9019\u53f0\u5df2 approve\u3001tag \u6b63\u78ba\uff1ahttps://login.tailscale.com/admin/machines\\n'\nprintf '    - \u65e5\u5f8c\u5347\u7d1a\uff1abrew upgrade tailscale &amp;&amp; sudo launchctl kickstart -k system/com.tailscale.tailscaled\\n'\n", "creation_timestamp": "2026-09-03T07:04:27.285479Z"}