Hand a phone its configuration.把配置交给手机。
Type your target below and this page makes two things: a
dshbyok://setup link, and a QR code for it. Scan the code or open
the link on the phone, and the app shows what it would change and waits for
you to confirm.在下面填上你的目标,这个页面会做两件事:生成一条
dshbyok://setup 链接,以及它的二维码。用手机扫码或直接打开这条链接,
App 会先告诉你它将要改动什么,等你确认之后才生效。
Nothing here is sent anywhere. There is no analytics, no font, no script from
another host — the page draws the QR code with a vendored encoder
(qr.js, MIT). The address field is prefilled from the URL
fragment (the part after #), which browsers never transmit,
so a link to this page cannot leak the address it carries to this site or to
anyone watching the network.这里的东西不会发送到任何地方。没有统计、没有外部字体、没有来自其它主机的脚本
—— 二维码是页面用仓库内自带的编码器(qr.js,MIT)自己画出来的。地址栏的预填值来自
URL 的片段(# 之后的部分),浏览器从不发送片段,所以把这个页面的链接转给别人,
不会把里面的地址泄露给本站,也不会泄露给任何旁听网络的人。
Get the app first先装上应用#
This app is the client; the network is Tailscale, and
it has to be installed on both ends — the computer that runs DSH and the phone or tablet —
signed into the same tailnet. The computer's copy comes from
tailscale.com/download; the Android app is on
Google Play
(com.tailscale.ipn). Neither replaces the other: without Tailscale on both ends, the link below
has nowhere to go.这个应用是客户端,
网络那半是 Tailscale,而且两端都要装 —— 跑 DSH 的电脑,和手机或平板 —— 并登录同一个
tailnet。电脑端去 tailscale.com/download 下载;安卓端在
Google Play(包名
com.tailscale.ipn)。两者谁也替代不了谁:两端没有 Tailscale,下面这条链接就没有地方可去。
Tailscale's package server — the file itself. Google Play and
tailscale.com do not load everywhere (mainland networks especially), and the package server is
the official place that hands out the APK file instead of a page:
tailscale-android-universal-1.102.4.apk
— universal build, 100.6 MB, straight from Tailscale. Two things to know: it is the
stable track (Play ships newer — the maintainer runs 1.103.90 from Play), and it
does not update itself, so install a newer one by hand when you want it. The English page it
comes from is pkgs.tailscale.com/stable/#android.Tailscale 包服务器 —— 直接给文件。Google Play 和
tailscale.com 不是哪儿都能打开(国内网络尤其),而包服务器是官方唯一直接给 APK 文件、
不需要你先打开一个网页的地方:tailscale-android-universal-1.102.4.apk
—— 通用包,100.6 MB,来自 Tailscale 自己。两点要知道:它是 stable 通道
(Play 上的更新,维护者用的就是 Play 的 1.103.90),而且它不会自动更新,想要新版本得自己再装一次。
它来自的那个英文页面是 pkgs.tailscale.com/stable/#android。
And if reading that page in English is the annoying part, there is a Chinese translation of exactly that section here: Tailscale for Android, from the package server — same file, same requirements, same caveats, in Chinese.如果嫌那一页是英文, 这里有一份同一节的中文说明:Tailscale 安卓版:从官方包服务器安装 —— 同一个文件、同样的要求、同样的注意事项,中文。
Everything below only means something to this app, so the hand-off is two things: the
APK, and a link or a code from this page. The APK is published as a release — one file for everybody, with
nobody's address baked into it. It is a single arm64 build for Android 8.0 (API 26) or
newer.下面生成的东西只有这个应用认得,所以要交到对方手上的就是两样:
安装包,以及本页生成的链接或二维码。安装包以 Release 发布 —— 所有人拿到的是同一个文件,里面没有预置任何人的地址。
它是单一的 arm64 构建,要求 Android 8.0(API 26)或更高。
Download the APK (arm64, mirror)下载安装包(arm64,镜像) GitHub release linkGitHub 发布链接 sha256sha256 Release notes发布说明
Both download buttons serve the same bytes — the mirror is this site's
own copy of the release asset, published under byok/ because GitHub's release storage is slow or
unreachable from some networks while this page's own host is fine — the two files come from
different places, which is why there are two buttons). It is refreshed automatically whenever a release is published, and the
sha256 link next to it is the checksum of exactly those bytes — compare it before installing if
you got the file from anywhere else.两个下载按钮给的是同一份字节 ——
镜像就是本页所在站点自己保存的那份发布资产,放在 byok/ 下。两份文件来自不同的主机:GitHub 的发布存储
在部分地区很慢甚至打不开,而本页所在的主机正常,这就是有两个按钮的原因。它会在每次发布新版本时自动刷新,
旁边的 sha256 链接就是这份字节的校验值 —— 如果你是从别处拿到的文件,装之前先对一下。
The newest published release is v0.4.6 — about 35 MB, arm64 — one file for everybody, with nobody's address baked into it. If both links crawl, whoever runs the server can hand the same file over directly. This page is rebuilt on every release, so the version above is the one the buttons serve.最新发布的版本是 v0.4.6,约 35 MB,arm64 —— 所有人同一个文件,里面没有预置任何人的地址。 如果两条链接都慢得离谱,服务器主人可以直接把同一个文件交给你。本页每次发版都会重建, 所以上面这个版本号就是按钮给的那一份。
Android asks once whether installs from this source are allowed. The app requests no storage and no contacts, and it talks to the server you point it at and to nothing else. (The QR scanner asks for the camera at the moment you open it; refusing costs nothing, because a code can also be read from a picture.) If the phone cannot reach GitHub at all, the mirror button above serves the same bytes from this page's own host; failing that too, whoever runs the server hands the same APK over directly — that file plus a link from this page is the whole hand-off.Android 会问一次是否允许来自这个来源的安装。这个应用不要存储、 不要通讯录,只跟你指定的那台服务器说话。(二维码扫描在你打开它的时候才会申请相机权限;拒绝也没有代价, 因为也可以用图片识别。)如果手机完全连不上 GitHub,用上面那个镜像按钮,它从本页所在的站点给出同一份字节; 连它也不行时,由服务器主人直接把同一个安装包交给对方 —— 一个安装包加上本页生成的一条链接,就是全部所需。
Make a link生成链接#
Scan it扫码#
The link链接
Any camera or QR app will open it: the scheme is dshbyok, so
Android asks which app should handle it. Nothing installs and nothing changes
until the app itself applies it. Reading this page on the phone
itself? Tap the link above instead of scanning your own screen.任何相机或扫码 App 都能打开它:协议是 dshbyok,所以
Android 会问你要交给哪个 App 处理。在 App 自己应用之前,什么都不会安装、什么都不会改变。
你就是在手机上打开这个页面? 直接点上面的链接,不用扫自己的屏幕。
The app reads this code itself, too: its settings screen has a scanner for exactly this link, and the same screen can read a code from a picture you pick, with no camera at all. It also draws this phone's configuration as a code, so a second phone can be handed the same target the same way. A code is never applied by itself, whichever direction it travels.这个码 App 自己也能读:它的设置页里就有扫这条链接的扫描器,同一个页面还能直接识别你选的一张图片, 完全不用相机。它也会把本机当前的配置画成一个码,方便用同样的方式把同一个目标交给第二台手机。 无论往哪个方向走,码都不会自己生效。
What a link may say — and what it may never say链接可以说什么 —— 以及绝不可以说什么#
Configuration only只带配置
A link may carry: target, port, scheme,
path, mode, update,
control, node. The app shows the merged result
before applying it, and refuses a link whose target its address policy would
not let you type by hand.链接可以携带:target、port、scheme、
path、mode、update、
control、node。App 会先显示合并后的结果再应用;
如果某条链接的目标连手动输入都不被地址策略允许,它同样会被拒绝。
Never a credential绝不带凭据
If a link contains any field whose name looks like a credential —
authkey, key, token,
password, secret — the whole link is
refused, and the app says so. A link can be photographed off a
screen, so a secret in one is a secret in public. The auth key is typed into
the app, or obtained through the control plane's own login.只要链接里出现任何名字像凭据的字段 ——
authkey、key、token、
password、secret —— 整条链接都会被拒绝,
并且 App 会说明原因。链接是可能被从屏幕上拍走的,所以链接里的秘密就等于公开的秘密。
认证密钥请在 App 里输入,或通过控制面自己的登录流程取得。
Never silent绝不悄悄生效
A link cannot re-point the app by itself. That is the attack worth designing against — a QR code that quietly aims the app at a stranger's server, so the next thing you type is your key. The app therefore parses, shows, and waits: apply or ignore is your tap.链接本身无法把 App 改指向别处。这正是要防的那条攻击链 —— 一张二维码悄悄把 App 指向陌生人的服务器,你接下来输入的就是自己的密钥。所以 App 的做法是:解析、展示、 然后等你 —— 应用还是忽略,由你点。
Baking a target into your own build把目标写进你自己的构建#
The public build carries no address, on purpose. A private or branded build can pre-fill one, and then nobody has to type anything:公开构建里不带任何地址,这是有意为之。私有构建或定制品牌的构建可以预填一个地址, 这样谁都不用再手输:
./gradlew assembleDebug -PdefaultTarget=phone.tailnet-name.ts.net:3080 -PdefaultMode=system
The value only *defaults* the first screen — it is never a policy, and once the user saves anything the stored value wins. Those properties are also run through the same parser a link is, so a malformed value degrades to "no default" rather than shipping a broken address. Nothing is committed: the public build's values are empty, and CI checks that they stay that way.这个值只是首次进入时屏幕上的默认值 —— 它永远不是策略;用户一旦保存过任何设置, 就以保存的值为准。这些属性同样会走与链接完全相同的解析器,所以写坏的值会退化成「没有默认值」, 而不是发布一个坏地址。仓库里什么都不提交:公开构建的这三个值恒为空,CI 也会一直检查它们保持为空。
More detail, including the full field table and the reasoning: docs/PROVISIONING.md.更详细的说明(完整字段表与设计理由)见 docs/PROVISIONING.md。