TSLinkTSLink 文档

命令参考

TSLink 所有命令的完整参考

查看 Markdown

概述

TSLink 提供了一组简洁的命令来管理 Tailscale 网络上的服务。所有命令遵循 tslink <命令> [参数] [标志] 的模式。

认证

通过引导菜单录入并存储凭据。普通 login 不是浏览器 OAuth callback;单独的页面打开 helper 是另一项操作。

bash
tslink login

非交互式模式(适用于 CI/CD 和自动化):

bash
# 通过 stdin(不把 secret 放进 argv 或 history)
printf %s "$TSLINK_API_KEY" | tslink login --api-key-stdin
printf %s "$TSLINK_CLIENT_SECRET" | tslink login --client-secret-stdin

# 通过 secret manager 预注入的环境变量
tslink login

选择恰好一个明确凭据来源(一个 stdin flag 或一个兼容 argv flag);多个明确来源是 usage error。未指定明确来源时,先考虑环境变量,再进入交互提示。--json 禁用交互录入;自动化优先使用 stdin 或 secret manager 注入的环境,避免 argv/history 泄漏。

标志:

标志描述
--api-key-stdin从 stdin 读取 API 访问令牌
--client-secret-stdin从 stdin 读取 OAuth 客户端密钥
--manage-acl显式 opt in 远端 ACL tag-owner mutation;默认 login 不会改写共享 ACL policy
--api-key <令牌>兼容性 API 访问令牌路径;自动化中避免使用,因为 argv 可能泄漏
--client-secret <密钥>兼容性 OAuth 客户端密钥路径;自动化中避免使用,因为 argv 可能泄漏
--expires-in <时长>以「从现在起的时长」记录 API 访问令牌有效期(90d、30d 或 Go duration);仅适用于 api-key
--expires-at <时间戳>以 RFC3339 时间戳记录 API 访问令牌有效期;仅适用于 api-key,且与 --expires-in 互斥
--retire-other当前凭证验证并提交成功后,删除另一个凭证槽位;默认 api-key 与 client-secret 两个槽位并存
--open-keys-page独立辅助命令:打开 Tailscale API keys 页面,退出码 0 且不存储任何凭证
--open-oauth-page独立辅助命令:打开 Tailscale OAuth 页面,退出码 0 且不存储任何凭证

TSLink 接受两种密钥类型:

  • API 访问令牌(tskey-api-*)— 输入时自动验证,tslink serve 时自动派生认证密钥。
  • OAuth 客户端密钥(tskey-client-*)— 不会周期性过期,但当前 Tailscale REST 标签/设备自动化能力比 API-token 模式更窄。

凭据优先使用系统钥匙串(macOS Keychain、Linux Secret Service、Windows Credential Manager)。明文 0600 文件回退仅在 macOS/Linux 证明旧钥匙串值不存在或已删除后允许;钥匙串不可达或状态不确定时拒绝写入,应恢复访问后重试。Windows 不允许凭据文件回退。无头运行本身不保证可以回退。

API 令牌生成地址:管理后台 → Keys。OAuth 密钥生成地址:管理后台 → OAuth(点击 "+ credential" → "OAuth client" → 按所需 API 操作配置 scopes → 复制 client secret)。

API 访问令牌会过期,TSLink 会记录过期时间。传 --expires-in 或 --expires-at 时按你声明的值存储;两个都不传时,TSLink 按 90 天上限假定,并记录 expires_at_source=assumed_max,这样后续 tslink doctor 能区分「假定的期限」和「声明的期限」,而不会把猜测当成事实。

--open-keys-page 和 --open-oauth-page 是引导辅助命令,不是登录方式。只有当 stdin 是交互式终端且 CI 未设置时才真的打开浏览器,否则打印 URL 和引导步骤。两种情况下都以退出码 0 结束且不存储任何东西,因此可以安全地在「还在找凭证从哪来」的脚本里运行。

清除 Tailscale 认证状态并移除已存储的凭证(API 密钥和/或 OAuth 客户端密钥)。在登出之前必须先停止网关。

bash
tslink logout

这会从系统钥匙串中移除所有凭证(以及旧文件),并移除 ~/.config/tslink/ 中的节点状态。你的服务注册表(registry.json)会被保留。

Flags:

Flag说明
--kind <api-key|client-secret>只移除该凭证槽位及其 metadata;节点状态和另一个槽位保留

用 --kind 可以在保留另一个凭证继续可用的前提下退役其中一个,例如把自动化从 OAuth client secret 迁移到 API 访问令牌之后。

服务管理

默认 add 会确保后台网关运行,可能返回授权交接。完成授权后用 url --wait;不要在默认 add 后再启动另一个 serve。同名条目会被替换。

注册一个新服务以暴露到你的 tailnet。必须提供 --proxy、--dir 或 --tcp 之一。

服务类型标志:

标志描述示例
--proxy <目标>反向代理到本地 Web 服务--proxy localhost:3000
--dir <路径>通过 HTTPS 暴露文件目录--dir ~/Documents/shared
--tcp <host:port>原始 TCP 代理(数据库、自定义协议)--tcp localhost:5432

行为标志:

标志描述要求
--ephemeral请求临时节点;控制面按不活跃状态清理,而非断开即移除任何类型
--tags <标签>逗号分隔的 ACL 标签(如 tag:web,tag:internal)。省略时使用默认标签(tag:tsmain)。任何类型
--allow <身份>proxy/file HTTP 服务的允许身份。拒绝与 --tcp 和 --funnel 组合;原始 TCP 由 Tailscale ACL/tag 和目标服务自身认证保护。--proxy 或 --dir
--funnel请求通过 Tailscale Funnel 公开暴露仅 --proxy
--public确认 --funnel 会将代理服务暴露到公共互联网--funnel
--funnel-ttl <时长>访问期限至少 1h,默认 24h,公网上限默认 7d;支持相对/绝对形式,新的公开拒绝 never。--funnel
--no-auto-provision关闭 Funnel policy 自动 provisioning--funnel
--control-url <URL>服务级控制服务器 URL(如 Headscale)任何类型
--no-daemon-install只保存配置,不安装也不启动后台服务任何类型
--dry-run校验并打印服务,不写 registry、不启动网关,默认 false任意类型
--wait <时长>等待一个精确的 runtime URL 或 enrollment URL(默认 30s;--wait=0 表示注册后不等待)任何类型

其他标志:

Flag说明
--ack-unlimited-request-body明确确认移除 HTTP 请求体上限;需 --max-request-body unlimited。
--force-unsafe-public危险:覆盖 recipe 的 never-public 拒绝,可能公开主机控制或私有数据;需 --funnel --public,add 还需 --recipe。
--health-body匹配前 64 KiB 正文中的子串,仅 proxy;勿把秘密放入参数。
--health-interval后端检查间隔,10s 至 1d,默认 1m。
--health-pathHTTP 后端检查路径,默认 /,仅 proxy。
--health-status-maxHTTP 期望状态上限,默认 299,仅 proxy。
--health-status-minHTTP 期望状态下限,默认 200,仅 proxy。
--health-timeout后端检查超时,100ms 至 30s,默认 5s。
--idle-timeoutHTTP keep-alive 空闲超时,默认 60s。
--max-request-bodyHTTP 上传上限,默认 32MiB;unlimited 需 --ack-unlimited-request-body。
--preserve-host转发节点可信的 canonical 外部 Host,仅 proxy;普通注册默认关闭,recipe 有自己的默认值。
--recipe应用 recipe ID;默认预览,--yes 应用。
--request-header-timeoutHTTP header 读取超时,默认 10s。
--request-read-timeout请求体无读取进展的最长时间,默认 30s,不是总上传时长。
--yes应用审阅过的 recipe plan;add 需要 --recipe。
--requestable明确在私有 portal 请求表单披露此应用名称;仅私有 HTTP/文件,默认关闭。

示例:

bash
# 暴露一个 Web 应用
tslink add webapp --proxy localhost:3000

# 暴露一个 API 服务器,带 ACL 标签
tslink add api --proxy 127.0.0.1:8080 --tags tag:api,tag:prod

# 暴露一个目录用于文件共享
tslink add docs --dir ~/Documents/shared

# 通过 TCP 暴露 PostgreSQL 数据库
tslink add mydb --tcp localhost:5432

# 请求临时节点;本地注册保留至主动移除
tslink add devserver --proxy :8080 --ephemeral

# 通过 Tailscale Funnel 公开访问
tslink add public-site --proxy localhost:3000 --funnel --public

# 限制特定用户访问
tslink add internal --proxy localhost:9090 --allow user@example.com,tag:admin

# 为该服务指定控制服务器
tslink add headscale-app --proxy localhost:3000 --control-url https://headscale.example.com

按名称移除已注册的服务。本地 registry 移除会立即完成。本地 tsnet state 只有在远端身份被证明已清除后才删除;daemon 运行时由其 reconciliation 负责删除 state。远端 tailnet 清理只有在 TSLink 拥有匹配设备的精确所有权证明时才会尝试;否则匹配候选会报告为 protected,并跳过清理。

bash
tslink remove webapp

remove 是幂等的。移除一个从未注册过的名字是一次成功调用,只是什么都没移除,所以要回答「服务是否已经没了」看 removed 而不是 ok:

json
{"type":"tslink.result","ok":true,"schema_version":1,"command":"remove","code":0,"data":{"name":"docs-example-nonexistent","removed":false,"device_cleaned":false,"device_cleanup_skipped":false}}

如果网关正在运行,服务将通过热重载移除,无需重启。

Flags:

Flag说明
--strict服务不存在时返回 not_found(退出码 5),而不是上面那种幂等成功

幂等是清理脚本该有的默认行为:重跑第二遍不应该失败。--strict 面向相反的场景 —— 调用方希望「这个名字确实注册过」成为一个被强制检查的前置条件。

以表格格式显示所有已注册的服务,无论网关是否运行。

bash
tslink list

输出列:

列描述
NAME服务在 tailnet 上的主机名
TYPE服务类型(proxy、file 或 tcp)
BACKEND本地目标(proxy/tcp 为 host:port,file 为路径)
ENDPOINT预期的 tailnet 端点类型
EXPOSURE暴露方式(tailnet、allow-list 或 Funnel)

标志:

标志描述
--name <name>只返回名字完全匹配的那个服务
--type <type>按服务类型过滤:proxy、file 或 tcp
--fields <list>--json 视图的逗号分隔精简字段
--verbose返回完整的 owner-only 诊断视图
--tailnet只读:列出整个 tailnet 中每一台带 TSLink 标签的设备,而非本机已注册的服务

默认列表读的是本机的 ~/.config/tslink/registry.json。--tailnet 改为查询 Tailscale API,报告 tailnet 中每一台带 TSLink 标签的设备:本机注册的服务、其它机器注册的服务,以及孤儿节点。它回答的是单机注册表回答不了的跨机器问题。

bash
tslink list --tailnet
tslink list --tailnet --json

每一行都标明它来自机器边界的哪一侧:

origin含义
local_registry本机 registry.json 中有一个主机名完全相同的服务
local_name_variant该主机名是本机某个已注册服务的 <service>-N tsnet 冲突变体,这是本机留下孤儿节点的常见形态
unregistered本机注册表对该主机名一无所知:它是另一台机器的服务,或是一个孤儿节点

人类可读输出以 N of M TSLink-owned tailnet devices are not registered on this machine. 结尾,JSON 负载在 count 之外还带 registered_count 与 unregistered_count。每个结果都带一个常量字段 cleanup_authority,因为这个视图暴露了一条真实的限制:tslink cleanup 只删除精确 NodeID 记录在本机 node-ownership.json 中的设备,所以本机注册表没有命名的设备,必须回到创建它的那台机器上清理。--tailnet 永远不输出 NodeID,也永远不删除任何东西。

--tailnet 需要已存储的 Tailscale API 凭证(tskey-api-* 访问令牌或 OAuth 客户端密钥)。没有凭证时它以 auth_error(退出码 3)失败,并在 error.next 中给出 bootstrap 指引;它不会返回空列表。它与 --name、--type、--fields、--verbose 互斥(usage_error,退出码 2),因为那些 flag 过滤的是本机已注册服务,而 --tailnet 报告的是 tailnet 设备。

注册一个一次性的 proxy 或 file 服务,需要时启动 daemon,并等待一个精确的 runtime URL——这是从零到一个 URL 最快的路径。已存在的目录会作为 file 服务暴露其内容;普通文件只暴露选中的文件并返回该文件的 URL,不暴露同级文件或父目录列表;裸端口或 host:port 目标会变成 HTTP 代理。分享默认是临时(ephemeral)的,重复分享同一目标会复用已有服务,而不是创建带后缀的孤立节点。

bash
tslink share ./build
tslink share ./report.html          # URL 直接指向 report.html
tslink share 3000
tslink share localhost:8080 --name preview
tslink share ./build --ephemeral=false

在零凭据首次运行时,stdout 只有一行——Tailscale 授权 URL,stderr 会打印出精确的续接命令(tslink url <name> --wait)。加 --json 时,这是一个成功的 status:"needs_login" 结果,携带 auth_url——不是认证错误。

标志:

标志描述
--name <名称>请求的服务名(DNS label)。匹配的目标会复用它;不相关的名字冲突会得到一个数字后缀,而不是覆盖已有服务。
--ephemeral使用临时 tailnet 节点(默认 true;传 --ephemeral=false 获得持久状态)
--wait <时长>等待一个精确的 runtime URL(默认 30 秒;与 tslink url 不同,share 不需要传这个标志就会等待)
--no-daemon-install要求后台服务已在运行;不去安装它
Flag说明
--ack-unlimited-request-body明确确认移除 HTTP 请求体上限;需 --max-request-body unlimited。
--idle-timeoutHTTP keep-alive 空闲超时,默认 60s。
--max-request-bodyHTTP 上传上限,默认 32MiB;unlimited 需 --ack-unlimited-request-body。
--preserve-host转发节点可信的 canonical 外部 Host,仅 proxy;普通注册默认关闭,recipe 有自己的默认值。
--request-header-timeoutHTTP header 读取超时,默认 10s。
--request-read-timeout请求体无读取进展的最长时间,默认 30s,不是总上传时长。

打印一个已注册服务的精确 runtime URL,可选择等待它就绪。

bash
tslink url myapp
tslink url myapp --wait
tslink url myapp --wait --raw

标志:

标志描述
--wait [时长]只有传入时才等待精确 runtime URL;裸 --wait 表示 30 秒
--raw只打印 URL 和一个换行符,不带 JSON envelope;与 --json 冲突

如果 runtime 尚未报告精确的 tailnet 主机名,且你没有传 --wait,tslink url 会以非零状态退出,error.code 为 url_not_ready,而不是打印占位符。

网关

启动 TSLink 网关。为每个已注册的服务启动一个嵌入式 tsnet 节点并开始服务。

标志:

标志描述
--daemon作为守护进程在后台运行
--control-url <URL>自定义控制服务器 URL(如 Headscale)— 覆盖全局配置
--manage-acl显式开启普通启动 tag-owner ensure;另行确认公开的 Funnel auto-provisioning 仍默认开启
--mcp在专用的 tailnet-only 节点上提供远程 MCP 控制面;默认关闭,且要求 config.json 中配置非空 owner mcp.allow 或明确 mcp.bindings。参见远程 MCP 控制面
--no-auto-provision对本次 serve 进程内的所有服务关闭 Funnel policy 自动 provisioning
--no-browser打印 Tailscale 登录 URL,不打开浏览器
bash
# 在前台运行
tslink serve

# 作为后台守护进程运行
tslink serve --daemon

# 使用 Headscale 控制服务器(单次覆盖)
tslink serve --control-url https://headscale.example.com

启动时,TSLink 自动:

  • 解析凭证并为每个服务启动一个 tsnet 节点。
  • 使用 --manage-acl 和具有所需 scopes 的凭据配置普通 tag-owner policy。显式公开的 Funnel 默认配置共享 grant,除非 --no-auto-provision 或服务设置关闭。准确所有权设备清理是独立 API 操作。
  • 监视 registry.json 的变化并热重载服务。

停止正在运行的 TSLink 网关(前台或守护进程)。读取 tslink.pid 并验证进程属于 TSLink,最多等待 5 秒终止。macOS/Linux 使用 SIGTERM 正常关闭;Windows 使用进程终止,不是正常关闭。

stop 不会移除自动启动注册。受管理的 macOS LaunchAgent 启用了 KeepAlive,停止后会按 30 秒节流重启;需要持续停止时先执行 tslink uninstall。Linux systemd 用户服务的重启策略是 on-failure,成功的正常关闭会让它保持停止。Windows 的启动注册在卸载前仍会于下次登录生效。卸载后,停止任何剩余的手动网关,再用 tslink status --json 验证后才删除状态。

bash
tslink stop

JSON 的 data.supervision 报告已验证的 manager、installed、autostart、restart_on_exit,以及可选 detail / evidence / autostart_scope。它与 daemon_running 分开;进程存活不证明自动启动或监督有效。

显示 TSLink 网关的当前状态:守护进程状态、认证状态和已注册服务数量。

bash
tslink status

输出示例:

代码
→ tslink: running (pid 12345)
→ tailnet: authenticated
→ services: 3 registered

Flags:

Flag说明
--urls显示仅限所有者可见的服务端点概览
--name <name>按准确服务名过滤 --urls;要求 --urls

--urls 打印每个服务的可达位置。这是 owner-only 输出:它列出的端点就是你自己注册表里定义的那些,因此请按对待注册表本身的方式对待这份结果。

标签管理

管理 TSLink 服务的 ACL 标签。API-key 模式可以通过 Tailscale API 创建或拉取标签;client-secret-only 模式不要在验证前依赖标签/设备自动化。

子命令:

子命令描述
list列出所有已注册服务及其标签
pull从 Tailscale API 打印当前 ACL 标签
add <服务> <标签>为特定服务添加标签
set <服务> <标签>用一个标签替换特定服务的标签
set-default <标签>设置未指定 --tags 时使用的默认标签
delete-remote <标签> --force --manage-acl通过本地安全检查和显式远端 ACL opt-in 后,经 API 从 Tailscale ACL 策略中全局移除 ACL 标签所有者规则

列出所有已注册服务及其标签。

bash
tslink tags list

从 Tailscale API 打印当前 ACL 标签。 client-secret-only 模式会跳过该远端拉取,因为它需要 API 访问令牌。

bash
tslink tags pull

为特定已注册服务添加标签。

bash
tslink tags add webapp tag:staging

用一个标签替换特定服务的标签。

bash
tslink tags set webapp tag:web

设置未指定 --tags 时应用于服务的默认标签。持久化在 GlobalConfig.DefaultTag 中。

bash
# 将默认值从 tag:tsmain 更改为自定义标签
tslink tags set-default tag:myteam

通过本地安全检查后,经 API 从 Tailscale ACL 策略中全局移除 ACL 标签所有者规则。此操作会全局修改你的 tailnet ACL 策略,且远端 ACL mutation 默认关闭,因此必须同时显式传入 --force 和 --manage-acl。

bash
tslink tags delete-remote tag:deprecated --force --manage-acl

维护

对照本地 registry 与 tailnet:停用已过期 Funnel、删除本机具有准确所有权证明且不再需要的设备,并报告本机对共享 Funnel grant 的使用情况。永不删除共享 grant。

bash
# 预览 -- 这是默认行为
tslink cleanup

# 实际执行
tslink cleanup --dry-run=false

Flags:

Flag默认说明
--dry-runtrue预览对账结果,不做注册表、设备或 ACL 删除;传 --dry-run=false 才实际执行
--manage-aclfalse只报告本机是否使用共享 Funnel tag-owner / nodeAttrs grant;永不删除共享 grant,也不查询 tailnet ACLs
--adopt <hostname>无为一个字面量 TSLink-tagged 遗留设备 hostname 预览或记录精确 ownership
--forcefalse确认显式指名的 --adopt 迁移

--dry-run 默认为 true,所以裸命令不删除任何东西。这个默认值是安全属性而不是便利性:删除要求持久化的精确 NodeID ownership proof,hostname 匹配仅用于发现。hostname 匹配上但 NodeID 未被证明的设备会出现在 devices_protected 里,永不删除。

--adopt 面向那些在 ownership proof 机制之前创建的设备。它只接受一个字面量 hostname,写入 proof 要求恰好一个 TSLink-tagged 远端匹配,外加 --force --dry-run=false;匹配多于一个时报 cardinality 冲突而不是替你猜。预览过程是只读的。

JSON envelope 会说明这次 cleanup 为什么什么都没做 —— 这比「它没做事」这个事实本身更重要:device_cleanup_skipped 配合 device_skip_reason 区分了 registry.json 结构上不可信、ownership ledger 不可用、orphan 缺少 retired_at provenance、没有可用的 API client、hostname-only 匹配受保护、以及远端清理失败这几种情况。输出中永远不出现 NodeID,只出现 hostname。

直接检视本地服务注册表,不经过 runtime。

严格校验一份 registry.json 并报告其中所有问题,且不修改文件。不带参数时校验当前生效的注册表;传路径则校验别处的文件,例如你正准备安装的候选文件,或来自另一台机器的副本。

bash
tslink registry check
tslink registry check ./candidate-registry.json --json

返回结果包含 path、schema_version、valid_services、total_services,以及一个 issues[] 数组,其中每一项用 index 和 name 指名出问题的服务,并带稳定的 code 和给人读的 message。所有被拒绝的条目在一次调用中全部报出,因此 valid_services 小于 total_services 时,你能准确知道 runtime 会丢弃哪些服务、以及为什么,而不是只看到 loader 恰好最先撞上的那一个问题。

邀请

邀请某个人加入你的 tailnet,或把某个 TSLink 拥有的服务设备共享给 tailnet 之外的人。

这里的每个子命令都会通过 Tailscale API 产生对外副作用:真实的人会收到真实的邀请。它们是 TSLink 中仅有的、效果能被你之外的人感知到的命令,因此每一条都显式指名收件人、要求由 tslink login 存储的 user-owned tskey-api- 令牌,并在 JSON envelope 中返回一个记录本次发送内容的 remote_side_effect_plan 对象。

设备共享还有第二重要求:TSLink 只共享它自己注册的设备,按精确 nodeId 匹配。无法证明归属的设备永远不会成为候选,因此打错服务名的结果是失败,而不是共享出一台不相干的机器。

子命令:

子命令说明
user <email>邀请一个用户加入 tailnet
device <service> <email>把一个 TSLink 拥有的服务设备共享给外部用户
list列出未接受的用户邀请和 TSLink 拥有的设备邀请
revoke <id> --kind <user|device>撤销一个用户或设备邀请
resend <id> --kind <user|device>重发一个以邮件方式创建的用户或设备邀请

邀请一个人加入你的 tailnet。

bash
tslink invite user teammate@example.com
tslink invite user teammate@example.com --role auditor
tslink invite user teammate@example.com --print-link
Flag默认说明
--role <角色>member接受邀请后分配的角色:member、admin、it-admin、network-admin、billing-admin、auditor
--print-linkfalse不发邮件;返回 API 提供的邀请 URL 供你自行送达

--print-link 改变的是由谁送达邀请,不是谁可以接受邀请。JSON envelope 明确陈述送达事实而不是暗示它:Tailscale 发出邮件时 emailed: true,你自行送达时 emailed: false 并附带 invite_url。该 URL 由 Tailscale API 原样返回 -- TSLink 从不自行构造 -- 并且它是 bearer 凭证:谁持有谁就能接受。

把某个已注册服务背后的设备共享给外部用户,让对方无需加入你的 tailnet 就能访问那一台机器。

bash
tslink invite device my-api partner@example.com
tslink invite device my-api partner@example.com --multi-use
Flag默认说明
--print-linkfalse不发邮件;返回 API 提供的邀请 URL 供你自行送达
--multi-usefalse允许该设备邀请被接受多次
--allow-exit-nodefalse允许接收方把共享设备用作 exit node

--multi-use 和 --allow-exit-node 各自都会放大这份邀请授予的范围,各自都默认关闭。multi-use 邀请 URL 在第一次被接受之后仍然有效;exit-node 共享则允许接收方把自己的流量路由经过这台机器。

列出未接受的用户邀请,以及这个 TSLink 拥有的服务对应的设备邀请。

bash
tslink invite list
tslink invite list --json
Flag默认说明
--show-urlsfalse在人类可读输出和 JSON 输出中包含 bearer 邀请 URL

URL 默认不打印,因为它们是 bearer 凭证:打印一次就等于把一份可被接受的邀请放进你的终端回滚区,以及任何会捕获它的地方。

JSON envelope 把「空结果」和「不完整结果」区分开。complete: true 表示每个被请求的设备目标都无错误地检查过;false 表示有部分设备结果缺失。device_targets[] 携带逐服务的结果,因此一个已检查、invite_count: 0 的目标读作「没有未接受的邀请」,而不是「没查到」。

撤销一个未接受的邀请。

bash
tslink invite revoke 12345 --kind user
Flag默认说明
--kind <user|device>必填邀请命名空间

--kind 刻意没有默认值。用户邀请 ID 和设备邀请 ID 是两个独立的数字命名空间,同一个数字可以在两边各指向一个邀请;要求显式给出命名空间,是为了让目标是被指定的而不是被猜出来的。撤销设备邀请还要求精确的 TSLink 节点 ownership proof。只有在 Tailscale API 接受撤销之后才会出现 revoked: true。

重发一个最初以邮件方式创建的邀请。

bash
tslink invite resend 12345 --kind user
Flag默认说明
--kind <user|device>必填邀请命名空间

用 --print-link 创建的邀请没有邮件地址,无法重发。重发输出也不会再次打印邀请 URL:把一份 bearer 凭证重印到第二个界面上没有任何收益,因此结果只报告 emailed 就停下。

诊断、访问解释与模板

运行本地诊断,并通过与其它命令相同的 JSON envelope 和退出码 contract 返回 warning/critical 阈值。

Flags:

Flag说明
--probe-external探测非 loopback 的服务目标
--probe-remote用一次 device-list 读取向 Tailscale API 验证每个已存储凭证,并把 last_verified 记入 credential-meta.json

两者默认关闭,因为两者都会伸到本进程之外:--probe-external 会向非 loopback 目标建立连接,--probe-remote 每个已存储凭证消耗一次 Tailscale API 调用。默认运行保持在本地且不产生费用。

Doctor 还会从本机 tailscaled 读取并报告本节点是否开启了 Tailscale SSH。人类可读输出打印 Tailscale SSH (this node): <state>;JSON 负载带有 tailscale_ssh.state 与 tailscale_ssh.acl_rule_required: true。这项检查存在的原因是:tailscale ssh <host> tslink <command> 是从 tailnet 内另一台机器操作这份安装的零代码路径,而这条路径需要两个都位于 Tailscale 层的条件:本节点开启了 Tailscale SSH,且 tailnet ACL 中有一条允许调用者的 ssh 规则。TSLink 只检测第一个条件并指出两者;它不会开启 Tailscale SSH,也不会修改 policy 文件。

状态Finding code含义
enabledtailscale_ssh_enabled一旦 tailnet ACL 有 ssh 规则允许调用者,tailscale ssh <this-host> tslink list --json 即可用
disabledtailscale_ssh_disabled在这台机器上运行 tailscale set --ssh 并添加 ACL ssh 规则,才能使用远程路径
unknowntailscale_ssh_unknown一秒内无法读取本机 Tailscale client 状态;检查 tailscale status

三种结果都是 informational,永远不会改变 doctor 的 status 或退出码。

解释一个已注册服务如何被访问,包括 tailnet endpoint、--allow 行为和 Funnel 暴露。

列出内置个人模板。

显示一个内置模板定义。

应用内置模板。使用 --dry-run 可在不写入 registry 的情况下预览 plan。

标志描述
--dry-run预览模板 plan,不写入 registry
--yes把模板里缺失的服务写入 registry
--no-daemon-install只应用配置,不安装也不启动后台服务

配置

管理持久化在 ~/.config/tslink/config.json 中的全局 TSLink 设置。

子命令:

子命令描述示例
set <键> <值>设置配置值tslink config set control-url https://hs.example.com
get <键>获取配置值tslink config get control-url
list列出所有配置值tslink config list

可用配置键:

键描述默认值
control-url自定义控制服务器 URL(Headscale)Tailscale 默认
bash
# 设置 Headscale 控制服务器
tslink config set control-url https://headscale.example.com

# 查看当前值
tslink config get control-url

# 清除(恢复 Tailscale 默认)
tslink config set control-url ""

# 显示所有设置
tslink config list

查看 TSLink 守护进程日志输出。默认读取 stderr 日志(tslink.err.log)的最后 50 行。

标志:

标志描述默认值
--last <N>显示的行数50
--level <级别>按最低日志级别过滤(debug、info、warn、error)(无)
--source <源>日志源:out(标准输出)或 err(标准错误)err
bash
# 显示最后 50 行日志
tslink logs

# 显示最后 100 行
tslink logs --last 100

# 仅显示错误
tslink logs --level error

# 显示标准输出日志
tslink logs --source out

# JSON 输出
tslink logs --last 20 --json

系统

注册 TSLink 为开机自启动。

bash
tslink install
  • macOS:创建 LaunchAgent(~/Library/LaunchAgents/com.tslink.daemon.plist)
  • Linux:创建 systemd 用户服务(~/.config/systemd/user/tslink.service)
  • Windows:注册交互用户 Task Scheduler 任务,启动内建 supervisor 并恢复 daemon 崩溃。--startup 为明确选择的 fallback,无崩溃恢复。
Flag含义
--force仅 macOS;Linux/Windows 不支持。即使 launchd 域不可用也继续升级(可能启动第二个守护进程)
--no-auto-provision在已安装后台服务中关闭自动 Funnel policy provisioning,并在受管理重启后保留

在 macOS 上,gui/$(id -u) 只在该用户存在桌面(Aqua)会话时才存在。可用性取决于会话本身,而不是你是否通过 SSH 连接。首次安装会回退到 user/$(id -u);升级时若某个曾经使用过的域无法检查,则拒绝执行,因为 TSLink 无法证明那个域是空的。该拒绝以 exit 1 返回,error.code 为 launchctl_domain_unavailable,其 data 给出不可用的域、完整的 --force 命令以及残留风险。

Flag说明
--startup仅 Windows:明确使用 Startup fallback,无崩溃恢复。

移除自启动注册。

bash
tslink uninstall
Flag含义
--force仅 macOS;Linux/Windows 不支持。即使 launchd 域不可用也移除 plist(可能留下一个仍在运行的守护进程)

同样的域规则适用。当没有任何域确认任务已卸载、且至少有一个域无法检查时,uninstall 保留 plist 并以 exit 1 退出,使恢复句柄得以保存。--force 会照样移除它,并说明如何检查以及如何移除可能仍在加载的任务。

编程式访问

已发布 manifest 中的命令,除 stdio 的 tslink mcp 服务器外,都接受 --json,并向 stdout 写出一个带版本的信封,因此 owner 侧自动化就是同一套 CLI 加一个 flag。Cobra 的 help 和 completion 命令不在 manifest 中,只输出纯文本。MCP client 通过 tslink mcp 或远程 MCP 控制面执行同样的操作。

常见自动化命令:

自动化 action当前入口
listtslink list --json
addtslink add <name> --proxy <host:port> --json(或 --dir、--tcp)
removetslink remove <name> --json
statustslink status --json
doctortslink doctor --json
access_explaintslink access explain <name> --json
template_listtslink template list --json
template_plantslink template apply <template> --dry-run --json
template_applytslink template apply <template> --yes --json
manifesttslink manifest --json
bash
# 列出本机已注册的服务
tslink list --json

# 添加服务
tslink add myapp --proxy localhost:3000 --json

# 删除服务
tslink remove myapp --json

# 查看状态
tslink status --json

# 运行只读诊断
tslink doctor --json

# 解释一个服务的本地访问模型
tslink access explain myapp --json

# 先预览、再应用内置模板
tslink template list --json
tslink template apply local-web --dry-run --json
tslink template apply local-web --yes --json

--json 只改变输出格式。tslink add --json 与人类路径使用同一套安全护栏:Funnel 服务必须传 --public,TCP 服务会拒绝 --allow,因为 TSLink 不会对原始 TCP 字节流应用 HTTP 身份检查。

每个 --json 响应都使用 JSON 输出格式 一节描述的带版本信封:命令载荷放在 data 下,结构化错误放在 error 下,command 记录产生该输出的命令。一次成功(在没有任何服务的机器上执行 tslink list --json)如下:

json
{"type": "tslink.result", "ok": true, "schema_version": 1, "command": "list", "code": 0, "data": {"schema_version": 1, "services": [], "count": 0}}

一次用法错误(tslink list --tailnet --verbose --json)如下:

json
{"type": "tslink.result", "ok": false, "schema_version": 1, "command": "list", "code": 2, "error": {"code": "usage_error", "message": "--tailnet conflicts with --verbose; --verbose filters this machine's registered services, while --tailnet reports tailnet devices", "next": ["tslink --help"]}}

MCP client 可以通过 tslink mcp(stdio)以及 tslink serve --mcp 启动的 tailnet-only 远程控制面执行同样的操作;参见 TSLink 作为 MCP 服务器。

在本地通过 stdio 启动一个 Model Context Protocol 服务器,让 MCP 客户端把 TSLink 当作工具来驱动,而不必解析 CLI 输出。它在 stdin/stdout 上使用按行分隔的 JSON-RPC 2.0,owner 会话暴露 44 个工具,精简角色更少:从 share、list、unshare、status,到 add、url、tags_*、access_explain、doctor、logs、invite_* 和 template_*。

bash
tslink mcp

MCP 进程本身不会打开任何网络监听端口,也不需要 mcp.allow 条目;share 可能会启动独立的 TSLink daemon 和它请求的 tsnet 服务。协议帧走 stdout、诊断信息走 stderr,因此这里会拒绝 --json:stdout 已被协议帧独占。

tslink serve --mcp 在一个专用的 tailnet-only tsnet 节点上通过 HTTPS 提供依角色变化的工具集合(owner 为 44 个),供 tailnet 内其它机器上的 MCP client 使用。它默认关闭,且要求 config.json 中配置非空 owner mcp.allow 或明确 mcp.bindings。

客户端配置、完整的工具 schema、远程控制面和端到端示例,请看 TSLink 作为 MCP 服务器。

选项说明
--apps精简 MCP 会话可见的逗号分隔应用。
--inventory明确允许 viewer 读取全部应用清单;不授予应用访问或访问历史。
--max-duration单应用人员授权的最长时长;精简本地 operator/manager 默认 24h。
--scopeMCP 角色:owner、viewer、app-operator 或 people-manager;默认 owner。

打印已安装二进制文件对自身的机器可读描述:每个命令、每个标志、退出码、错误码、凭据来源,以及导出的安全能力清单。这是 agent——以及本文档站点自己的 parity 检查——用来确认某个具体安装的构建实际支持什么,而不是相信文字描述的方式。

tslink manifest 对 tslink --help 隐藏(它是面向机器的命令,不是人类的操作步骤),但它和其它子命令一样可以直接运行:

bash
tslink manifest              # 完整 manifest,缩进 JSON
tslink manifest --compact    # 仅命令、标志和稳定错误码
tslink manifest --json       # 完整 manifest,包在标准 --json envelope 里

标志:

标志描述
--compact只打印命令、标志和稳定错误码

本文档站点的 CLI parity 检查(pnpm parity:check)把已提交的 fixture 与这份 manifest 的形状逐项比对;该 fixture 由 TSLink 自己的 go run ./tools/gen-manifest 生成,不是手工维护的。

Roadmap / Experimental 命令和字段

关于 roadmap 命令和字段(middleware、Docker 集成、Admin REST API、自定义域名/ACME)的详情,请参阅实验性与路线图。

全局标志

标志描述
--json带版本 CLI JSON;tslink mcp 因 stdout 保留给协议 frames 而拒绝
--version显示已安装版本
--help显示任何命令的帮助信息
bash
tslink --help
tslink add --help
tslink status --json

退出码

所有命令返回语义化退出码,便于程序化错误处理:

退出码含义示例
0成功命令完成
1一般错误意外失败
2用法错误无效的参数或标志
3认证错误缺少或无效的凭证
4冲突tslink serve 时已在运行
5未找到tslink url 指向不存在的服务。tslink remove 是幂等的:移除一个未注册的名字是一次成功调用,只是什么都没移除(removed: false,exit 0)
64警告tslink doctor 完成但有警告
65严重tslink doctor 发现严重问题

JSON 输出格式

传递 --json 时,CLI 命令输出 JSON 信封;tslink mcp 是明确的例外:

json
{
  "type": "tslink.result",
  "ok": true,
  "schema_version": 1,
  "command": "status",
  "code": 0,
  "data": {
    "supervision": {
      "manager": "none",
      "installed": false,
      "autostart": false,
      "restart_on_exit": false,
      "detail": "No launchd ownership/autostart could be verified. Run: tslink install"
    },
    "daemon_running": false,
    "daemon_state": "absent",
    "daemon_pid": 0,
    "ownership_proof_available": true,
    "authenticated": false,
    "credential_stored": false,
    "credentials": {
      "api_key": {
        "present": false,
        "expiry_state": "none",
        "early_warning": {
          "state": "none",
          "source": ""
        }
      },
      "client_secret": {
        "present": false,
        "expiry_state": "none",
        "early_warning": {
          "state": "none",
          "source": ""
        }
      }
    },
    "credential_expiry_state": "none",
    "node_authorized": false,
    "authorized_service_count": 0,
    "auth_status": "not_authenticated",
    "service_count": 0,
    "services": [],
    "guest_links": [],
    "access_log": {
      "current": false,
      "enabled": true,
      "last_write": null,
      "drops": 0,
      "size_bytes": 0,
      "error": "access_log_not_started",
      "updated_at": "0001-01-01T00:00:00Z"
    },
    "portal": {
      "enabled": false,
      "state": "disabled"
    },
    "alerts": {
      "notifier": "none",
      "events": []
    }
  }
}

错误时,error 字段是带稳定字符串 code 的结构化对象。下面是网关未运行时 tslink url nonexistent --json 的实际前提错误;即使名称不存在,也会先返回 exit 1 / daemon_not_running。网关运行后,对不存在服务的查询才会到达 exit 5 / not_found:

json
{
  "type": "tslink.result",
  "ok": false,
  "schema_version": 1,
  "command": "url",
  "code": 1,
  "error": {
    "code": "daemon_not_running",
    "message": "TSLink is not running; install and start its background service with 'tslink install'",
    "next": [
      "tslink install"
    ]
  }
}

应用与访问者

通过 recipes 列出、发现和分享自托管应用,不安装或配置应用。

列出带版本的 recipe catalog 与应用侧配置建议。

只读、有限的 loopback HTTP GET 指纹检测。部分结果返回 complete: false;匹配不能证明认证或健康。

默认预览,--yes 才应用,--dry-run 优先。保留已有名称;先核对应用登录、proxy trust、健康路径与主机端口。

Flag说明
--ack-unlimited-request-body明确确认移除 HTTP 请求体上限;需 --max-request-body unlimited。
--allow私有 HTTP 身份,逗号分隔;与 Funnel 冲突。
--control-url自定义控制服务器 URL;不能与 Funnel 同用。
--dry-run只预览,不写入,即使同时使用 --yes。
--ephemeral请求临时节点,不会自动删除本地注册信息。
--force-unsafe-public危险:覆盖 recipe 的 never-public 拒绝,可能公开主机控制或私有数据;需 --funnel --public,add 还需 --recipe。
--funnel公共互联网暴露,需 --public;不支持人员授权或 --allow。
--funnel-ttl访问期限至少 1h,默认 24h,公网上限默认 7d;支持相对/绝对形式,新的公开拒绝 never。
--health-body匹配前 64 KiB 正文中的子串,仅 proxy;勿把秘密放入参数。
--health-interval后端检查间隔,10s 至 1d,默认 1m。
--health-pathHTTP 后端检查路径,默认 /,仅 proxy。
--health-status-maxHTTP 期望状态上限,默认 299,仅 proxy。
--health-status-minHTTP 期望状态下限,默认 200,仅 proxy。
--health-timeout后端检查超时,100ms 至 30s,默认 5s。
--idle-timeoutHTTP keep-alive 空闲超时,默认 60s。
--max-request-bodyHTTP 上传上限,默认 32MiB;unlimited 需 --ack-unlimited-request-body。
--name覆盖 recipe 默认服务名。
--no-auto-provision禁用 Funnel policy 自动配置。
--no-daemon-install只保存注册表,不安装或启动 daemon。
--preserve-host转发节点可信的 canonical 外部 Host,仅 proxy;普通注册默认关闭,recipe 有自己的默认值。
--proxy覆盖实际 loopback HTTP(S) 主机端口。
--public明确确认 Funnel 将应用公开到互联网。
--request-header-timeoutHTTP header 读取超时,默认 10s。
--request-read-timeout请求体无读取进展的最长时间,默认 30s,不是总上传时长。
--tags逗号分隔的 tag: 标签;Tier 1 入网仍为用户拥有的无标签节点。
--yes应用审阅过的 recipe plan;add 需要 --recipe。
bash
tslink apps share jellyfin
tslink apps share jellyfin --yes

管理人员的私有 HTTP/文件访问。TCP 与公共 Funnel 无法按人限制。见人员分享。

为新访问者授权,--apps 必填。首次授权收紧应用范围。--invite 创建逐应用邀请,--print-links 展示 bearer link。本地授权不需 token;创建邀请需用户自己的 API token。

Flag说明
--apps私有 HTTP/文件应用,逗号分隔,或当前支持应用的 all;不含未来应用。
--for访问期限;预设 1h、8h、24h、3d、7d,支持相对或 until <date/time>;新人员默认 24h,update 省略时保留期限。Guest/公开拒绝 never。
--invite创建或续做逐应用 single-use device invitations,需用户自己的 API token。
--print-links明确展示 bearer invitation URL;需要 --invite。
bash
tslink people add alice@example.com --apps photos --for 7d
选项说明
--ack-never明确确认 tailnet 成员永久授权;device-invited guest 不允许。
--qr把准确 portal URL(portal 停用时首个有效应用)渲染成终端 QR。
--qr-invite改为指定应用的 bearer 邀请;需 --invite --print-links 加 --qr 或 --qr-png,QR 是凭证。
--qr-png把私有 QR PNG 写入已存在目录;JSON 只包含 payload 文本。
--until绝对截止时间:RFC3339、YYYY-MM-DD 或 YYYY-MM-DDTHH:MM,无 offset 时用本地时区;与 --for 互斥。

读取人员、授权、期限与撤销记录,不创建邀请。

修改应用范围、期限或明确续做邀请。只改应用时保留已有期限,新增应用默认 24h;--for 应用于所有选定应用。查看 complete 与逐应用 state/code。未知 POST 需主人核对后 reconcile;替换邀请保留授权和期限。

Flag说明
--apps私有 HTTP/文件应用,逗号分隔,或当前支持应用的 all;不含未来应用。
--for访问期限;预设 1h、8h、24h、3d、7d,支持相对或 until <date/time>;新人员默认 24h,update 省略时保留期限。Guest/公开拒绝 never。
--invite创建或续做逐应用 single-use device invitations,需用户自己的 API token。
--print-links明确展示 bearer invitation URL;需要 --invite。
--reconcile-invite主人核对后指定 app=id 或 app=none 解决未知 POST;update 需 --invite。
--replace-invite主人确认 app=旧记录ID,远端证实缺失后替换;需 --invite,不能同时 --apps 或 --for。
选项说明
--ack-never明确确认 tailnet 成员永久授权;device-invited guest 不允许。
--qr把准确 portal URL(portal 停用时首个有效应用)渲染成终端 QR。
--qr-invite改为指定应用的 bearer 邀请;需 --invite --print-links 加 --qr 或 --qr-png,QR 是凭证。
--qr-png把私有 QR PNG 写入已存在目录;JSON 只包含 payload 文本。
--until绝对截止时间:RFC3339、YYYY-MM-DD 或 YYYY-MM-DDTHH:MM,无 offset 时用本地时区;与 --for 互斥。

先保存本地拒绝,再清理待接受邀请。没有 token 也能拒绝;远端清理可能未完成,已接受的网络分享可能保留。新 HTTP/文件请求被拒绝,已有流可继续。

Flag说明
--reconcile-invite主人核对后指定 app=id 或 app=none 解决未知 POST;update 需 --invite。

访问流程与历史

读取保留的历史与汇总,不调用远程 API、不创建文件/锁。见访问历史。

选项类型 / 默认值说明
--appstring按应用/服务过滤。
--decisionstring过滤 allowed 或 denied。
--limitint / 100返回事件数量 1..10000;summary 覆盖所有匹配,默认 100。
--sincestring正 Go duration(如 24h)或 RFC3339 下界。
--untilstringRFC3339 上界,包含端点。
--whostring按 login、节点名或 tag 过滤。

设置单应用路径隐私。全局 off 优先,full 可能保存敏感应用路径,inherit 清除本地覆盖。

把一个人员授权(--person)或 Funnel(省略)设为当前时间加时长,始终返回 JSON。到期需 --regrant,撤销仍拒绝。见期限。

选项类型 / 默认值说明
--ack-neverbool / false明确确认 tailnet 成员永久授权;device-invited guest 不允许。
--forstring访问期限;预设 1h、8h、24h、3d、7d,支持相对或 until <date/time>;新人员默认 24h,update 省略时保留期限。Guest/公开拒绝 never。
--personstring选择人员在此服务上的授权;省略时选择 Funnel。
--regrantbool / false明确重新激活已到期授权或 Funnel;人员撤销仍生效。
--untilstring绝对截止时间:RFC3339、YYYY-MM-DD 或 YYYY-MM-DDTHH:MM,无 offset 时用本地时区;与 --for 互斥。

管理单个 HTTP proxy 应用的浏览器 guest 授权,公网 Funnel 必须有访客 gate。

--for 必填,启用 gate 时显式 --public。--print-link 前需私有应用在线,只展示一次 bearer 链接;拒绝 file/TCP 与已有开放 Funnel。见访客链接。

选项类型 / 默认值说明
--forstring必填,按共同期限策略选择;预设 1h、8h、24h、3d、7d,访客/公开拒绝 never。
--labelstring主人给链接的备注,不代表身份。
--pinbool / false从隐藏终端输入或 stdin 读取 PIN,不得放入 argv。
--print-linkbool / false明确返回一次 bearer 链接与可发送消息。
--publicbool / false确认通过 Funnel 暴露公网,并强制访客认证。

列出不含秘密的授权、期限和本地使用估计,不返回 bearer token。

永久撤销该 ID,取消受跟踪的 guest 流;已经接受的有界请求可能完成。

检查单个授权,不恢复链接或 PIN。

Owner 读取有界 intent/completion receipt;缺 completion 表示结果未知,不保证审计完整。

管理每台主机的私有 portal,不汇总多主机、不改变应用注册。

只关闭 portal 监听,保留入网状态及 owner/admin 身份。

--owner 必填,保存私有 portal 配置,由 daemon 应用。用 status --urls 获取准确运行 URL。远程 MCP 还需当前 portal owner;首次设置/恢复需本地进行。

选项类型 / 默认值说明
--adminsstringSlice / []额外管理员 login;这些身份可打开所有私有应用。
--funnelbool / false明确拒绝:portal 必须保持 tailnet-only。
--hostnamestring / homePortal 节点 hostname,默认 home。
--ownerstring主人实际的 Tailscale login,必填。

查看真人 tailnet 成员从 portal 发出的访问请求。见portal 与请求。

--for 必填,按主人选择的期限授权单应用;相同重试不续期。远程 owner/people-manager 还需当前 portal owner、应用/期限范围;精简角色要求人员已存在。

选项类型 / 默认值说明
--ack-neverbool / false明确确认 tailnet 成员永久授权;device-invited guest 不允许。
--forstring必填,按共同期限策略选择;预设 1h、8h、24h、3d、7d,访客/公开拒绝 never。

拒绝一个请求,相同重试返回原决定;远程调用有相同 portal-owner 检查。

选项类型 / 默认值说明
--reasonstring可选拒绝原因,最多 500 个字符,会展示给访问者。

读取持久 inbox,保留维护可能写入;备注是不可信数据。

普通命令继承 --json;extend 始终返回 JSON。MCP stdout 专用于协议 frames。

目录

概述认证tslink logintslink logout服务管理tslink add <名称>tslink remove <名称>tslink listtslink list --tailnettslink share <path|port|host:port>tslink url <name>网关tslink servetslink stoptslink status标签管理tslink tagstslink tags listtslink tags pulltslink tags add <服务> <标签>tslink tags set <服务> <标签>tslink tags set-default <标签>tslink tags delete-remote <标签> --force --manage-acl维护tslink cleanuptslink registrytslink registry check [path]邀请tslink invitetslink invite user <email>tslink invite device <service> <email>tslink invite listtslink invite revoke <id> --kind <user|device>tslink invite resend <id> --kind <user|device>诊断、访问解释与模板tslink doctortslink access explaintslink template listtslink template showtslink template apply配置tslink configtslink logs系统tslink installtslink uninstall编程式访问tslink mcptslink manifestRoadmap / Experimental 命令和字段全局标志退出码JSON 输出格式应用与访问者tslink appstslink apps listtslink apps detecttslink apps sharetslink peopletslink people addtslink people listtslink people updatetslink people remove访问流程与历史tslink access logtslink access path <app> <prefix|full|off|inherit|true|false>tslink extend <service>tslink guesttslink guest create <app>tslink guest listtslink guest revoke <id>tslink guest show <id>tslink mcp-audittslink portaltslink portal disabletslink portal enabletslink requeststslink requests approve <id>tslink requests deny <id>tslink requests list