多台 Windows 用 Tailscale 组网 + SSH 互控与文件互投

SKILL运维和基础设施
运维和基础设施 未标注权限 许可证不明确
安装这个技能

面向「手上有好几台 Windows,分散在不同地点,想让它们互相看得见、控得了、传得了文件」的场景。解决五类必然踩的坑。一是组网期,精简系统上装 Tailscale 会因 iphlpsvc 被禁返回 msiexec 1603,1.4MB 的 tailscale-setup.exe 是联网引导器、离线机器用不得,必须下完整 MSI;入网要不要 --accept-routes,以及「广播的网段一旦与接收方自身网段重叠,会因隧道路由 metric 更低而制造全网段黑洞」这条硬性禁令;找任何 exe 之前必须先跑 where——Git for Windows 常装在用户目录而非 Program Files,断言「没装」之前至少确认过一次。二是误判期,tailscale ping 首次报 DERP 中继往往是打洞协商的临时态、复查才准;Windows 公用网络配置与 AP 隔离策略会静默丢 ICMP,用 ping 判死活必错;本机 HTTP 代理会让「主机名通、IP 不通」看着像路由问题。三是 SSH 期,Permission denied 在 Windows 上无法区分「用户不存在」与「公钥不匹配」,必须先在目标机拿真实账户列表;ssh-keygen -N 的引号写法会把两个引号字符变成字面密码导致私钥加密;ssh -vvv 出现 Server accepts key 却仍被拒,病根在客户端私钥而非服务端 authorized_keys;两把私钥 ACL 不一致(一把带继承标记)会被 OpenSSH 直接拒掉;Git for Windows 自带的 ssh/scp 优先于 Windows 自带那份,后者同机会连不上。四是投递期,Win32-OpenSSH 的 scp/sftp 在 Windows 上会报 pipe Unknown error(ERROR_PIPE_BUSY)并静默失败,两台机器同处一个局域网时主机名会被解析成 fe80 IPv6 链路本地地址而非 Tailscale 地址(短主机名更会 ENOTFOUND,连不上≠离线),tailscale file cp 报 not in your Tailnet 通常是发起方自己掉线;node 里 spawn ssh/scp 会报 EBUSY 文件锁,定时任务应改用 bash。五是自动化期,计划任务要连其他机器时会撞上两个互相独立的开关——Tailscale 层用 `tailscale up --unattended=true` 让它在 GUI 用户登出后仍在线(默认关着,不开就下线),任务层由 LogonType 决定无人时触不触发(不写 `/ru` 默认是 `Interactive`,用户一注销就不跑;要无人值守得用 `/ru SYSTEM` 或 `/ru 用户名 /rp 密码`);另外 SYSTEM 身份拿不到 Tailscale 网卡时 ssh 会回显一串 `@@@@`;做定时同步的省心组合是「先比对哈希相同就跳过 + 离线机标记 PENDING 立刻跳过 + 状态存文件」,5 分钟一轮跑完即退,空闲时零占用。附带一个可直接运行的 SendFile.ps1 工具:选机器、选文件、发送,支持指定中文目标目录,内部已处理史上全部坑。当用户说多台电脑组网、异地互连、远程控这几台机器、机器之间传文件、 Tailscale 连不上、打洞、中继、直连、子网路由、KeyExpiry、Permission denied、pipe Unknown error、计划任务连不上、EBUSY、这台机器没装 Git 时使用。触发词:Tailscale 组网、tailnet、多机互联、组网打洞、DERP 中继、 MagicDNS、子网路由 approve、key expiry、异地 SSH、authorized_keys、公钥登录、scp 失败、sftp 报错、传文件到另一台电脑、ping 不通但端口通、手机/平板装 Tailscale 登录不上、扫码登录失败、卡在登录页、auth key 怎么用、计划任务、schtasks、定时同步、EBUSY。

一句话选型结论

按资料字段整理

多台 Windows 用 Tailscale 组网 + SSH 互控与文件互投是运维和基础设施类的 Agent Skill,安装方式未标注,权限级别未标注,许可证不明确,有可直接复制的安装命令。技能本质是给模型读的说明书,效果取决于模型与客户端;装完先用一个小任务验证,再放进正式流程。

分类运维和基础设施 安装方式未标注 权限级别未标注权限 许可证许可证不明确
累计安装 未标注 本周 未标注
GitHub 星标 未标注 仓库 未标注
下载量 未标注 技能市场下载数
权限级别 未标注权限 安装前请确认风险范围
许可证 许可证不明确 未说明

安装

未标注
在支持 Skills 的客户端里执行,装完问一句「你有哪些技能」验证
skillhub install user_878e6d7a/tailscale-win-mesh
  1. 1
    确认客户端支持

    确认你的客户端支持 Skills(Claude、Codex、Cursor 等),并升级到较新的版本。

  2. 2
    拿到技能文件

    执行上面的命令安装;也可以把仓库里的 SKILL.md 手动放进客户端的 skills 目录。

  3. 3
    刷新技能列表

    重新打开客户端,或刷新技能列表,让新放进来的技能被加载。

  4. 4
    验证是否生效

    问一句「你有哪些技能」,确认列表里出现它,再拿一个小任务试跑。

技能档案

运维和基础设施
字段来自技能仓库与公开索引,中文说明由本站标注
作者 / 维护方user_878e6d7a仓库 未标注 · 版本 未标注
宿主客户端未标注技能可被这些客户端加载
安装方式未标注安装包 community
权限级别未标注权限安装前请确认风险范围
许可证未说明许可证不明确,商用前请自行确认条款
仓库可用性available技能仓库当前是否可访问
最近核验未标注上游最后核验时间 · 数据更新 1791631607
正文体量53.8 KBSKILL.md 原文大小,越大说明说明书写得越细

典型使用场景

3 类
按分类整理的常见用法,具体以 SKILL.md 为准
查服务与日志

按现象定位服务状态与日志线索。

写部署与巡检脚本

把重复的部署、巡检写成脚本。

故障排查

按步骤排查网络、依赖与配置。

选择建议

装之前先看这几条
按条目字段给出的判断,不替你决定用不用

值得选的地方

  • 有可直接复制的安装命令,不用自己翻文档找入口。

先想清楚的地方

  • 没有标注权限级别,实际能做什么要装好后确认。
  • 许可证不明确,商用或二次分发前先到仓库确认条款。
  • 没有做站内核验,内容以作者仓库为准。
  • 技能只是说明书,实际效果取决于模型与客户端,装完先用小任务试一遍。

更适合

  • 需要在运维和基础设施场景里给模型补一套做法的人。
  • 愿意先读一遍 SKILL.md、确认它会让模型做什么的人。

不太适合

  • 对许可证要求很严、必须明确可商用的人。
  • 只想装完就自动变强、不打算看说明书的人。

同类对比

6 个同类
同分类里挑最接近的几条,字段以各自详情页为准
技能权限级别安装方式累计安装宿主客户端安装命令
多台 Windows 用 Tailscale 组网 + SSH 互控与文件互投当前 未标注权限 未标注 未标注 未标注 有
Azure 部署 调用外部服务 GitHub 仓库 664.2 万 Codex、Claude、Cursor 有
Azure diagnostics 问题管理 调用外部服务 GitHub 仓库 657.6 万 Codex、Claude、Cursor 有
Azure aigateway 部署 调用外部服务 GitHub 仓库 655.3 万 Codex、Claude、Cursor 有
Azure AI 搜索 调用外部服务 GitHub 仓库 651.4 万 Codex、Claude、Cursor 有
Azure Cloud migrate 部署 调用外部服务 GitHub 仓库 650.7 万 Codex、Claude、Cursor 有

SKILL.md 全文

53.8 KB
模型实际读到的说明书原文,来自技能仓库

多台 Windows 用 Tailscale 组网 + SSH 互控与文件互投

