Skip to content

About

Turn a Windows PC into a download outlet: an SSH reverse tunnel exposes a local Range-capable HTTP worker to your Linux box — plus an MCP adapter so agents can drive downloads as tool calls.

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Local Download Worker

把一台 Windows PC 变成「下载出口」:它在自己的机器上发起下载,再通过 SSH 反向隧道 把 HTTP 服务暴露给 Linux 侧使用。 适用场景:服务器直连某些外部数据源慢或被限流,而 Windows 家宽出口快得多。

English quick start — 见下方 English quick start;本文档其余部分为中文。


本仓库包含四部分

目录 是什么 文档
仓库根目录(installer/、worker/) Windows 侧:把一台 Windows PC 装成下载出口(本 README 的主体,从下一节开始) 本文件
tools/ Linux 侧下载链:提交 → 排队 → 派发 → 传输 → 看状态。出口可以是你自己的机器,也可以是本仓的 Windows Worker 本文件 Linux 侧下载链
mcp/ MCP 适配层:把这条下载链包装成 MCP Server,任何 MCP 客户端都能用工具调用发起下载、查进度、看出口健康状况 mcp/README.md
linux/ Linux 侧只读视图:dw_tasks.py 把 Linux 上的下载台账读成一帧 JSON,供 Windows 侧的 download-worker status / process 显示「Linux 现在派了什么活、各自下到哪」 本文件 Linux 侧任务视图

四者可以分开用:只想要「Windows 家宽当下载出口」,看完本文件就够了; 想在 Linux 上跑一条带队列与续传的下载链,看 tools/; 想让 agent(Claude Code 等)直接调下载,再看 mcp/; 只想要一个「Linux 端下载队列的命令行仪表盘」,单独拿走 linux/dw_tasks.py 也行(纯标准库、只读)。


Linux 侧下载链

tools/ 是一条自带队列的下载链:申请写进队列 → driver 按优先级派发 → 下载器 分块传输并逐块校验 → 落地后做终验(大小 / 校验和 / 空洞扫描),全过才算完成。 传输可以走本仓的 Windows 出口(更快),也可以直连目标站点。

# 配置:仓库里不带任何站点真值,部署差异全在这一步填
mkdir -p ~/.config/download-worker
python3 tools/dl_config.py --export-example > ~/.config/download-worker/site.json
$EDITOR ~/.config/download-worker/site.json
python3 tools/dl_config.py --check

# 起 driver(长驻:它才是把队列里的申请变成实际下载的那个进程)
python3 tools/dl_all_priority.py

# 提交一个下载,然后看进度
python3 tools/dl_req.py submit --item "item-001" --platform gamma \
    --url "https://example.org/archive.bin" --outdir /abs/path/item-001
python3 tools/dl_req.py list
python3 tools/dl_ctl.py show               # 队列 / 在跑的线 / 运行时控制

几个值得先知道的点:

  • 等级:p0 加急 / p1 高 / p2 普通(默认)/ p3 低 / p4 批量。 等级高的先跑;同等级内后提交的先跑。提交时用 --level。
  • 运行时控制不用重启:dl_ctl.py 能改线数、暂停/恢复、插队、跳过某个目标, 写的是控制文件,driver 下一轮就读到。
  • 中断了就重跑同一条命令:断点记在 sidecar(<输出>.download.json)里。 sidecar 还在就说明没下完 —— 别手动删输出文件。
  • 终验全过才算完成:字节数对、校验和对,都不代表中间没有整段空洞; 这条链默认会扫空洞并把有洞的文件隔离待重下。
  • 出口:没配 worker 时用 --direct 直连;配了则默认走出口、失败可回落。 出口侧(Windows Worker)的安装见本文件上文。
  • 进度不必去翻网络挂载:driver 会把「谁在哪个口、下到哪了」写成一份台账放在中转盘 (download.log 全局 + download_status_<port>.log 单口,由 tools/ssd_ledger.py 维护): 跟着派活自动建,任务彻底结束后 10 天自动清。PC 侧的 download-worker status all 和 MCP 的 download_status 读的就是它 —— 一次小读,网络挂载打嗝时也秒回。
  • 中转盘水位随口数自动放大:ops.stage_base_gb + ops.stage_per_port_gb × 出口数, 上限由 ops.stage_cap_gb 封顶(默认 500 GB);涨到水位暂停取新任务,回落到 60% 再继续。 单文件超过 ops.stage_max_file_gb 的不走中转,直接落目的地。

完整参数与口径见 mcp/用户手册.md(那份手册同时是这条链的用户手册)。


English quick start