> 版本 1.2.0 | 多台 Windows 长期互连实测产出:组网、判链路、通 SSH、传文件、定时任务,
> 五层各有一批反直觉结论。每一条「反直觉」都是踩出来的,别凭常识改。

> 本技能属于「多台 Windows 远程运维」系列,总纲与决策路由见 remote-win-fleet;系列通用的硬规则(判链路、远程执行、脚本交付、传输通道、钥匙策略)在总纲里统一定义。

这个技能解决什么问题

手上有三四台 Windows:一台放办公室长期开机、一台随身、一台在另一个办公点。
想让它们变成一个机群——彼此能 SSH 上去执行命令,能互丢文件,不需要任何人肉 U 盘。

这件事难的地方不在「软件怎么装」,而在每一步都有会骗你的现象:

  • 装满屏的 relay "sfo",你以为要排查服务,其实等几秒它就自己变 direct;
  • ping 100% 超时,你以为路由没生效,其实链路完全是好的——Windows 防火墙把 ICMP 丢了;
  • Permission denied 反复出现,你以为公钥贴错了,其实是用户名不对——这两种情况在 Windows 上返回一模一样;
  • scp 报一个看不懂的 pipe: Unknown error,你换个姿势再试,其实它正静静地什么都没传过去。

每一条的代价都是几小时。本技能按「组网 → 判链路 → 通 SSH → 传文件」四层把它们一次说清。

更新记录

v1.2.0
- 新增 1.7 找 exe 别只查默认安装位置:只查 C:\Program Files\Git\ 就断言「没装 Git」,
实际装在 C:\Users\<用户>\AppData\Local\Programs\GitFull\。2026-10-04 因此误判了一整轮。
- 新增 3.8 Git for Windows 自带的 ssh/scp 优先于 Windows 自带的:
同一台机器实测,GitFull\usr\bin\ssh.exe 连得上,C:\Windows\System32\OpenSSH\ssh.exe 连不上。
原因是 Win32 那份用命名管道(见 5.1),在 Tailscale 环境下握手成功率明显更低。
- 新增 3.9 计划任务里跑 SSH 拿不到 Tailscale 网卡:SYSTEM 身份会看到一串 @@@@。
分「有用户登录」和「无人值守」两种情况;无人值守那节进一步拆成两个正交的开关 ——
Tailscale 层(--unattended,管无人时在不在线)与任务层(LogonType,管无人时触不触发),
附「5 分钟一轮同步小文件」的模板与实测到的 Interactive 判读。
- 新增 3.10 私钥权限不一致会被 OpenSSH 拒绝:一把 (I)(F) 能用、另一把 (R) 被拒,
报 Permission denied (publickey)。用 icacls 统一。
- 新增 5.6 node 里 spawn ssh/scp 会报 EBUSY:文件锁,不是调用姿势问题。
同机 bash 调同样命令完全正常 —— 工具能跑就别硬套 node。
- 修 1.7 的失效交叉引用、1.5 的 metric 表述(0 vs 256 改成「隧道路由 metric 更低」并给出实测样例
5 / 291)、3.4 的 Server accepts key: 补冒号、5.2 补「等大小稳定」的具体阈值。
- allowed-tools 补上 PowerShell —— 正文大量用 icacls / where.exe / Test-NetConnection。
- 新增目录:按层分组列出全部小节号与「治什么」。
目录只写小节号、不做跳转链接 —— 各家渲染器的锚点算法不一样(. : → —— 怎么处理全无共识),
写死链接注定在某个平台上失效;小节号才是稳定的。读者用 Ctrl+F 搜 3.9 即可。
- 1.4 的 KeyExpiry 对照表去掉「值为 null」一行(Go 的 time.Time 不会序列化成 JSON null,
那行是推测而非实测),并注明这一点。

<details>
<summary><b>更早的版本(v1.1.0 / v1.0.0)</b></summary>

v1.1.0
- 新增 1.6 手机/平板入网:Google 账号 tailnet 在大陆必须走 auth key。
这是 2026-09-30 拿一台安卓平板实测出来的:login/controlplane 都通,只有 accounts.google.com 挂,
于是「Sign in with Google」和「扫二维码」两条路一起死,且症状完全一样(卡在登录页)。
根因不在客户端版本,在身份提供方。

v1.0.0
- 首版:四层作业线 + SendFile.ps1 工具 + 排错速查表。

</details>


目录

只想解决一个具体问题?用 Ctrl+F 搜小节号(如 3.9)直接跳,不必通读。

> 这里刻意不写跳转链接 —— 各家渲染器生成锚点的规则不一样(有的保留 1.1 里的点,
> 有的去掉,还有的会把 → —— 变成连字符),写死链接注定在某个平台上失效。
> 小节号是稳定的,锚点不是。

层 小节号与标题 治什么
1 组网 1.1 离线包必须下完整 MSI 离线机装不上
1.2 安装返回 1603 的根因 iphlpsvc 被禁
1.3 免交互入网用 auth key 免交互入网
1.4 key expiry 永久了没 是不是永久
1.5 子网路由:三条禁令 全网段黑洞
1.6 手机/平板入网 扫码登录卡死
1.7 找 exe 之前先 where 误判「没装」
2 判链路 2.1 tailscale ping 首次报中继 误判链路坏
2.2 判断通不通,看端口不看 ICMP ping 全超时
2.3 本机代理会把你骗得更惨 代理捣乱
2.4 顺手记一条 NoState 别急着重登
3 SSH 3.1 Windows 自带的 OpenSSH 只有客户端 背景节,见配套技能
3.2 Permission denied 是没法自己告诉你答案的 用户名还是公钥
3.3 生成密钥的引号坑 私钥被加密
3.4 「Server accepts key」却仍被拒 病根在客户端
3.5 公钥文件不能带 BOM 贴了不生效
3.6 多把钥匙是常态 试错白等超时
3.7 ssh.exe 的位置各台不一样 写死路径
3.8 Git 自带的 ssh/scp 优先 ssh 时通时不通
3.9 计划任务里跑 SSH 手动能连、计划任务不能(含无人值守方案)
3.10 私钥权限不一致 Permission denied
4 远程执行 4.1 中文路径的正解:-EncodedCommand 中文路径传不过去
4.2 路径带空格会被吃掉引号 引号被吃
4.3 含中文的 .ps1 要带 BOM 假解析错误
4.4 远端的 $env:TEMP 可能是空的 参数绑定异常
5 传文件 5.1 千万别信 Win32-OpenSSH 的 scp pipe Unknown error
5.2 Taildrop 官方通道
5.3 主机名在局域网里会骗你 投递一律用 IP
5.4 not in your Tailnet 发起方掉线
5.5 怎么选 三条路怎么挑
5.6 node 里 spawn ssh/scp 会报 EBUSY 定时任务改用 bash
工具与速查 SendFile.ps1 选机器选文件直接发
端到端验收清单 新机器进群先打勾
排错速查 34 条症状对照表

第 1 层 · 组网:把机器拉进同一个 tailnet

1.1 离线包必须下完整 MSI

https://pkgs.tailscale.com/stable/?mode=json

取 tailscale-setup-&lt;ver&gt;-amd64.msi(约 36 MB),安装过程不需要联网。

⚠️ 那个 1.4 MB 的 tailscale-setup.exe 是联网引导器,运行时才现拉 MSI。
精简系统、离线机、网络受限的内网机器上用它,结果就是静默失败。

1.2 安装返回 1603 的根因

msiexec 返回 1603,十有八九不是包坏了,而是 iphlpsvc(IP 帮助程序)被禁用
——优化软件常干这事,精简系统出厂就可能这样。

Set-Service iphlpsvc -StartupType Automatic
Start-Service iphlpsvc

救完再静默装,一次就过。这条要排在所有安装动作之前。

1.3 免交互入网用 auth key

后台生成 tskey-...(勾 Reusable,有效期 1–7 天),然后:

tailscale up --authkey=&lt;key&gt; --accept-routes=false

两点必须说清:

  • --accept-routes=false 是默认姿势。 不要一上来就接收别人广播的子网路由,原因见 1.5。
  • 入网成功之后,去后台 revoke 这个 auth key。它作为明文留在了那台机器的落地包里。

读 auth key 文件时注意:匹配要锚定行首(^\s*tskey-),
并且模板里的说明文字不要写出这个前缀——踩过一次:说明里写了「只认含 tskey- 的行」,
脚本取「第一个含 tskey- 的行」,于是读到了说明行自己。

1.4 key expiry 永久了没?别再去看年份

判据很反直觉:

tailscale status --json

看 Self 对象——没有 KeyExpiry 这个字段 = 永久有效。

字段消失,不是被设成 0001-01-01。拿着旧读数算天数会得出「快过期了」的错误结论。
长期开机的机群,这一步不做,半年后某天会全体掉线。

⚠️ 别把「字段不存在」和「字段存在但为空」搞混,这两个状态在 JSON 里长得很像:

Self 里的样子 含义
压根没有 KeyExpiry 这个键 永久有效 ✅
有这个键,值是 0001-01-01T00:00:00Z 这种零值 不是永久,去管理台核对
有这个键,值是一个未来的时间戳 真会过期,看还剩几天

判据要落在「键是否存在」上,而不是去比对值。

(Tailscale 的这个字段是 Go 的 time.Time,序列化时要么整个键消失、要么给一个具体时间戳,
不会输出 JSON null —— 所以只要你看到 null,那多半不是这个字段,别顺着它往下查。)

1.5 子网路由:三条禁令

想让 A 机访问 B 机所在的内网网段(比如在另一台机器上架了个服务),才需要这一节。
能用 Tailscale 地址解决的事,就不要碰子网路由。

禁令一:广播的网段必须是网关机自己所在的网段。
别照抄脚本里写死的网段。先在网关机上 ipconfig 确认真实内网地址,再
tailscale up --advertise-routes=&lt;真网段&gt;,后台 approve。

禁令二:广播的网段绝不能与接收方自身所在网段重叠。
这条是拿一次全网段黑洞换来的:接收方内网是 192.168.2.0/24,网关机广播了同一个段,
于是该段全部流量被塞进隧道交给网关机,而网关机根本不在这个段里 → 黑洞。
ping 网关从 3–12ms 变成全丢。

机制说明:Tailscale 写入的子网路由,会被系统赋予比本地 WLAN 更低的 metric,
于是优先生效。metric 的具体数值依版本与网卡而异 —— 实测见过
「Tailscale 隧道 5 / 本地 Wi-Fi 291」这种组合,别把某个数字当成固定值,
要判断谁优先就看 route print -4 里这两条路由的 metric 谁小。

route print -4

恢复了之后记住:任何需要路由的日子,--accept-routes=false 是保命值;
救回来靠的也是它:tailscale up --accept-routes=false 之后路由表只剩本地条目。

禁令三:广播之后别用 ping 验收。 见下一层。

1.6 手机/平板入网:Google 账号的 tailnet 在大陆只能走 auth key ❗

如果 tailnet 是拿 Google 账号建的(tailscale status 里看到 xxx@gmail.com),
那么在大陆网络下,安卓/iOS 客户端两条常规登录路都会死,而且死法一模一样:

登录方式 实际走的链路 大陆结果
Sign in with Google → accounts.google.com ❌ 打不开
Sign in with other(IdP 是 Google 时) → 同上 ❌
扫二维码(用另一台设备扫) 那台设备的浏览器 → login.tailscale.com → 仍跳 Google ❌

⚠️ 顺手排掉一个高频误判:Tailscale 有两种二维码,用户很容易混。

  • 下载码 —— 官网 tailscale.com/download/android 页面上那个,
    以及管理台 Machines → Add device → Android → Download Tailscale 重定向过去的同一个码。
    官方文案就是「Scan the QR code or use the link below to download」,
    扫开指向 Google Play / 下载页完全正常,和登录无关。
  • 登录码 —— 只有打开 App 本身、在登录页点 QR code 才出。

用户说「二维码指向谷歌商店」时,八成撞的是①。
但它对解决问题没有帮助——② 那条路在大陆本来也是死的(见上表)。
→ 只要看到用户在二维码上打转,就别陪着分析码指向哪,直接转到 auth key 这条路。

懒得读上面两段的话,看这张表就够:

码的类型 在哪扫到 扫开跳向 能解决登录问题吗
下载码 官网下载页 / 管理台 Add device Google Play、下载页 ❌ 与登录无关
登录码 App 打开后的登录页点 QR code 跳 Google 登录 ❌ 大陆仍走不通

两张码都救不了大陆的 Google 登录,只有 auth key 能。

2026-09-30 实测(本机直连、不走代理):

login.tailscale.com        -&gt; 302   ✅
controlplane.tailscale.com -&gt; 302   ✅
accounts.google.com        -&gt; 000 超时 10s  ❌   ← 只有它挂

关键结论:问题不在客户端版本、不在 APK 来源、不在 Android 版本。
换 APK、清缓存都没用——挂的是身份提供方那一步。

为什么电脑上还能用? 因为它早就登录过了:节点密钥默认 180 天有效期,
只要不掉线就一直续着,不需要再走一次 Google。
所以「电脑好好的、手机登不上」是最典型的症状,别据此怀疑客户端。

正解:auth key。这条链路不经过任何身份提供方。

安卓客户端(1.82 以后版本菜单一致):

打开 Tailscale
  → 若自动弹出浏览器要你登 Google:直接关掉(X / 返回),不要登
  → 右上角 ⚙ 设置 → Accounts
  → 右上角 ⋮(三个点)→ Use an auth key
  → 粘贴 tskey-auth-... → Add account
  → 回主页点 Connect

⚠️ 菜单里 Use an auth key 就挨着 Use an alternate server,
两者都在 Accounts 页的同一个 ⋮ 菜单下,别点错(后者是给自建 headscale 用的,填错会把你导到别的控制面)。

key 在管理台 https://login.tailscale.com/admin/settings/keys 生成:
勾 Reusable、有效期选 90 天,比一次性 key 省事(一次性用完就得再签,且失败一次就废了)。

排「到底卡在哪一步」的判据(拿移动端浏览器直接试,不用猜):

测试 结果 结论
打开 controlplane.tailscale.com 打不开 设备本身出不了网 → 换 DNS / 换 Wi-Fi / 关厂商"纯净模式"拦截
打开 controlplane.tailscale.com 打得开仍登不上 就是身份提供方(Google)的问题 → 上面 auth key
打开 accounts.google.com 打不开 实锤:Google 被墙,auth key 是唯一解

彻底绕开账号的备选:EasyTier 安卓版(只填「网络名 + 密码 + 公共节点」,无账号无登录),
代价是链路两端都要换成 EasyTier,见配套技能 easytier-win-mesh。

1.7 找 exe 之前先 where,别只查默认安装位置 ❗

这一条是 2026-10-04 拿一整轮无效排查换来的。

当时要判断「目标机器上有没有 bash」,只查了 C:\Program Files\Git\bin\bash.exe
和 C:\Program Files (x86)\Git\...,都不存在,就直接下了结论「没装 Git,bash 跑不了」。
实际上那台机器装了 Git for Windows,装在:

C:\Users\&lt;用户&gt;\AppData\Local\Programs\GitFull\bin\bash.exe
C:\Users\&lt;用户&gt;\AppData\Local\Programs\GitFull\usr\bin\ssh.exe
C:\Users\&lt;用户&gt;\AppData\Local\Programs\GitFull\usr\bin\scp.exe

再往前一步就已经跑通了。Git for Windows 装到用户目录是常见情况(免管理员权限的安装方式),
不是异常情况。

正确做法:先把「这个 exe 到底在哪」问出来,再谈有没有。

where.exe bash
where.exe git
where.exe ssh
where.exe scp

where 会把 PATH 里所有命中都列出来,一眼就能看出装在标准位置还是用户目录。

三条延伸:

  1. where 结果里出现多行是常态(Program Files 一个、System32 一个、用户目录一个),
    这时不能只取第一行 —— 要挨个实测哪个能用(见 3.8)。
  2. where 找不到的东西,可能在 %LOCALAPPDATA%\Programs 下(Git、Node、Python 都爱这么装),
    值得单独扫一眼。
  3. 想找某个 exe 属于哪个包,直接看同级目录:
    bin\ 放入口、usr\bin\ 放真正的可执行文件、mingw64\bin\ 放运行时。
    Git for Windows 的 bash.exe 在 bin\,ssh.exe/scp.exe 在 usr\bin\,两个别搞混。

教训:在 Windows 上断言「某工具不存在」之前,至少要跑过一次 where,
并且按第 1.7 节「三条延伸」的第 2 条确认过用户目录。凡是结论只有一条路径支撑的,都可能是误判。


第 2 层 · 判链路:别拿 ping 下结论

这是整个技能里最高频、最害人的一层。三条:

2.1 tailscale ping 首次报中继,别急着下结论

tailscale ping &lt;对方 100.x&gt;

第一次的输出经常是 via DERP(...)。这往往是打洞协商过程中的临时状态,
几秒后会自动升级成 direct。实测 6 对链路里有 3 对首次报中继,复查全部变直连。

→ 判定一对链路快不快,必须复查。 第一次的结果什么都不能证明。

2.2 判断通不通,看端口不看 ICMP

Windows 公用网络配置文件、AP 隔离策略、远端主机防火墙——任何一层都会静默丢 ICMP。
ping 100% 超时而链路完全正常的情形,是常态不是意外。

正确姿势:

Test-NetConnection &lt;目标&gt; -Port &lt;业务端口&gt;

再加一层 HTTP 交叉验证:

$env:http_proxy = $null          # 先清代理,见 2.3
Invoke-WebRequest http://&lt;目标&gt;:&lt;端口&gt;/ -UseBasicParsing

判据优先级:TCP 端口 OPEN > HTTP 200 > ping。 ping 只能排最后。

顺带一条:某台机器上跑的服务在 Tailscale 地址上访问不到、在内网地址上能访问,
多半不是「socket 绑死了具体 IP」,而是安装包配的防火墙入站规则带了 scope(只放行局域网段)。
补一条 -Profile Any、不带 -LocalAddress 的放行规则即可。别急着加 portproxy 转接,
那是多余的层。

2.3 本机代理会把你骗得更惨

本机若设了 http_proxy,.NET/curl 会自动走代理,结果是
「不含点的主机名」自动 bypass 代理能通,而 IP 形式的地址全部超时
——看起来完全像「路由没生效、DNS 没配好」。

→ 凡是要用 HTTP 判定链路,先 $env:http_proxy=$null,并用 TCP 层交叉验证。

2.4 顺手记一条

Tailscale 掉到 NoState 时别急着重登。托盘里的 tailscale-ipn 多半本来就在跑
(再启动会因 UI mutex 已存在而退出),是服务侧在重连,等一会自己会好;
而且 CLI 的 tailscale up 这时也拉不起来。先 tailscale status 确认,再决定要不要动。


第 3 层 · 通 SSH:装机与钥匙分发

3.1 Windows 自带的 OpenSSH 只有客户端

C:\Windows\System32\OpenSSH\ 里通常没有 sshd.exe,不能就地「反正有 OpenSSH 那就改改配置」。
必须单独补服务端。精简 / Ghost 版系统尤其麻烦,Add-WindowsCapability 装出来的往往是残缺品
(能力显示 Installed,运行时报入口点错误)。这一步的完整做法见配套技能 remote-win-ssh-offline。

3.2 Permission denied 是没法自己告诉你答案的 ❗

Windows OpenSSH 里,下面两种情况返回完全相同的信息:

Permission denied (publickey,password,keyboard-interactive).
  • 用户名根本不存在
  • 用户名对,但公钥不匹配

无法据此判断用户名对错。

→ 必须先去目标机拿真实账户列表:Get-LocalUser,或 net user,或者直接看 C:\Users 下的目录名。
拿到之后再逐个试。这一步省掉,后面所有排查都是瞎猜。

另外留意:同一台机器上可能有多个账户,跑 Tailscale 托盘的是 A 账户,而 SSH 登录的是 B 账户。
这会直接影响第 5 层的文件落点。

3.3 生成密钥的引号坑(真踩过)

想在 bash 里表达「空密码」:

ssh-keygen -N &#x27;&#x27;      # ✅ 正确
ssh-keygen -N &#x27;&quot;&quot;&#x27;    # ❌ 两个引号字符被当成字面密码

第二种写法会生成一个被 aes256-ctr + bcrypt 加密的私钥,后面各种奇怪的认证失败由此而来。

生成后务必自查:

ssh-keygen -y -P &#x27;&#x27; -f &lt;key&gt;     # 能输出公钥才算真的没密码

3.4 「Server accepts key」却仍被拒 → 病根在客户端

ssh -vvv 里只要看到 Server accepts key:(带冒号,服务端回了 PK_OK),
就说明服务端的 authorized_keys 已经配对成功了。此时还报 Permission denied,
问题是客户端这把私钥读不出来(被密码加密了、权限不对、格式不对)。

→ 别再去折腾服务端的公钥文件。

3.5 公钥文件不能带 BOM

.pub 文件一旦带 UTF-8 BOM,authorized_keys 里那一行的首字符会变成不可见字符,整条公钥直接失效,
而且不会有任何明确报错。

3.6 多把钥匙是常态,别指望一把通吃

机器多了以后,各台授权的公钥往往不统一。别试图「统一一下」,直接接受现实:

  • 把所有候选私钥都装到每台机器上
  • 连接时依次试,哪把成功就用哪把
  • 成功后写进缓存(如 %TEMP%\..._keycache.txt),下次先试对的

为什么值得做缓存:试错一把不对的钥匙,代价是白白等一个连接超时,实测能拖到 15 秒以上。

跨地点 SSH 握手偶发很慢(实测过同刻网络正常、单次超过 8 秒而重试只要 0.7 秒),
所以 ConnectTimeout 别设太小(建议 20–25 秒),并且整轮失败后再试一次,
免得把在线的机器误判成离线。

3.7 ssh.exe 的位置各台不一样

离线包装进 C:\Program Files\OpenSSH,Windows 可选功能才是 C:\Windows\System32\OpenSSH。
挨个探测,写死必挂。 先跑 where ssh(见 1.7)拿到全量,再按 3.8 决定用哪一份。

3.8 Git 自带的 ssh/scp 优先于 Windows 自带的那份 ❗

2026-10-04 同一台机器上的实测对比(连同一台对端、同一把私钥,只换 ssh.exe):

ssh.exe 来源 结果
C:\Users\&lt;用户&gt;\AppData\Local\Programs\GitFull\usr\bin\ssh.exe ✅ 成功,返回对端主机名
C:\Windows\System32\OpenSSH\ssh.exe ❌ status=null(握手失败/超时)

两个原因叠加:

  1. Win32 自带那份用命名管道实现 pipe(),会 ERROR_PIPE_BUSY(见 5.1),
    在 Tailscale 环境下握手成功率明显更低;
  2. C:\Program Files\OpenSSH\(离线包装的那份)也可能不可用 —— 它和 System32 那份是同一套实现。

所以候选顺序应该是:

1) Git for Windows 的 usr\bin\ssh.exe      ← 最稳,优先
2) Git for Windows 的 usr\bin\scp.exe
3) C:\Program Files\OpenSSH\ssh.exe
4) C:\Windows\System32\OpenSSH\ssh.exe
5) PATH 里兜底

探测时不要只看「文件存在」,要挨个真连一次:

foreach ($p in $cands) {
    if (Test-Path $p) {
        $r = &amp; $p -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=8 `
                  -i $key &quot;$user@$ip&quot; hostname 2&gt;$null
        if ($LASTEXITCODE -eq 0 -and $r) { &quot;可用: $p  -&gt; $r&quot;; break }
        else { &quot;不可用: $p&quot; }
    }
}

Git for Windows 的目录结构别搞混(见 1.7.3):

GitFull\bin\bash.exe        ← 入口
GitFull\usr\bin\ssh.exe     ← 真正的 ssh
GitFull\usr\bin\scp.exe     ← 真正的 scp

3.9 计划任务里跑 SSH:拿不到 Tailscale 网卡 ❗

想让某台机器定时连其他机器(同步文件、跑巡检),把它做成计划任务时会撞上这堵墙。

先分清两种情况——「有人在用这台机器」和「没人用」,解法完全不同。

情况 A:有用户保持登录(最省事)

运行身份 症状
SYSTEM(/ru SYSTEM) ssh 回显一长串 @@@@,随后超时;schtasks 报「找不到系统文件」
当前用户(/ru 用户) ✅ 正常
不写 /ru(默认当前用户上下文) ✅ 正常

根因:Tailscale 默认跑在交互式登录会话里。非交互式上下文(SYSTEM、计划任务、
某些 SSH 会话)拿不到那个虚拟网卡,于是包发不出去。

写法:干脆不写 /ru。

schtasks /create /tn SyncTask ^
  /tr &quot;\&quot;C:\Users\&lt;用户&gt;\AppData\Local\Programs\GitFull\bin\bash.exe\&quot; \&quot;D:\job\sync.sh\&quot;&quot; ^
  /sc minute /mo 5 /f
  • 不带 /ru → 任务在当前用户上下文里跑,能用上 Tailscale
  • /sc minute /mo 5 → 每 5 分钟一轮,跑完即退,
    空闲时 CPU 0%、内存 0 MB(这是「定时同步」最省资源的做法,
    比常驻守护进程省一个数量级)
  • 不要用 /rl HIGHEST,普通权限即可,反而更不容易出问题
  • 脚本路径含中文时,schtasks 的引号处理容易出错,
    最稳的做法是让 action 指向一个纯 ASCII 的 .bat 入口,由它去调 bash

⚠️ 这一招有前提:那个用户处于登录状态。
机器重启后没人登录、或会话被注销,「当前用户上下文」就不存在了,任务要么不跑,
要么跑在一个同样拿不到网卡的会话里。要覆盖这种场景,看下面的情况 B。

情况 B:需要无人值守(没人登录也要跑)

⚠️ 这里有两个互相独立的开关,别混成一个方案。

层 管什么 由谁决定
Tailscale 层 无人登录时 Tailscale 在不在线 tailscale up --unattended=true
任务层 无人登录时计划任务触不触发 任务的 LogonType(/ru 决定)

--unattended 只管上面那层。任务不写 /ru 的话,LogonType 默认是 Interactive,
用户一注销,任务压根不会被触发 —— 不是「触发了但拿不到网卡」,而是「根本不跑」。

实测(本机 Get-ScheduledTask,未写 /ru 建的任务):

Name      : WorkbenchAddressSync
LogonType : Interactive      ← 仅在用户已登录时运行
UserId    : YOGA3PRO

所以无人值守要两层都配齐:

Tailscale 层 任务层 无人时 Tailscale 在线? 无人时任务触发? 备注
未开 unattended /ru SYSTEM ❌ 网卡不存在 ✅ 情况 A 那个 @@@@ 就是它,实打实踩过
未开 unattended /ru 用户名 /rp 密码 ?推理 ✅ 密码进凭据管理器
开 unattended /ru SYSTEM ?推理 ✅ 推荐方向
开 unattended 任务不写 /ru(Interactive) ✅ ❌ 不触发 实测 LogonType 为 Interactive
开 unattended /ru 用户名 /rp 密码 ?推理 ✅ 也能用,但密码仍要交出去

⚠️ 只有第 1 行的 ❌ 和第 4 行的「不触发」是实测/已确认的,
带「?推理」的三行都没在本机复现。

三行的不确定性不是同一个量级,别并列看。 决定「无人时在线」的有两个变量:

  • 变量 ①:Tailscale 服务会不会随 GUI 用户登出下线 → unattended 管这个
  • 变量 ②:网卡在「非交互式会话」这个身份上下文里出不出现 → unattended 管不到

对着这两个变量看:

行 变量 ① 服务在线 变量 ② 网卡可见 整体把握
第 1 行(未开 unattended + /ru SYSTEM) ❌ 确定不通(实测 @@@@) ❌ 不必验 不可行
第 2 行(未开 unattended + /ru 用户名) ⚠️ 大概率不通(见下) ?不确定 基本没戏
第 3 行(开 unattended + /ru SYSTEM) ✅ 有保证 ?不确定 只剩一个未知量
第 5 行(开 unattended + /ru 用户名) ✅ 有保证 ?不确定 只剩一个未知量

第 2 行为什么是「大概率不通」而不是「确定不通」:

--unattended 的官方说明是 "keeps running even after the current GUI user logs out"
—— 反过来读,默认状态就是随 GUI 登出下线。所以没人登录时 Tailscale 服务本身可能已经不在了。

唯一可能翻盘的情形:/ru 用户名 /rp 密码 跑起来后,Windows 建了一个「准交互式会话」,
而 Tailscale 内部把它算作「GUI 用户在线」。没实测过,不好一口咬死。
要排除这个可能性,去下面「要自己验,用行为验证」那四步实测一次。

所以第 2 行 ≈ 第 1 行,别选。 区别只在于第 2 行留了个理论上的翻盘口子。

第 5 行明显优于第 2 行 —— unattended 的作用正是让服务不随 GUI 登出下线,
开了它就把变量 ① 消掉了(从「大概率不通」变成「有保证」)。
第 3 行和第 5 行把握相当,区别只在要不要把密码交出去(见下面的取舍)。

要自己验,用行为验证(这是唯一可靠的判据):

  1. 退出 Tailscale 托盘:托盘图标右键 → Exit
    (只退 GUI 进程 tailscale-ipn.exe,不要停服务 ——
    服务还活着正是要测的东西)
  2. 让任务以目标身份跑一次(/ru SYSTEM 就触发 SYSTEM 任务,/ru 用户名 就触发那个任务)
  3. 从另一台机器 tailscale ping &lt;该机 IP&gt;
  4. 还 pong = 这一行的「在线」成立;不通 = 不成立

第 1 行的 ❌ 不用验,情况 A 那个 @@@@ 就是它。

推荐做法:

tailscale up --unattended=true
schtasks /create /tn SyncTask /ru SYSTEM ^
  /tr &quot;\&quot;C:\Program Files\Git\bin\bash.exe\&quot; \&quot;D:\job\sync.sh\&quot;&quot; ^
  /sc minute /mo 5 /f

⚠️ bash 路径要用 Git 的,不是 C:\Windows\System32\OpenSSH\bash.exe ——
后者根本不存在:那个目录是 Win32-OpenSSH 的实现目录,只有
ssh.exe / scp.exe / sftp.exe / sshd.exe。bash 在 Git 里(见 3.8)。

⚠️ 情况 A 和情况 B 的路径前提不同,别照抄。

情况 A(不写 /ru) 情况 B(/ru SYSTEM)
跑在谁上下文 当前用户 SYSTEM
读用户目录里的 AppData ✅ 读自己的,没问题 ⚠️ 取决于那个目录的 ACL

实测(2026-10-04,一台 Windows 10 22H2,Git 装在用户目录 AppData\Local\Programs\GitFull):

&gt; whoami
nt authority\system
&gt; &amp; &quot;C:\Users\&lt;用户名&gt;\AppData\Local\Programs\GitFull\bin\bash.exe&quot; -c &quot;echo SYSTEM_BASH_OK&quot;
SYSTEM_BASH_OK

同一次测试里查到的 ACL:

&gt; icacls &quot;C:\Users\&lt;用户名&gt;\AppData\Local\Programs\GitFull&quot;
  NT AUTHORITY\SYSTEM     FullControl  Allow
  BUILTIN\Administrators  FullControl  Allow

但这不能当通例 —— ACL 收紧过的机器上可能读不到。两个做法:

:: 做法一(推荐):把 Git 装成系统级,路径固定,谁都能跑
::   https://git-scm.com/download/win  选 &quot;System Level Git&quot;
:: 做法二:先用 icacls 确认 SYSTEM 有读权限
icacls &quot;C:\Users\&lt;用户&gt;\AppData\Local\Programs\GitFull&quot; | findstr SYSTEM

看不到 SYSTEM 且带 (F) 就别用这条路,换成 /ru 用户名 /rp 密码。

  • --unattended 的官方说明是:"run in Unattended Mode where Tailscale keeps running
    even after the current GUI user logs out (Windows-only)"
    ,默认 false,
    不开这一步 Tailscale 就随 GUI 登出一起下线 —— 那正是情况 A 里 SYSTEM 看不到网卡的原因。
  • 任务侧用 /ru SYSTEM:LogonType 变成 ServiceAccount,不依赖任何用户登录,
    也不用把密码交给计划任务。
  • 前提:tailnet 已经在这台机器上登录过一次(有有效节点密钥)。
    --unattended 只改运行身份,不能替代首次登录。
  • 也可以在 GUI 里开:托盘图标右键 → Preferences → 勾选 Run unattended。

怎么确认 unattended 真的开着(tailscale status 不显示这个状态,别对着输出发愣):

tailscale debug prefs

(SYSTEM 上下文或 PATH 没它时用全路径 &amp; &quot;C:\Program Files\Tailscale\tailscale.exe&quot; debug prefs。)

看输出里有没有 Unattended 字段。⚠️ 部分版本(如 1.102.4)这个字段压根不输出 ——
那就没法从命令行查,唯一的硬证据是行为验证:

  1. 托盘图标右键 → Exit(只退 GUI 进程 tailscale-ipn.exe;
    不要停服务 —— 服务 tailscaled 还活着正是要测的东西)
  2. 在另一台机器上 tailscale ping &lt;该机 IP&gt;
  3. 还 pong = unattended 生效;不通 = 没生效

⚠️ Windows 11 24H2 上有额外一层(社区实测,非官方文档):
整个「完整网络初始化」被绑定到了交互式登录,即使 tailscaled 服务以 SYSTEM 启动、
虚拟网卡存在,未登录阶段 Tailscale 仍可能上不了线。
变通办法是开自动登录(netplwiz),开机自动登录一次把网络栈拉起来,之后再锁屏。

取舍:
- 自动登录省事但安全性降一档(开机后直接进桌面)。
- /ru 用户名 /rp 密码 避开自动登录,但有两个坑:
① 该用户改密码后任务即失败(报「登录失败:未知用户名或密码错误」);
② 企业域环境常开 网络访问: 不允许存储网络身份验证的密码和凭据 这条安全策略,
开了就此路不通,只能回到 /ru SYSTEM + unattended。
- 如果这台机器本来就有人守着,用情况 A 就够,不必上这套。

配套的三条省心技巧(做定时同步时一并用上)

  1. 先比对哈希,相同就跳过 —— 稳定态零传输,占用降到几乎为零
  2. 离线机标记 PENDING 后立刻跳过,不要重试等待;上线后下一轮自动补
  3. 状态存文件(如 sync-state.txt),只记「上次推送成功时的源文件哈希」,
    下一轮拿它和当前源文件比

3.10 私钥权限不一致,会被 OpenSSH 直接拒掉 ❗

症状:同一台机器上,一把私钥能连上、另一把报
Permission denied (publickey,password,keyboard-interactive)。

根因:OpenSSH 客户端会检查私钥文件的 ACL,权限过松或带继承标记就拒绝使用。
最容易翻车的是「两把钥匙的权限不一样」—— 一把是继承来的、另一条是手工设过的。

先看清现状:

icacls C:\Users\&lt;用户名&gt;\.ssh\&lt;私钥文件名&gt;

输出里看三样东西:

  • 有没有 (I) —— 继承标记,有就会被拒
  • 本人权限是 (F) 还是 (R) —— 不统一就出事
  • 有没有多余的主体(Users、Everyone 等)—— 有也要去掉

统一成「只有三个主体、全部完全控制」:

icacls &lt;私钥路径&gt; /inheritance:r
icacls &lt;私钥路径&gt; /grant:r &quot;SYSTEM:(F)&quot; &quot;Administrators:(F)&quot; &quot;&lt;用户名&gt;:(F)&quot;
icacls C:\Users\&lt;用户&gt;\.ssh /inheritance:r
icacls C:\Users\&lt;用户&gt;\.ssh /grant:r &quot;SYSTEM:(OI)(CI)(F)&quot; &quot;Administrators:(OI)(CI)(F)&quot; &quot;&lt;用户名&gt;:(OI)(CI)(F)&quot;

⚠️ 目录也要改 —— 只改文件不改目录,OpenSSH 仍会挑出继承来的那一层。

⚠️ 这条操作需要管理员权限,非管理员会静默失败
(EPERM: operation not permitted / Permission denied),改完记得回读 icacls 确认真的生效了。


第 4 层 · 远程执行:中文、空格与环境变量

4.1 中文路径的正解:-EncodedCommand

要把「含中文路径」的命令送到对面执行,唯一稳的写法:

powershell -NonInteractive -NoProfile -ExecutionPolicy Bypass -EncodedCommand &lt;base64&gt;

其中 payload 是 [Text.Encoding]::Unicode.GetBytes($code) 再 base64
(注意是 UTF-16LE,不是 UTF-8)。

好处是整条命令行全是 ASCII,彻底绕开 cmd 的编码问题,
也绕开了管道符被误切分(此前 |Out-Null 被 cmd 当成外部命令,报「'Out-Null' 不是内部或外部命令」)。

回传也一样:远端把文件名 base64 后打印,本机解码,避免本机按 GBK 解码成乱码。

4.2 路径带空格会被吃掉引号

命令行传 C:\Program Files\... 到对面,会出现
&#x27;C:\Program&#x27; is not recognized 这类错误——引号在转发过程中丢了。

→ 用 8.3 短路径:C:\Progra~1\。

4.3 含中文的 .ps1 必须 UTF-8 带 BOM

PowerShell 5.1 下,无 BOM 的 UTF-8 脚本里的中文字面量会变成乱码。
最典型的症状:某个中文文件名的判断永远为 False,还以为是逻辑错。

4.4 远端的 $env:TEMP 可能是空的

通过 SSH 拉起的会话里,$env:TEMP 有时为空,
于是 Test-Path -LiteralPath $null 直接抛参数绑定异常。

→ 远端脚本一律写死绝对路径,不要用 $env:TEMP 拼。


第 5 层 · 传文件:三条路怎么选

5.1 千万别信 Win32-OpenSSH 的 scp / sftp

症状:

pipe: Unknown error        (exit 255)

看着像在传,其实静默失败——文件没过去,命令还可能返回 0。
scp -vvv 能给出决定性日志:

debug3: pipe - ERROR CreateFile() :231   →  231 = ERROR_PIPE_BUSY

Win32-OpenSSH 在 Windows 上用命名管道实现 pipe(),这是系统级失败,不是调用姿势问题。
同一个版本的 ssh 往往是好的(它对这个错误容错继续),所以「ssh 能连」不能证明 scp 能用。

自检方法:让 scp 连一个不存在的地址,比如 192.0.2.1。
正常应该走到 Connection timed out(说明 pipe 创建成功);坏的则根本到不了这一步。

→ 换 MSYS2 版的 scp / sftp(Git for Windows 自带,在 Git\usr\bin\ 下),立刻好。
(Windows 自带 sc.exe 那类同名命令会干扰,shim 时注意 PATH 顺序:生效 PATH 是系统 PATH 在前、用户 PATH 在后。)

5.2 Taildrop:慢活多路里最稳的一条

tailscale file cp 走的是官方通道,不依赖 ssh/scp,不需要开防火墙、不需要管理员权限。
代价是:

  • 异步——投递命令返回 ≠ 对方已收到。要轮询等文件出现、且等大小稳定再动它,否则搬半截。
    「稳定」给个可抄的阈值:文件大小 > 0,且连续 3 次轮询(间隔 1 秒)大小都没变。
    大文件(10 MB 以上)把间隔放宽到 3 秒,但判据是「连续 3 次不变」,不是「等够几秒」。
  • 文件落在对方 Downloads,且 Tailscale 跑在哪个账户下就落哪个账户的 Downloads。
  • 同名文件会被加后缀。

5.3 主机名在局域网里会骗你 ❗

两台机器同处一个局域网时,主机名会被解析成 IPv6 链路本地地址 fe80::…%WLAN
而不是 Tailscale 的 100.x 地址,于是 tailscale file cp &lt;主机名&gt;: 报
is not a Tailscale IP address。

→ 投递一律写 100.x IP,不用主机名。

5.4 not in your Tailnet 多半是你自己的问题

tailscale file cp 报 unknown target; not in your Tailnet,
第一反应通常是「对方掉线了」,实际上多半是发起方自己处于 NoState(见 2.4)。

→ 先查自己的 tailscale status,别去折腾目标机。

5.5 怎么选

场景 用什么
人在本机,要丢个文件给对方、不指定目录 Taildrop(最快最省心)
要落到对方指定目录(含中文) Taildrop 投递 + 一条 -EncodedCommand 让对方自己搬
大批量 / 要续传 / 要回读校验 MSYS2 的 sftp,见配套技能 remote-win-backup-mesh
同在一个局域网 直接用局域网 IP 走 SSH,能绕开「仅放行 100.64.0.0/10」的防火墙规则
做定时任务(同步、巡检) bash 脚本 + 计划任务,见 3.9 与 5.6

5.6 node 里 spawn ssh/scp 会报 EBUSY —— 别硬套 node ❗

2026-10-04 实测:写了个 Node 脚本调 ssh/scp/taskkill,
全部返回 spawnSync &lt;路径&gt; EBUSY(有时是 exit=null,症状一样),
而同一台机器上用 bash 调完全相同的命令,一切正常。

这不是调用姿势问题,是文件锁。 排查时试过的、都无效的「解法」:

  • 换绝对路径(System32 → Program Files → Git)—— 照样 EBUSY
  • 清空环境变量、spawnSync 改成 execSync、加 windowsHide —— 照旧
  • 在 bash 里用 node -e 包一层再 spawn —— 也 EBUSY
  • 改成 powershell -File 调脚本 —— 仍 EBUSY

最终有效的做法:直接用 bash 写。 Git for Windows 自带 bash(见 1.7),
它的 ssh/scp 走的是 MSYS2 那一套,没有这个锁问题。

#!/bin/bash
# 别在 node 里 spawn ssh,用 bash 直接跑
GF=&quot;C:/Users/$USER/AppData/Local/Programs/GitFull&quot;
SSH_BIN=&quot;$GF/usr/bin/ssh.exe&quot;
SCP_BIN=&quot;$GF/usr/bin/scp.exe&quot;
SSH_OPTS=&quot;-o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=NUL -o ConnectTimeout=25 -o LogLevel=ERROR&quot;
# UserKnownHostsFile=NUL:Windows 设备名,意思是「不记录 known_hosts」。
#   在 Git Bash(MSYS2)下实测过 NUL 和 /dev/null 都能连上,但 **NUL 更干净** ——
#   /dev/null 那次多打一行 &quot;Warning: Permanently added ... to the list of known hosts&quot;。
#   纯 Windows 环境(cmd/PowerShell 里的 ssh.exe)就只能用 NUL。

&quot;$SSH_BIN&quot; $SSH_OPTS -i &quot;$KEY&quot; &quot;$USER@$IP&quot; hostname &gt;/dev/null 2&gt;&amp;1 \
  &amp;&amp; echo &quot;在线&quot; || echo &quot;离线&quot;

&quot;$SCP_BIN&quot; $SSH_OPTS -i &quot;$KEY&quot; &quot;$SRC&quot; &quot;$USER@$IP:$DEST&quot;

判断该用哪个工具链:

你要做的事 用什么
单机运维、几条命令、想有结构化输出 node(这台机上 spawn 别的 exe 没 EBUSY 的话)
批量调用 ssh/scp、写计划任务 bash(Git 自带)
需要复杂判断 + 调 ssh PowerShell + -EncodedCommand(见 4.1)

原则:工具能跑就别硬套另一个。 在 Windows 上,
「bash 调 ssh」和「node 调 ssh」不是一回事,
后者多一层进程、多一把锁、多一个失败点,而收益是零。


工具:SendFile.ps1

scripts/SendFile.ps1(配 scripts/SendFile.bat 双击用)把上面第 5 层全部结论固化成了一个命令。

.\SendFile.ps1                                  # 交互:选机器 → 选文件(可多选)→ 发送
.\SendFile.ps1 -List                            # 只看机器清单
.\SendFile.ps1 -To &lt;别名&gt; -Path &#x27;.\报告.xlsx&#x27;      # 发到对方 Downloads
.\SendFile.ps1 -To &lt;别名&gt; -Path &#x27;.\地图.xmind&#x27; -Dest &#x27;C:\Users\&lt;账号&gt;\Desktop\某中文目录&#x27;
.\SendFile.ps1 -To all -Path &#x27;.\a.zip&#x27;,&#x27;.\b.pdf&#x27; -Dest &#x27;D:\项目资料&#x27;
.\SendFile.ps1 -To &lt;别名&gt; -Path &#x27;.\大文件.zip&#x27; -Dest &#x27;D:\资料&#x27; -WaitSec 90

它替你处理掉的事:

  1. 只用 IP,不用主机名(躲开 5.3)
  2. 发送前自检本机 Tailscale 是否 NoState(躲开 5.4)
  3. 中文目录名 / 文件名一律 base64 后下发,远端 -EncodedCommand 执行(躲开 4.1)
  4. 远端轮询等文件出现且大小稳定再搬(躲开 5.2 的半截问题)
  5. 回执里的文件名同样 base64 回传(躲开 GBK 乱码)
  6. 多把私钥依次试 + 成功即缓存(躲开 3.6 的白等超时)
  7. ssh.exe 位置挨个探测(躲开 3.7)
  8. 远端扫描 C:\Users\*\Downloads 而不是只认 $env:USERPROFILE
    ——因为「跑托盘的账户」和「SSH 登录的账户」不一定是同一个(躲开 3.2 末尾那条)
  9. 有重名文件时自动改成逐个发送(Downloads 是平铺目录,同名会互相覆盖)
  10. 判断投递失败不用 Out-String 匹配 error——PowerShell 会把 stderr 渲染成 NativeCommandError,
    一条无害警告就能把你误判成失败。它逐条取 ErrorRecord.Exception.Message,并剔除 is not replying 那句提示。

改机器表:脚本顶部 $Machines 数组,四列分别是别名 / SSH 账户 / 显示名 / IP。改成你自己的即可。


端到端验收清单

新拉一台机器进群,按顺序打完勾:

  • [ ] Tailscale 装上了,tailscale status 能拿到 100.x
  • [ ] tailscale status --json 里 Self 没有 KeyExpiry 字段(永久)
  • [ ] tailscale up --accept-routes=false(没有子网路由需求就一直保持)
  • [ ] 入网用的 auth key 已在后台 revoke
  • [ ] where ssh 至少两条候选,且已知该用哪一份(Git 自带的优先,见 3.8)
  • [ ] icacls &lt;私钥&gt; 看过:没有 (I) 继承标记,主体只有三个(见 3.10)
  • [ ] ssh 能连上——用户名是去目标机 Get-LocalUser 查出来的,不是猜的
  • [ ] ssh-keygen -y -P &#x27;&#x27; -f &lt;key&gt; 能输出公钥(私钥没被密码加密)
  • [ ] tailscale ping &lt;对方&gt; 复查一次,是 direct 不是 derp
  • [ ] Test-NetConnection &lt;对方&gt; -Port 22 是 OPEN(不是用 ping 判的)
  • [ ] .\SendFile.ps1 -To &lt;对方&gt; -Path &lt;一个小文件&gt; 成功收到
  • [ ] 若要做定时任务:手动触发一次验证过(见 3.9)
  • [ ] 若要做无人值守 · Tailscale 层:已开 --unattended
    (tailscale debug prefs 看 Unattended;tailscale status 不显示这个状态。
    字段压根不输出的版本,用「退出托盘后从另一台 ping 还通不通」做行为验证)(3.9)
  • [ ] 若要做无人值守 · 任务层:用 /ru SYSTEM 或 /ru 用户名 /rp 密码,
    不是不写 /ru(不写就是 Interactive,注销后不触发)(3.9)

排错速查

现象 真正的原因 怎么办
装 Tailscale 返回 1603 iphlpsvc 被禁 起服务后再装(1.2)
tailscale-setup.exe 装不动 它是联网引导器 下完整 MSI(1.1)
tailscale ping 报 via DERP 打洞协商临时态 复查(2.1)
ping 全丢但服务能开 ICMP 被 DHCP/防火墙/AP 隔离丢 用 -Port 判(2.2)
主机名通、IP 不通 本机 HTTP 代理 清 http_proxy(2.3)
「这机器没装 Git / 没 bash」 只查了 C:\Program Files\Git\,实际装在用户目录 先跑 where(1.7)
where 出来好几行,不确定用哪个 多份实现(Git 的 / OpenSSH 的) 挨个真连一次,能连上的那个(3.8)
同一台机器上 ssh 时通时不通 换成了 Windows 自带的 ssh.exe 用 Git 自带那份(3.8)
计划任务里 SSH 回显一串 @@@@ SYSTEM 身份拿不到 Tailscale 网卡 不写 /ru(3.9 情况 A)
计划任务报「找不到系统文件」 同上,PATH 里也没有 ssh 不写 /ru + 脚本里用绝对路径(3.9)
手动能连、计划任务不行 同上 同上(3.9 情况 A)
机器重启后没人登录,计划任务就不跑了 任务 LogonType 是 Interactive(不写 /ru 的默认值),注销后压根不触发 任务改 /ru SYSTEM 或 /ru 用户名 /rp 密码;无人时还要 Tailscale 在线得另开 --unattended(3.9 情况 B,两个开关要配齐)
想查 unattended 开了没,tailscale status 里找不到 status 本来就不显示这个字段 tailscale debug prefs 看 Unattended;该版本不输出的话,用「退出托盘后从别的机器 ping」做行为验证(3.9)
Permission denied (publickey...) 用户名不存在 或 公钥不匹配,返回一样 Get-LocalUser 查真实账户(3.2)
一把私钥能连、另一把 Permission denied 私钥 ACL 不一致(有 (I) 继承标记或权限不一致) icacls 统一(3.10)
Server accepts key 仍被拒 客户端私钥被加密/读不了 查 -N &#x27;&#x27;(3.3 / 3.4)
公钥贴了没生效 .pub 带 BOM 去掉 BOM(3.5)
scp pipe: Unknown error Win32-OpenSSH 的 named pipe 缺陷 换 MSYS2/Git 版(5.1 / 3.8)
node 里 spawn ssh/scp 报 EBUSY 文件锁,node 这条路在这台机上走不通 改用 bash(5.6)
spawnSync ... exit=null 同上(EBUSY 的另一种表现) 同上(5.6)
is not a Tailscale IP address 主机名被解析成 fe80:: 用 IP(5.3)
用短主机名(ssh config 里的别名)报 ENOTFOUND 那是你本机 ssh config 里的别名,MagicDNS 不认 用全名或 100.x IP(5.3)
把「连不上」当成「机器离线」 短名解析失败 ≠ 机器不在线 先 tailscale ping &lt;IP&gt; 确认(2.2 / 5.3)
not in your Tailnet 发起方自己 NoState 查自己 status(5.4)
远端 &#x27;C:\Program&#x27; not recognized 空格路径引号被吃 8.3 短路径(4.2)
远端 &#x27;Out-Null&#x27; 不是内部或外部命令 管道符被 cmd 切了 -EncodedCommand(4.1)
远端含中文脚本报假的解析错误 无 BOM 的 UTF-8 加 BOM(4.3)
Test-Path 参数绑定异常 远端 $env:TEMP 为空 写死绝对路径(4.4)
电脑能上、手机/平板卡在登录页;换 APK、扫码都不行 tailnet 绑的是 Google 账号,accounts.google.com 在大陆打不开 客户端改用 auth key(1.6)
平板浏览器打不开 controlplane.tailscale.com 设备本身出不了网 换 DNS / Wi-Fi / 关厂商拦截(1.6)
传完是 0 字节 / 半截 Taildrop 异步,没等稳 轮询等到大小稳定(5.2)
文件传到对方却找不到 托盘账户 ≠ SSH 登录账户 扫 C:\Users\*\Downloads
本地网络全断 子网路由与自己网段重叠,隧道路由 metric 更低造成黑洞 --accept-routes=false(1.5)
ssh 能连但 scp 传不过去 又是 Win32 那份管道缺陷 换 Git 自带的(3.8 / 5.1)

边界声明

不做:跨平台的 Linux/macOS 节点;Tailscale 之外其它组网方案的配置(WireGuard 原生、ZeroTier 等);
ACL / 多用户权限治理;替代 remote-win-ssh-offline(补 SSH 服务端)与
remote-win-backup-mesh(大批量带校验搬运)——那两个是配套的上下游。
也不替用户决定「哪台机器该广播哪个网段」,这件事必须由人先确认真实内网地址再动。

资源

  • scripts/SendFile.ps1 —— 四机互传工具,改顶部机器表即用;支持指定中文目标目录
  • scripts/SendFile.bat —— 双击入口(本地自用;对外发布时服务端不收 .bat,走上面那个 ps1 即可)

常见问题

5 问
答案由本站依据条目字段整理
多台 Windows 用 Tailscale 组网 + SSH 互控与文件互投怎么安装?

在支持 Skills 的客户端里执行:skillhub install user_878e6d7a/tailscale-win-mesh。安装方式为未标注。装完刷新技能列表或重开客户端。

多台 Windows 用 Tailscale 组网 + SSH 互控与文件互投支持哪些客户端?

资料没有标注宿主客户端,但只要客户端支持 Skills(Claude、Codex、Cursor 等),一般都能加载。

多台 Windows 用 Tailscale 组网 + SSH 互控与文件互投权限高吗,安全吗?

权限级别为未标注权限。安装前先看它会读取或写入哪些内容。

多台 Windows 用 Tailscale 组网 + SSH 互控与文件互投收费吗?

本站不代收费用,也没有标注具体价格。是否收费取决于技能本身和所用的模型或客户端,正式使用前按作者说明确认。

多台 Windows 用 Tailscale 组网 + SSH 互控与文件互投用什么许可证?

许可证标注为 未说明(许可证不明确)。商用或二次分发前请到仓库确认条款原文。

热门专题

按场景挑技能
全部专题 →

技能内容以作者仓库为准;分类、权限级别与许可证状态的中文说明为中文整理,最近核验 未标注。