On the Windows PC that should become the download outlet:

  1. Prerequisites: Python 3.8+ (with venv), Tailscale installed and logged in, Windows OpenSSH client, and passwordless SSH from that PC to your Linux box (ssh -o BatchMode=yes <user>@<host> echo ok prints ok).
  2. Install — copy this folder to the PC and run (a UAC prompt appears; it also asks once about the Worker's own proxy and once before touching the execution policy):
    installer\install.cmd -LinuxUser <linux-user> -LinuxHost <linux-host> -LinuxTunnelPort <端口> -WorkerProxy http://<proxy-ip>:<端口>
    
    -WorkerProxy is optional: downloads are tried through it first and fall back to DIRECT; without it the Worker goes DIRECT only. It is the Worker's own proxy, dialled directly — the PC's private proxy (loopback) is refused. Change it later with download-worker proxy <url>.
  3. Verify — installer\verify.cmd (exit code 0 = everything checked out).
  4. Use it from Linux — curl http://127.0.0.1:<端口>/health -> {"status":"ok","worker":"local-download-worker"}; curl http://127.0.0.1:<端口>/status -> 这台 PC 此刻在传什么(空闲时 {"state":"idle"})。 Data plane: GET /stream?url=<public-url> (passes Range through) and POST /download — see HTTP API.
  5. Measure this PC — download-worker test 268435455 pulls 256 MiB through the configured exit (add -Direct to compare the direct route) and prints the real speed; nothing is written to disk.

它解决什么问题

调用方(Linux 上的脚本 / 客户端 / curl)
    │  HTTP   http://127.0.0.1:<LinuxTunnelPort>
    ▼
Linux 127.0.0.1:<LinuxTunnelPort>
    │  SSH 反向隧道(Windows 主动连出,ssh -R)
    ▼
Windows Worker <本机 Tailscale IP>:<WorkerPort>        <- FastAPI
    │
    ▼
Windows 本地网络出口 -> 目标站点 / ...

两个关键点:

  • 隧道方向是 Windows -> Linux(ssh -R)。Windows 侧主动连出,所以它可以在 NAT / 防火墙后面,路由器不需要端口映射,Windows 也不需要公网 IP。
  • Windows 侧只监听自己的 Tailscale 地址,并由防火墙规则限定只有 Tailscale 网段(100.64.0.0/10)能访问 —— 不对局域网或公网开放。

特性

特性 说明
一条命令安装 installer\install.cmd:自动请求管理员权限、绕过 PowerShell 执行策略限制,无需手工配置
全配置化 本机 Tailscale IP、端口、Linux 用户/地址、日志目录全部来自 worker-config.json,包内没有任何机器特定值
幂等 / 可升级 同一条命令重跑即升级;配置、虚拟环境、日志、带宽设置全部保留
无窗口 两个长驻计划任务以 S4U(session 0)运行,不会出现控制台窗口,也不可能被误关
自动恢复 Worker 退出后 5 秒自动重启(worker_supervisor.py);隧道同样有守护(tunnel_supervisor.py,退出即重启、5/10/15/20/30 秒退避),计划任务本身也带失败重启
手动开关 download-worker on / off / status:随时把这台 PC 从下载池里摘出去或放回来(含 Linux 侧状态同步,不留假告警)
带宽控制 0-100%(0 = 暂停),Get-WorkerBandwidth / Set-WorkerBandwidth 两个命令
日志轮转 + 回传 单文件 10 MB x 5 轮转;按周期增量同步回 Linux 侧
收敛的攻击面 只监听 Tailscale 地址 + 防火墙限定网段;/stream 只允许解析到公网 IP 的 http/https 目标(拦 SSRF)
干净卸载 删计划任务、防火墙规则、安装目录;不碰 Tailscale、SSH 密钥、Windows OpenSSH
装前自动清旧版 安装时自动找出旧安装(计划任务指向的目录 + 常见路径),停掉其残留进程(venv python + 无名 ssh 隧道)再删除,避免「文件被占用删不掉」「旧隧道占着端口导致新守护起不来」

前置条件

项目 要求 说明
Windows Windows 10 / Server 2016 或更新,64 位 安装器会检查
PowerShell 5.1(系统自带) 由 install.cmd 自动提权并绕过执行策略,不需要手工设置
Python 3.8 或更新(建议 3.10+,需含 venv/pip) 安装器不自动装 Python,缺失会明确报错
Tailscale 已安装且已登录 安装器不装、不登录 Tailscale,只检测并取 IPv4
OpenSSH 客户端 C:\Windows\System32\OpenSSH\ssh.exe Windows 可选功能,安装器只检测
SSH 免密 该 PC 已能用 ssh -o BatchMode=yes <user>@<linux> 免密登录 Linux 侧 安装器不包含任何私钥,缺失会明确报错

安装

把整个文件夹复制到目标 PC(含 installer 与 worker 两个子目录),然后:

方式一(推荐):在资源管理器里双击

installer\install.cmd

它会:

  1. 检测到没有管理员权限时,弹出 UAC 并以管理员身份重新打开自己;
  2. 用 -ExecutionPolicy Bypass 启动 install-worker.ps1(批处理文件不受 PowerShell 执行策略约束,所以在出厂默认 Restricted 的机器上也能直接跑);
  3. 结束后 pause,让你看清安装摘要。

第一次安装必须给出 Linux 侧信息:

installer\install.cmd -LinuxUser <linux用户> -LinuxHost <linux地址> -LinuxTunnelPort <端口>

方式二(进阶):已经在一个管理员 PowerShell 窗口里(且该窗口能跑脚本)时,直接调安装器:

cd <复制路径>\installer
.\install-worker.ps1 -LinuxUser <linux用户> -LinuxHost <linux地址> -LinuxTunnelPort <端口>

先干跑看一遍要做什么(不改系统):

.\install-worker.ps1 -LinuxUser <linux用户> -LinuxHost <linux地址> -LinuxTunnelPort <端口> -DryRun

参数

参数 默认 说明
-InstallDir C:\ProgramData\LocalDownloadWorker 安装目录(可换成别的盘,例如 D:\LocalDownloadWorker)
-LinuxUser 无(首次必填) Linux 侧 SSH 用户;重跑时省略则沿用已有配置
-LinuxHost 无(首次必填) Linux 侧地址(Tailscale IP 或可达的 IP/域名);重跑时省略则沿用
-LinuxTunnelPort <端口> Linux 侧监听端口。每台 Windows PC 必须不同(见下)
-WorkerPort <端口> Worker HTTP 端口(Windows 本机)
-WorkerProxy 无(=只直连) Worker 自己的代理,本机直接连它(不再经 ssh 隧道),形如 http://<代理IP>:<端口> 或 socks5h://<代理IP>:<端口>;写进 worker-config.json 的 proxy.url。下载永远先走它(比直连快得多),它连不上才回退直连;本机私人代理绝不承载下载数据(回环地址在本脚本和 worker.py 两处都被拒,见「代理出口」)。安装时不带此参数会交互询问(无人值守则沿用配置里的值)
-LinuxProxyHost、-LinuxProxyPort、-LinuxProxyType、-LinuxProxyLocalPort、-DisableLinuxProxy — 已废弃(r5 反向隧道代理):为免旧命令行直接报错而保留,host+port(+type)会被折算进 -WorkerProxy 并打印 WARN;-LinuxProxyLocalPort 接受但忽略;-DisableLinuxProxy 等价于 -WorkerProxy off
-LogSyncRemoteDir /home/<LinuxUser>/<工具目录>/log 日志同步到 Linux 的目标目录
-LinuxWorkerPoolPath <工具目录>/worker_pool.py Linux 侧 worker 池脚本路径。download-worker on/off 通过它同步本机在池里的启用状态(见「手动开关」)
-LinuxDwTasksPath <工具目录>/dw_tasks.py Linux 侧只读任务视图路径。download-worker status / process 通过它显示 Linux 的下载队列与进度(见「Linux 侧任务视图」)
-LogSyncIntervalMinutes 5 日志同步周期
-SkipLogSyncTask 关 不创建日志同步计划任务
-PipIndexUrl 无(PyPI) pip 镜像,如 https://pypi.tuna.tsinghua.edu.cn/simple
-FixExecutionPolicy 关 无人值守:新窗口若因执行策略无法加载带宽命令,直接放开当前用户策略(不再询问)
-SkipExecutionPolicyFix 关 从不询问、也不修改执行策略,只在需要时打印提示
-SkipLegacyCleanup 关 跳过「清理旧版本」这一步(自动清理误伤了不该动的目录时才用)
-KeepPaused 关 保留「主动下线」状态:不动 paused.flag,也不做启动验证(确实要让这台 PC 保持离线时用)。不加它就是默认把本机重新上线
-DryRun 关 只打印将要执行的动作,不改系统

Windows 侧 Tailscale IP 自动检测(tailscale ip -4,失败则回退解析 ipconfig 里 100.64.0.0/10 的地址),不写死任何机器的 IP。

安装器做了什么

  1. 检查管理员权限 / Windows 版本 / 64 位
  2. 检查 Python(含 py 启动器、PATH、常见安装路径)、python -m venv、pip
  3. 检查 Tailscale 并取本机 Tailscale IPv4
  4. 检查 OpenSSH 客户端,并做一次 ssh -o BatchMode=yes 免密认证测试
  5. 清理旧版本:找出旧安装(计划任务指向的目录 + D:\local-download-worker 等常见路径,且确实含 Worker 文件),停掉其残留进程(目录下的 python/pythonw + 持有 -R <隧道口>: 的 ssh),删除旧目录(最多重试 5 次),并复核没有进程还占着隧道口
  6. 处理「主动下线」标记:若存在 paused.flag(曾被 download-worker off 下线),默认删除它、把本机重新上线;加 -KeepPaused 则保留标记并跳过启动验证
  7. 创建 <InstallDir>,复制 Worker 源码
  8. 创建 <InstallDir>\.venv,pip install -r requirements.txt
  9. 创建 logs\,写入 bandwidth.json({"percent": 100},UTF-8 无 BOM)
  10. 写入 worker-config.json(本机全部可变配置)。写入前会问一次 Worker 代理(-WorkerProxy 已给则不问;交互终端才有此题,直接回车沿用当前值,输 off 关闭),写完顺手探测一次该代理通不通(不通只 WARN,不中断安装)
  11. 建防火墙规则:仅 100.64.0.0/10(Tailscale)可访问 -WorkerPort
  12. 建 3 个计划任务(见下)
  13. 注册两个带宽命令(见「带宽控制」),并生成 download-worker 开关命令 + 写入用户 PATH(见「手动开关」)
  14. 启动 Worker -> 轮询 http://<Tailscale IP>:<WorkerPort>/health(最多 90 秒)
  15. 启动隧道守护 -> 检查守护与 ssh 进程(确认端口上的 ssh 确实出自我们的守护;若发现游离隧道就杀掉并等守护接管,最多 90 秒)-> 从 Linux 侧 curl http://127.0.0.1:<LinuxTunnelPort>/health 验证
  16. 打印安装摘要(READY)

失败时不会留下「看起来装好了」的状态:安装器以非零退出码结束,并打印安装日志 / Worker 日志 / 隧道日志的位置。

安装后产生的东西

<InstallDir>\
|-- worker.py  worker_config.py  start_worker.py  start_tunnel.py
|-- worker_supervisor.py  tunnel_supervisor.py  sync_log.py
|-- download-worker.ps1  WorkerBandwidth.psm1  requirements.txt
|-- worker-config.json          <- 本机配置(升级时保留)
|-- paused.flag                 <- 仅当 `download-worker off` 时存在(本机主动下线标记)
|-- .venv\                      <- 独立 Python 环境(升级时保留/复用)
`-- logs\
    |-- worker.log  worker.log.1..5   <- Worker 日志(轮转 10 MB x 5)
    |-- worker_supervisor.log         <- Worker 守护日志(启动/退出/暂停)
    |-- tunnel.log                    <- SSH 隧道日志
    |-- tunnel_supervisor.log         <- 隧道守护日志(重启与退避都记在这里)
    |-- bandwidth.json                <- 带宽百分比(升级时保留)
    |-- worker.log.sync-state         <- 日志同步断点
    `-- installer.log                 <- 安装器日志

另有一个开关命令落在用户目录(见「手动开关」):

%USERPROFILE%\bin\download-worker.cmd   <- 生成的转发器;%USERPROFILE%\bin 会加入用户 PATH

worker-config.json 示例:

{
  "worker_host": "100.x.y.z",
  "worker_port": <端口>,
  "linux_user": "<linux用户>",
  "linux_host": "<linux地址>",
  "linux_tunnel_port": <端口>,
  "linux_worker_pool_path": "<工具目录>/worker_pool.py",
  "log_sync": {
    "enabled": true,
    "remote_dir": "/home/<LinuxUser>/<工具目录>/log",
    "interval_minutes": 5
  },
  "proxy": {
    "url": "http://<代理IP>:<端口>",
    "retries": 2,
    "windows_private": { "enabled": false },
    "large_file_threshold_bytes": 1073741824
  }
}

proxy.url 空串 = 只走直连;可填 http://host:port、socks5://host:port、socks5h://host:port(后者由代理解析 DNS)或带凭据的 http://user:pass@host:port(凭据只进客户端、不进日志)。改它最省事的办法是 download-worker proxy <url>,下个请求即生效(worker.py 每次请求都重读配置,不用 restart)。 worker_host 留空或写 "auto" 时,Worker 启动时会自动检测本机 Tailscale IPv4。 linux_worker_pool_path 只被 download-worker on/off 的状态同步用到(旧配置没有这一项时回退到安装器默认值)。

计划任务(Task Scheduler)

任务名 程序 参数 触发 登录类型 窗口
Local Download Worker .venv\Scripts\python.exe worker_supervisor.py 登录时(+15 秒) S4U 无窗口
Local Download Tunnel .venv\Scripts\pythonw.exe tunnel_supervisor.py 登录时(+45 秒) S4U 无窗口
Local Download Log Sync .venv\Scripts\pythonw.exe sync_log.py 登录时 + 每 N 分钟 Interactive 无窗口(pythonw,短命进程)

三者共用:RestartCount=3 / RestartInterval=PT1M / ExecutionTimeLimit=PT0S(不限时)/ MultipleInstances=IgnoreNew,以当前用户身份、最高权限运行。 Worker 自身的进程级恢复由 worker_supervisor.py 负责(退出后等 5 秒重启),隧道由 tunnel_supervisor.py 负责(ssh 退出即重启,间隔按 5/10/15/20/30 秒退避、稳定跑满 60 秒则重置;秒退时会把 tunnel.log 的最后一行 ssh 报错抄进自己的日志),不引入第三套 watchdog。

为什么隧道也要守护(一次真实事故):任务计划自带的重启只在任务失败退出时生效;ssh 若自己正常退出(换网络 / 掉线 / 被对端断开),任务状态会停在 Ready 而没人拉它 —— 表现就是「PC 在线、worker 也在,但 Linux 侧那个端口连不上」。守护脚本把「拉起 ssh」变成一个永不退出的循环,同时它也顺带做到「开机/登录后一定会有隧道」。两件事都由计划任务 + 守护脚本闭环。

守护脚本同样尊重「手动开关」:download-worker off 期间 worker 守护与隧道守护都不拉起进程(见下节)。

隧道自己的探活设置(worker/start_tunnel.py 里那几条 -o):ServerAliveInterval=15 + ServerAliveCountMax=3 = 链路静默死掉后 ~45 秒内 ssh 自己退出(有数据在流时探测会被抑制,所以短间隔不影响下载速度,只决定空闲隧道能在死链上挂多久);TCPKeepAlive=yes 让系统层再看一道;ConnectTimeout=15 给建连也封顶,避免黑洞路由让 ssh 卡在 connect() 里、守护以为它还活着;ExitOnForwardFailure=yes 让「端口被占」这类问题立刻失败而不是假装健康;LogLevel=VERBOSE 把断开原因(Timeout, server ... not responding、remote port forwarding failed、Connection reset by peer…)留在 tunnel.log,否则日志只剩一句 exit 255,事后无从诊断。

客户端的探活救不了 Linux 侧的端口:链路静默死亡时 Linux 那端收不到 FIN,只能等 TCP 自己超时,这期间端口仍被半开会话占着 —— 新隧道会以 remote port forwarding failed 立刻失败(守护因此退避重试,直到对端放开)。把这段等待缩短要靠 Linux 侧(识别并清掉端口上已死的 ssh),不属于本包。

为什么完全没有窗口(两层保证):

  1. 登录类型 S4U("不管用户是否登录都运行、不存储密码"):两个任务都运行在 session 0,那里根本没有桌面 —— 即使是控制台子系统的 python.exe 也不会显示窗口,更不存在"被误关的窗口";注销/锁屏后任务继续运行(重新登录时 AtLogOn 触发器只是再确认一次)。
  2. 子进程也隐藏:Tunnel 的启动器是 GUI 子系统的 pythonw.exe,且 start_tunnel.py 拉起 ssh.exe 时带 CREATE_NO_WINDOW。Worker 用 python.exe 而不是 pythonw.exe,因为它跑在 session 0 里本来就没有窗口,这样日志与 stdout 行为跟常规 Python 程序一致。

若目标机不允许 S4U 登录(缺"作为批处理作业登录"权限),安装器会打印 WARN 并自动退回 Interactive;此时 Tunnel 用 pythonw.exe 仍然无窗口,Worker 会重新出现一个控制台窗口(仅在这种情况下)。

退回 Interactive 还有一个更隐蔽的后果:任务进程会落在"启动它的那个登录会话"里——如果那个会话是 SSH / RDP,退出该会话时 Windows 会把隧道一起带走(实测症状:从 shell 登录再 exit,worker 的隧道跟着关)。安装器会在安装日志里回读并打印 LogonType = S4U/Interactive,verify.cmd 也会显示,一眼可查;要修就给该账号"作为批处理作业登录"权限(secpol.msc → 本地策略 → 用户权限分配)后重跑安装器。

第三项 Local Download Log Sync 是短命任务:每 N 分钟(默认 5)启动一次,一秒钟左右同步完就退出,所以平时去看它永远是 Ready——那是正常的,不是没跑。 它把 worker.log(含 .1–.5 轮转)以及 tunnel.log、worker_supervisor.log、tunnel_supervisor.log 增量推回 Linux 侧的 log_sync.remote_dir(断点存在 logs\worker.log.sync-state);远端目录不存在时脚本会先 mkdir -p 建好(此前远端目录一缺就静默失败、日志从没到过 Linux 侧)。不需要这个任务就用 -SkipLogSyncTask 关掉。

验收

installer\verify.cmd          # 只读检查,退出码 0 = 全部通过

检查项:安装目录与源文件、venv 与 Python 版本、fastapi/httpx/uvicorn 是否装好、worker-config.json、logs\、bandwidth.json、三个计划任务状态、防火墙规则(端口/远端地址)、带宽命令是否真的能跑(会开一个全新 PowerShell 进程执行一遍)、download-worker 开关命令与用户 PATH、隧道任务是否已指向 tunnel_supervisor.py、隧道守护进程在跑、/health(Tailscale IP 与 localhost 各一次)、隧道 ssh 进程、从 Linux 侧经隧道访问 /health、Linux 日志目录;若存在 paused.flag 会打印 NOTE 提醒这是人为关机。

端到端真实下载在 Linux 侧做:

curl -s -m 5 http://127.0.0.1:<端口>/health          # 期望 {"status":"ok",...}
curl -s "http://127.0.0.1:<端口>/stream?url=https://example.org/big.tar" -o big.tar

升级 / 重跑

同一个命令重跑即可,安装器是幂等的:

  • 刷新:Worker 源码、依赖(pip install)、防火墙规则、三个计划任务、带宽命令
  • 保留:worker-config.json 中未在命令行重新指定的项、logs\bandwidth.json、logs\ 内容、.venv(能跑就复用,坏了才重建)
  • 自动清理:装之前先清掉旧安装(见「安装器做了什么」第 5 步)。若旧版本装在另一个目录(例如旧的 D:\local-download-worker),它的目录会被删掉;如果它和当前 -InstallDir 是同一个目录,则只停进程、保留目录(配置 / 日志 / venv 继续用)
  • 重新上线:若这台 PC 曾被 download-worker off 下线(存在 paused.flag),重跑安装器会删掉标记、把机器放回下载池;确实要让它保持离线就加 -KeepPaused
  • 重跑后会重启 Worker 与隧道任务

卸载

installer\uninstall.cmd                       # 或 uninstall-worker.ps1(管理员 PowerShell)
.\uninstall-worker.ps1 -KeepFiles             # 只删任务和防火墙,保留安装目录

删除:三个计划任务、Local Download Worker* 防火墙规则、注册的带宽命令、%USERPROFILE%\bin\download-worker.cmd 开关命令(bin 目录若因此变空,会一并从用户 PATH 里摘掉;里面还有别的东西则保留并 WARN)、安装目录(会先做「看起来像不像安装目录」的校验;不像则拒绝删除,除非 -Force),并停掉残留进程 —— 判据是可执行文件在安装目录下(venv 的 python/pythonw)或命令行里出现该目录,加上持有 -R <隧道口>: 的 ssh(隧道进程的命令行里没有目录,只能按端口找);目录删除失败会自动重试 5 次并列出仍被占用的文件。 不会碰:Tailscale、SSH 私钥、Windows OpenSSH、Linux 侧 sshd 配置 —— 这些属于系统环境,不属于本工具。

带宽控制

安装器会把这两个命令注册到当前用户的 PowerShell 模块路径,新开一个 PowerShell 窗口直接敲就能用(不需要 Import-Module,也不需要知道装到了哪个盘):

Get-WorkerBandwidth                 # 例: Worker bandwidth: 100% (10 MB/s)
Set-WorkerBandwidth 50              # 0=暂停, 10=1MB/s, 20=2MB/s, 50=5MB/s, 100=10MB/s
Set-WorkerBandwidth 100             # 恢复

命令只是往 <InstallDir>\logs\bandwidth.json 写一个 {"percent": <0-100>},Worker 每次限速判断时都会重新读它,所以改完立即生效(0 = 暂停)。

查当前生效值:Get-WorkerBandwidth,或直接 download-worker status(它会显示带宽上限;如果最近 10 分钟有下载,还会附一条从 logs\worker.log 算出的实测均速)。带宽文件缺失/损坏时 Worker 按 100% 跑。

执行策略(安装时会自动处理)

Windows 客户端的出厂执行策略是 Restricted —— 在这个设置下任何 .ps1 / .psm1 脚本文件都不能被加载,上面这两个命令也就无从注册。安装器的处理方式:

  1. 用一个全新的 PowerShell 进程真的把命令跑一遍(跑之前先清掉安装器自己可能携带的进程级策略,模拟你新开窗口的环境);
  2. 如果失败原因是执行策略,问你一次是否放开当前用户的策略(等价于 Set-ExecutionPolicy -Scope CurrentUser RemoteSigned:用户级、不需要管理员、可逆;本机创建的脚本可以运行,从网上下载的脚本仍需签名或 Unblock-File);
  3. 同意就设置并重新验证;不同意(或加了 -SkipExecutionPolicyFix,或检测到是非交互式环境)就只打印提示,安装继续。

对应的纪律:

  • 无人值守安装想直接放开 -> 加 -FixExecutionPolicy;不想让它碰策略 -> 加 -SkipExecutionPolicyFix;
  • 手工等价命令:Set-ExecutionPolicy -Scope CurrentUser RemoteSigned(改完要新开窗口才生效);
  • 完全不碰策略的兜底:直接编辑 <InstallDir>\logs\bandwidth.json(内容就是一个 {"percent": 50})。

手动开关:download-worker on / off / status

安装器会在 %USERPROFILE%\bin 放一个 download-worker.cmd,并把该目录加入用户 PATH(安装器也会顺手更新当前会话的 PATH)。新开一个命令行窗口(cmd / PowerShell 都行,不需要管理员),直接敲:

download-worker status              # 看现在开着还是关着(默认动作)
download-worker off                 # 把这台 PC 从下载池里摘出去:Worker + 隧道都停
download-worker on                  # 放回下载池:Worker + 隧道都回来
download-worker restart             # 重启 Worker + 隧道,但不改变开关状态(等价于 off→on 的进程部分)
download-worker proxy               # 看当前代理设置
download-worker proxy http://<代理IP>:<端口>   # 设 Worker 自己的代理(下个请求即生效)
download-worker proxy socks5h://<代理IP>:<端口>
download-worker proxy off           # 清掉代理,只走直连
download-worker test 268435455      # 实测这台 PC 的下载速度(走配置里的代理)
download-worker process             # 实时看 Linux 侧的下载队列与进度(Ctrl+C 退出)

代理只进 worker-config.json 并立刻被 worker.py 读到,不需要 restart;指向本机回环(127.0.0.1 / localhost,也就是私人代理)会被当场拒绝。

status all / status <port> = 全局的下载安排与进度

不带参数的 status 回答的是「这台 PC 自己怎么样」外加喂给它自己那个口的队列。带上参数问的是另一个问题:

download-worker status all     # 整台下载池:所有口的安排与进度
download-worker status <port>  # 只看某个口("linux" = 服务器直连那条线)

它读的是管家端写在中转盘上的下载台账(tools/ssd_ledger.py 维护):每行一个数据,写清它在哪个口、下到哪了、多大、谁申请的、什么时候开始。台账跟着派活自动建,任务彻底结束后 10 天自动清。

为什么非要另立这一层:进度必须是一次小读。这台 PC 想看管家端的进度只有两条路 —— 走 SSH,或走网络挂载。而网络挂载病态时一次目录清单要几十秒(dw_tasks.py 那种遍历式视图会整块卡住)。台账把进度预先压成「少量文件 + 单次读」,所以 status all 在网络挂载打嗝时照样秒回。

两种「读不出」都会明说,不会给一个看着像在跑的假状态:任务没走中转(老式直写目的地)→ 该行标 error;整份台账读不到 → 给一行原因,而不是一张空表。

test = 在这台 PC 上实测下载速度

download-worker test 268435455                          # 拉 256 MiB,走配置里的代理
download-worker test 1G                                 # 也可以用单位写法
download-worker test 268435455 "https://host/path/file" # 换一个目标文件
download-worker test 256M -Direct                       # 绕开代理,测直连做对比

和手工那条 curl.exe --proxy <代理> -L -r 0-<n-1> -o NUL -w "..." <url> 做的是同一件事:拉 <bytes> 字节(纯数字 = 字节数,与 curl -r 0- 后面那个数同款;也可写 256M / 1G / 512K)丢进 NUL 不落盘,进度表实时打在终端,结束后报告实测速度(MiB/s + MB/s)、耗时、接收字节数、HTTP 状态码,并按该速度推算 1 GiB / 10 GiB 各需多久。

项 说明
出口 默认走 proxy.url 配的那条(worker.py 真正用的那条路);配置里是回环代理会拒用并转直连;-Direct 直接绕开代理做对比
默认目标 省略 url 时用内置的公开测速文件(proof.ovh.net/files/10Mb.dat,支持 Range、无需凭据)
上限 / 提醒 单次样本上限 10 GiB;样本 < 1 MiB 提示读数没意义;实测 < 256 KB/s 提示「比预期慢很多」
副作用 没有:只读配置、只跑 curl,不改配置、不碰 worker / 隧道 / 计划任务,也不往磁盘写数据

测的是这台 PC 自己的出口,与 Linux 侧、与其它 PC 无关 —— 用来验证「这台机器直连 vs 走代理哪个快」或排查「怎么变慢了」。

没生效多半是窗口没重开(PATH 是进程启动时读的)。等不及就关掉窗口重开,或直接用全路径: <InstallDir>\download-worker.ps1 -Action off。

process = 实时看 Linux 侧的下载队列

download-worker process             # 每 2 秒刷新一帧,Ctrl+C 退出
download-worker process -ProcessInterval 5   # 换成 5 秒一帧

把 Linux 侧「现在派了什么活、各自下到哪」画在终端里,原地刷新(同 curl 进度表的做法,不是一路往下滚):上面是台账总览(各状态各多少条)+ 当前在下的任务(百分比、已下字节、物种/文件名、实测速度、状态),下面是最近完成的 5 条。正在传输的排在前面、行首带 >;挂在台账里但当前没在动的(多为中断残留、等重排队)用灰色显示,超过 8 条只列前 8 条并附一行汇总。

项 说明
数据来源 Linux 侧 dw_tasks.py(见 Linux 侧任务视图),经 ssh <linux_user>@<linux_host> 取;只读,不碰下载器、不改任何配置
只看本机 Linux 段按本机的隧道口过滤(-R <LinuxTunnelPort>,每台 PC 各一个),经 dw_tasks.py --channel 实现:每台 PC 只看到自己这条口在下的任务,另有「全局在下 N 个」一行说明全池情况。「最近完成」是全局的(台账无 PC 归属),会标注出来
速度 由 Linux 侧相邻两帧的字节差算出,只有 process 有(status 是单帧,给不出速度)
退出 Ctrl+C:ssh 与 PowerShell 同属一个控制台,中断会同时送到两边;本地 ssh 一死,远端脚本下一次写 stdout 就自己收摊,不会留孤儿进程
失败时 一帧都拿不到会提示,并给出可手动执行的 ssh <user>@<host> "python3 <path> --text"

需要 Linux 侧存在 dw_tasks.py(默认 <工具目录>/dw_tasks.py,即本仓库的 linux/dw_tasks.py;路径可用安装参数 -LinuxDwTasksPath 改)。没有它时 process 与 status 的 Linux 段只会打一行提示,其余功能不受影响。

off = 整机下线(用户确认的语义)

步骤 动作
1 创建 <InstallDir>\paused.flag —— 权威开关:Worker 守护与隧道守护看到它就都不再拉起进程
2 停掉本机属于本安装目录的 python / pythonw 进程与 ssh -R <port> 隧道进程
3 尽力 Stop-ScheduledTask + Disable-ScheduledTask(三个任务;失败只警告)
4 通过 ssh <linux_user>@<linux_host> 执行 worker_pool.py --set-enabled <port> false:Linux 池里这个口标记为人为关闭,调度器不会再往它派任务,也不会因为端口不通而报掉线告警

on 是反向操作:删 paused.flag → 启用并启动计划任务 → 轮询 /health(最多 30 秒)→ 同步 --set-enabled <port> true。 status 打印:开关状态(paused.flag)、当前带宽上限(读 logs\bandwidth.json;若最近 10 分钟有下载,另附一条实测算得的均速)、下载出口(直链 / 代理,附各自 IP,见下节)、三个计划任务状态、本安装目录的进程、/health、Linux 池里该口的 enabled/状态,以及Linux 侧的下载队列(见下节)。

几点须知:

  • 正在下载的任务不会丢:off 相当于拔线,Linux 侧调度器会把它踢出池并重排队,任务带 sidecar 断点,换别的出口或等它回来都能续传。
  • 第 4 步是「尽力而为」:ssh 不通只打印 WARN,不影响本机停机;此时 Linux 池只知道端口不通(会按掉线处理并告警),恢复后自动一致。
  • 和带宽命令的区别:Set-WorkerBandwidth 0 只是暂停下载流量(Worker、隧道、池内在线状态都不变,任务仍可派进来排队);download-worker off 是整机退出下载池。临时让路用前者,收工/搬家/维护用后者。
  • Linux 侧等价操作(不碰 Windows 时也可用):python3 <工具目录>/worker_pool.py --set-enabled <端口> false,或 --get-enabled <端口> 查。

status 里的「下载出口」行(直链还是代理、IP 是多少)

status 会用三行说明这次下载走的是哪条路:

  [ OK ]  下载出口:WORKER_PROXY(本机独立代理)—— <代理IP>:<端口> → <目标站> 
          配置代理:WORKER_PROXY http://<代理IP>:<端口>(HTTP)—— 优先走它,失败才回退直连
          私人代理:禁用(下载不占用)
行 来源 怎么读
下载出口 logs\worker.log 里最后一条 [NETWORK] route= 最近一次请求实际走的出口:DIRECT = 直连(附本机出口 IP + 目标主机),WORKER_PROXY = Worker 自己的代理(附它实际连的 host:port),WINDOWS_PRIVATE_PROXY = 本机私人代理(出现就是异常,说明配置被改过)。方括号里是那条日志的时间 —— 时间旧 = 那之后就没再下过东西,不是显示错误。
配置代理 worker-config.json 的 proxy.url 配置上的代理出口,附带协议(HTTP / SOCKS5);显示「未配置(只走直连)」= 这台 PC 直连不通就真的没办法了。用 download-worker proxy <url> 改。
私人代理 worker-config.json 的 proxy.windows_private 正常必须是「禁用」;若显示已启用,会附上它只对多大的小文件生效(下载大文件/大小未知一律不走它)。

装好后还没跑过下载(或本机 worker.py 仍是旧版)时,这一行显示 WARN「日志里没有 [NETWORK] 记录」,不是故障。 直连 / 代理各自的链路与切换规则见前面的「代理出口(下载流量走哪条路)」一节。

Linux 侧任务视图(dw_tasks.py)

linux/dw_tasks.py 是给 Windows 侧 status / process 供数的只读脚本:纯 Python 标准库、不依赖第三方包、不写任何文件、不动下载器。它读两份已经存在的产物,拼成一帧 JSON:

来源 提供什么
下载台账 CSV(--status-csv / 环境变量 DW_STATUS_CSV 指定,或配置文件里的 status_csv_candidates 按顺序自动探测) 各状态计数、当前在下(DOWNLOADING / REPAIRING)、最近完成
在下任务目录里的断点文件 <输出文件>.download.json 精确进度(已完成块 → 字节 → 百分比)、块计数、断点文件新鲜度

下载器一开始就把目标文件 sparse 预分配到全尺寸,所以 ls -l 的大小不代表进度;唯一可靠的两个来源就是断点文件与已分配块数(后者作为退路)。

python3 dw_tasks.py --json            # 一次性快照(download-worker status 用)
python3 dw_tasks.py --watch 2         # 每 2 秒一行 JSONL(download-worker process 用)
python3 dw_tasks.py --text            # 人看的表格(在 Linux 上直接跑)
python3 dw_tasks.py --limit 5         # 「最近完成」显示几条(默认 5)
python3 dw_tasks.py --channel <口>    # 只看这个通道口(某台 PC 的隧道口)在下的任务

通道归属(--channel / 帧里的 channel 字段):linux_downloader.py 的 --worker http://127.0.0.1:<口> 决定一条线走哪台 PC,所以按进程命令行就能把「此刻在下的任务」分到 linux(服务器直连)/ <隧道口> 各口 (扫 /proc 得到,只读)。--channel <口> 只保留该口的在下任务;过滤只作用于列表, 帧里的 active_total / active_live_total 仍是过滤前的全局条数 —— Windows 侧据此同时显示 「本机 N 个 / 全局 M 个」,不会被误读成全局只剩这几条。完成记录分不了 PC(台账 CSV 没有「哪个口下的」这一列), 「最近完成」始终是全局口径并如实标注。端口不写死:将来多一台 PC,新的口自动成为一种通道。

升级顺序:--channel 是较新才有的参数,Windows 侧的 download-worker.ps1 会直接带上它。 所以先更新 Linux 侧的 dw_tasks.py,再换 Windows 侧的脚本 —— 反过来的话,老 dw_tasks.py 遇到不认识的参数会直接报错退出,那一块就显示成「没有输出」(其余功能不受影响)。

--watch 是 JSONL:一行一个对象、写完即 flush,管道断开(Ctrl+C / ssh 掉线)就退出,不会僵在那里。列名认得几种常见写法(name/item/label、size_gb/size…,完整对照见 dw_tasks.py 的 COLUMN_ALIASES),缺列只是少一项信息,不会报错。

HTTP API

Worker 是 FastAPI 应用,默认监听 http://<Tailscale IP>:<WorkerPort>;经隧道后从 Linux 侧访问 http://127.0.0.1:<LinuxTunnelPort>。

方法 路径 说明
GET /health 存活探测:{"status":"ok","worker":"local-download-worker"}
GET /status 此刻在传什么(免鉴权、只读、立即返回):空闲 {"state":"idle"},在传则见下节
GET /stream?url=<公网 URL> 流式代理:把上游字节流原样转发(Range 透传,便于分段 / 断点续传);上游必须是 http/https 且解析到公网 IP
POST /download 让 Windows 侧直接落盘:JSON {"url": "<公网 URL>", "output": "<Windows 路径>"}

示例(在 Linux 侧):

# 流式取回一个文件
curl -s "http://127.0.0.1:<端口>/stream?url=https://example.org/big.tar" -o big.tar

# 让 Windows 机器自己下载并保存到它自己的盘
curl -s -X POST http://127.0.0.1:<端口>/download \
     -H "Content-Type: application/json" \
     -d '{"url":"https://example.org/big.tar","output":"<盘符>:\\Downloads\\big.tar"}'

/stream 的目标校验会拒绝解析到私有 / 回环 / 链路本地 / 保留 / 组播地址的 URL(防止把 Worker 当跳板打内网),只允许 http / https。

GET /status(在传任务快照)

给 Linux 侧的状态看板用:调用方每隔几秒探一次,面板上就能显示「这台 PC 正在传什么」,而不是只看到「活着」。 它只读(不改配置、不下载、不落盘)、免鉴权(与 /health 同级)、立即返回——读的是一份由传输线程维护的快照,不等待任何锁、不查上游。

// 空闲
{"state": "idle"}
// 在传:字段全部来自本次请求
{"state": "downloading",
 "url":    "https://example.org/archive.bin",
 "task":   "example-task-0001",    // 请求头 X-Task,没发就是空串
 "client": "dl_all_priority",      // 请求头 X-Client,没发就是空串
 "range":  "bytes=0-268435455",    // 请求头 Range,没发就是空串
 "bytes":  1048576,                // 本次已写出给调用方的字节数
 "total":  1662560245,             // 上游 Content-Length;拿不到是 null
 "started": "<开始时刻>"} // 该请求开始的本机时间

几点须知:

  • 一条在传请求一条记录。下载器按 --connections 开几条并发连接就有几条,/status 报最近活跃的那一条(同一次下载的 url / task / client 相同,只有 range 不同,所以看不出区别)。
  • bytes 的更新粒度 = /stream 现有的 1 MiB 块。流式行为一个字没动:为了统计去缓存或切小块都是不允许的,代价是慢速上游上 bytes 可能几秒才跳一次。
  • 请求在连接上游之前就登记(连上之前的那几秒也算「在传」),连接失败、上游 4xx/5xx、调用方中途断开都会立刻撤销登记;/status 每次被调用时还会顺手回收「请求任务已结束」的残留记录。
  • 返回的 url 是完整 URL(含 query)。S3 预签名 URL 的凭据就在 query 里,所以这个口和 /health、/stream 是同一个信任面:只应暴露给可信任的本地调用方(/status 本身不做任何鉴权,隧道口只绑 127.0.0.1)。

多台 Windows PC

Linux 侧 127.0.0.1:<LinuxTunnelPort> 一个端口只能被一条隧道占用,所以第二台 PC 必须换端口:

PC -LinuxTunnelPort Linux 侧调用地址
第一台 <端口>(默认) http://127.0.0.1:<端口>
第二台 <端口> http://127.0.0.1:<端口>

端口被占用时 ExitOnForwardFailure=yes 会让 ssh 立即退出,安装器会报「隧道进程未运行」并提示换端口。

代理出口(下载流量走哪条路)

原则:下载先走 Worker 自己的代理(WORKER_PROXY,本机直连它,不经隧道);它连不上才回退直连(DIRECT);这台 PC 自己的私人代理绝不承载下载数据。

为什么要有这套东西:httpx 默认 trust_env=True,会读取 Windows 的 HTTP_PROXY / HTTPS_PROXY / ALL_PROXY。装了私人代理(clash 之类)的机器上,所有 /stream、/download 流量会悄悄从私人代理出去,白白消耗机主的代理流量池。现在 Worker 建的每个 HTTP 客户端都显式 trust_env=False,环境变量代理彻底失效;出口只能由 worker-config.json 的 proxy 块决定。

直连自己那个代理,隧道只保留 -R(把 worker 发布给 Linux)这一件事。

出口 何时用 说明
WORKER_PROXY 配了就先走它 proxy.url 指的代理,本机直接连(http:// / socks5:// / socks5h://,后者由代理解析 DNS)。retries(默认 2)次尝试,中间隔 1.5 s——一次 DNS 抖动不该把请求推到下一条出口
DIRECT 没配代理时唯一出口;配了则作兜底 直连。代理连不上(网络层错误)时自动回退,下载不会因为代理挂了而停
WINDOWS_PRIVATE_PROXY 默认禁用,且大文件永不使用 本机私人代理。即使有人把 windows_private.enabled 打开,Content-Length ≥ large_file_threshold_bytes(默认 1 GiB)或大小未知的文件也不会进这条出口

只有网络层错误才换出口:DNS 解析失败、连接超时、连接被拒、网络不可达(Python 里的 httpx.TransportError)。HTTP 404 / 403 / 鉴权失败等响应错误原样返回给调用方,不会去试下一个出口、更不会无限换代理。

私人代理有两道锁:命令行(download-worker proxy、安装器)拒绝任何指向本机回环的目标(127.x / localhost / ::1),worker.py 收到同样的值也会忽略并记一条 WARN 日志——两层都不通。

链路长这样:

Linux 侧                    Windows 侧
linux_downloader.py
   │  http://127.0.0.1:<端口>
   ▼
隧道 ssh ──── Tailscale ────► worker.py ──► WORKER_PROXY ──► <代理IP>:<端口> ──► 目标站
      -R <端口>(worker)            │              (本机直接连,不经隧道)
                                 └─► DIRECT ────► 目标站(代理不可达时的兜底)

日志:Worker 每次请求都写一行 [NETWORK] plan=WORKER_PROXY,DIRECT url=<去 query 的 URL> range=<...>,实际用哪条出口由结果行给出:

[NETWORK] route=WORKER_PROXY url=https://example.org/... status=206 range=bytes=0-... proxy=<代理IP>:<端口>
[NETWORK] route=DIRECT       url=... status=206 ... proxy=-

日志里的 URL 去掉 query(S3 预签名 URL 的凭据就在 query 里),代理只记 host:port,不记任何凭据(带用户名口令的 url 也只写 host:port)。响应头里也带 x-worker-route(值为 WORKER_PROXY / DIRECT / WINDOWS_PRIVATE_PROXY),回调方可直接看到这次走的哪条出口。文件落盘完成的那行(STREAM complete ... route=...)同样带出口名。

开关:安装时给 -WorkerProxy <url>,或不带参数让它问一次;装好后改配置用 download-worker proxy <url> / proxy off——下个请求即生效,不必 restart

安全说明

  • 不引入任何密钥:安装包不含 SSH 私钥;隧道用的是目标机自己的 SSH 免密登录。
  • 监听面最小:Worker 只绑定本机 Tailscale 地址,防火墙规则只放行 100.64.0.0/10。
  • 不出公网:隧道是 Windows -> Linux 的反向连接,Windows 侧不需要公网 IP、不需要端口映射。
  • SSRF 防护:/stream 只接受能解析到公网 IP 的 http/https 目标;/download 由调用方决定落盘路径 —— 也就是说 Worker 的控制口只应暴露给可信任的 Linux 侧(它确实可以往 Windows 任意可写路径写文件,这是功能的一部分)。
  • 状态口只读:/status 不解锁任何动作、不改任何状态,只回一份「当前在传什么」的快照,内容不超出本机调用方自己发起的请求。
  • 执行策略:只有在你同意(或显式加 -FixExecutionPolicy)时,安装器才会把当前用户的策略设为 RemoteSigned;它不动机器级策略、不需要管理员。
  • 代理凭据:proxy.url 可带用户名口令(http://user:pass@host:port),它只写进本机 worker-config.json 并用于建连;日志、status、响应头里一律只出现 host:port(回归测试 T9 守着这条)。指向本机回环的代理地址会被命令行与 worker.py 两层拒绝,这台 PC 的私人代理不会因为误配而承载下载数据。

已知限制

Windows MCP Server、Worker Pool、多 Worker 调度、Tailscale 自动安装/登录、自动测速选 Worker、公网 Worker、GUI 配置程序、多用户权限、云端管理 —— 都不在本包范围内。

本包只做一件事:把一台已经可用形态的 Windows Worker 变成可重复安装的软件包。

可以怎么改(欢迎 Issue / PR)

这个包只解决「我自己的 Windows PC + 我自己的 Linux 服务器」这一个场景,所以刻意做窄了。 下面这些方向都没做,也不打算由作者做 —— 需要的人直接改,欢迎提 Issue / PR:

方向 现在的样子 改造要点
不依赖 Tailscale Windows 侧只监听 Tailscale 地址,防火墙也只放行 100.64.0.0/10 隧道的传输其实与 Tailscale 无关(ssh -R 本来就是主动连出,NAT 后面照样能用)。把「取本机 Tailscale IP」换成普通网卡地址、防火墙规则跟着换,就能在纯 SSH 可达的环境里跑。要改的是两处:worker/worker_config.py 的 is_tailscale_ip(),和安装器里「取址 + 建规则」那一段
打包成 exe 依赖目标机预装 Python(含 venv) 用 PyInstaller 把 worker.py / start_worker.py / start_tunnel.py 三个入口各打一个 exe,安装器改成拷 exe;记得把 .venv 那一步一起去掉
换掉 SSH 传输层 只有 ssh -R 一种隧道 换成 WireGuard / frp / 自建长连接都行 —— 只要 Linux 侧最终得到一个 127.0.0.1:<port>,Worker 侧一行都不用改
多出口调度 池与选口在 Linux 侧(不属于本包) 本包内可以加「按源站测速并上报」,让调度端有依据可选
Windows 侧 MCP Server 没有;MCP 适配层在 Linux 侧(见 mcp/) 若想让 agent 直接调用 Windows 本机

动手前建议先读 worker/worker_config.py(所有可变配置都从这里出)和安装器第 5 步的旧版本清理逻辑 —— 这两处是本包里最容易「改了一处、别处不认」的地方。

许可

MIT,见 LICENSE。

About

Turn a Windows PC into a download outlet: an SSH reverse tunnel exposes a local Range-capable HTTP worker to your Linux box — plus an MCP adapter so agents can drive downloads as tool calls.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages