headscale 全解析

安装

参考文章:

前置动作为安装 docker 和 docker-compose,以前的文章写过,就不重复造轮子了。

  1. 新建 docker 用 headscale 文件结构,下文以 root 用户默认目录为例

    1
    2
    3
    4
    mkdir -p /root/docker-compose/headscale/config
    cd /root/docker-compose/headscale/config
    touch ./config/db.sqlite
    curl https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml -o ./config/config.yaml
  2. 修改 /root/docker-compose/headscale/config 下的 config.yaml

    • server_url 改为公网 IP/域名及对外端口。这关乎到后续打开网页和 pc 修改完注册表后链接到服务器登录时弹出的网页 url,如果不设置成正确的公网 url,后续弹出的地址就不能用。

    • listen_addr 不用改,这一步和用非 docker 版不一样,因为这个参数所对应的是 docker 内部端口,后续是通过 docker 的映射映射到真正的对外端口的。

    • magic_dns 设为 false,防止连不了网。

    • 自定义私有网段(可选),便于记忆:

      1
      2
      ip_prefixes:
      - 10.1.0.0/16
  3. /root/docker-compose/headscale/ 目录下新建并编辑 docker-compose.yml,只需要修改下面配置文件的对外端口一项,与前面 config.yamlserver_url 设置的端口一致即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    version: '3.5'
    services:
    headscale:
    image: headscale/headscale:latest
    volumes:
    - ./config:/etc/headscale/
    - ./data:/var/lib/headscale
    ports:
    - [server_ports]:8080
    command: headscale serve
    restart: unless-stopped
  4. 确保 ssh 当前目录为 headscale 的 docker-compose 文件所在目录

    1
    cd /root/docker-compose/headscale

    启动

    1
    docker-compose up -d

配置

我只有 windows 和 android 端的设备,其实我还有个 ipad,但听说 headscale 暂时不支持 ios,那就算了。

windows(23.10.23 更新)

  1. windows 下载 安装包

  2. 打开链接 http://[server_url]:[server_ports]/windows,里面方括号括起来的两个内容自己就是我们上面 config.yaml填的 server_url 配置啦。

  3. (23.10.23 更新) 新版本这里会提示使用命令进行注册,原有的注册表配置方式已经废弃。只需要把页面中的这句命令行

1
tailscale login --login-server [server_url]

输入到 windows 这边的 cmd 里,就会自动跳到下一步。

3. 接下来弹出的界面就是 windows 端的教程。

这张图片里面其实简述了两种方法:

1. 下载 reg
2. 在 cmd 中输入两行命令

这两种方法其实都可以。我想说的是,如果之前配置过 headscale,但现在换了节点的话,可以打开 regedit,导航到 HKLM\Software\Tailscale IPN,修改其中 LoginURL 的值即可。

  1. 此时重新打开 tailscale,右键任务栏的图标后,会弹出注册页面。注册页面会显示让你到 vps 中粘贴命令:

1
headscale -n NAMESPACE nodes register --key xxxxxxx

但实际上我们需要改装一下这条命令。

首先在 vps 中创建一个 node。同样的,确保 ssh 当前目录为 headscale 的 docker-compose 文件所在目录,然后运行命令新建 node。

1
docker-compose exec headscale headscale namespaces create [节点名字]

改造上面的命令,将 windows 客户端加到节点中

1
2
3
-n已过期
docker-compose exec headscale headscale -n [节点名字] nodes register --key xxxxxx
docker-compose exec headscale headscale -u [节点名字] nodes register --key xxxxxx

查看效果,如果添加的节点显示 online,则成功

1
docker-compose exec headscale headscale nodes list

另外这个命令也会显示当前链接设备的 headscale 内局域网 ip。

android

android 端,需要编译 apk,可以通过 github action 下一个最新的,或者再 fork 一份然后自己跑一遍。安装后右上角三个点打开然后把命令考出来像 windows 一样改造后放入 vps,此处不再赘述。最新版的 apk(1.3 以上),疯狂点击 about 就会出现自定义服务器的选项,填入 http://[server_url]:[server_ports],重启即可看到命令。

(230514 更新) asus merlin (仅管理界面)

慎用。似乎会搞坏路由器的 dns。

  1. 安装 entware,然后通过 entware 安装 tailscale(安装 entware 的教程请见往期文章)

    1
    opkg install tailscale
  2. 启动 tailscaled。要对 tailscale 进行配置需要先启动 tailscaled,另外当 ssh 登出时,tailscaled 就会自动停止,但 tailscale 配置好后会开启自启并持续运行。

    1
    /opt/etc/init.d/S06tailscaled start
  3. 配置 tailscale。

    1
    tailscale up --login-server=http://<HEADSCALE_PUB_IP>:8080 --accept-routes=true --accept-dns=false

    此时 ssh 中会弹出一个网址,访问后会获得要到 vps 中输入的命令,参考上面 windows 端的操作,不再赘述。

  4. 配置防火墙。打开/新建 /jffs/scripts/firewall-start,在 #/bin/sh 下面新增两行:

    1
    2
    iptables -I INPUT 1 -s 10.1.0.0/24 -j ACCEPT
    iptables -t nat -A PREROUTING -d 10.1.0.3 -j DNAT --to-destination 192.168.123.1

    10.1.0.0/24 放 tailscale 网段地址。192.168.123.1 放内网访问到路由器管理界面的地址。

此时就在外网可以通过路由器的 tailscale 地址访问路由器管理界面的地址了。

其他延伸配置(23.10.23 更新)

反代 https

如果没有装有 web 服务器,可以用 headscale 内置的 https 功能。但我已经装有 caddy 了,直接用 caddy 就好。

参考文章:headscale/docs/reverse-proxy.md at main · juanfont/headscale

  1. 修改 /root/docker-compose/headscale/config 下的 config.yaml,把 server_url 改成 https 地址,端口不留,因为默认 https 就走 443 端口。

  2. 修改 caddy 配置文件 Caddyfile,一般位于 /etc/caddy/Caddyfile,增加配置(此处我已经申请过泛域名证书,就不使用 caddy 自己的自动申请域名方法了)

    1
    2
    3
    4
    https://xxx.xxx.xyz {
    reverse_proxy localhost:[server_ports]
    tls /etc/ssl/xxx.crt /etc/ssl/xxx.key
    }
  3. 然后重启 caddy 和 docker-compose headscale 即可。

更换 headscale nodes list 里的节点 id 和 ip

参考文章:自定义/更改 IP 地址 · 问题 #1455 · juanfont/headscale

安装 sqlite。

1
apt install sqlite -y

找到 /root/docker-compose/headscale/data/ 下的 db.sqlite 文件。使用 sqlite3 打开并编辑。

1
2
3
4
5
6
sqlite3 /var/lib/headscale/db.sqlite
查看有几个表
.table
SELECT id, hostname, ip_addresses FROM machines;
UPDATE machines SET ip_addresses = "10.1.0.2" WHERE hostname = "[主机名]";
UPDATE machines SET id = "1" WHERE hostname = "[主机名]";

重启被修改 ip 的客户端即可。

修改 randomize_client_port

如果访问不正常,可以尝试修改。参考文章:Headscale 端到端直连 - Yogile - 博客园

修改 /root/docker-compose/headscale/config 下的 config.yaml

randomize_client_port :原为 false ,须改为 true 。否则客户端端点访问互相干扰,无法访问服务。

使用 preauthkeys 无需服务器同意一键添加设备

新建 key

1
docker-compose exec headscale headscale --user zbttl preauthkeys create --reusable --expiration 24h

查看新建的 key,记下 key 的值

1
docker-compose exec headscale headscale -u [节点名字] preauthkeys list

在客户端处输入命令,以 windows 为例:

1
tailscale up --auth-key=[key_value] --login-server=[server_url] --accept-dns=false --unattended

tailscale 会即刻登录,无需进入服务器使用命令二次确认。

然后让 key 过期

1
docker-compose exec headscale headscale --user [节点名字] preauthkeys expire [key_value]

使用 headscale ui 并配置 Ephemeral 供临时使用

参考文章:

啥是 Ephemeral 呢?参考临时节点 · Tailscale 文档,简而言之,上一步我们用 preauthkeys 添加设备时候用的节点,变成了一次性的,使用者登录后只要登出(比如重启 tailscale,重启电脑等),节点信息就会失效,就连不上 headscale 控制器了,适合短期的给临时设备使用。要方便的使用这个功能,甚至方便的进行设备的添加删除,查看在线的设备而无需进入 ssh,都可以搭建这么一个 headscale-ui 在前端。

(可选,便于验证配置文件是否工作正常)config.yaml 中的 metrics_listen_addr 端口映射出来,默认为 9090。因为用的是 docker,还需要把这里的地址修改为 0.0.0.0 以被访问到

1
metrics_listen_addr: 0.0.0.0:9090

然后修改 headscale 映射的端口,还需要添加 headscale-ui 配置到 headscale 的 docker-compose 文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
version: '3.5'
services:
headscale:
image: headscale/headscale:latest
volumes:
- ./config:/etc/headscale/
- ./data:/var/lib/headscale
ports:
- [server_ports]:8080
- [metric_ports]:9090
command: headscale serve
restart: unless-stopped
headscale-ui:
container_name: headscale-ui
image: ghcr.io/gurucomputing/headscale-ui:latest
pull_policy: always
restart: unless-stopped
ports:
- [headscale_ports]:80

重启 docker-compose 后即可通过 ip:[metric_ports]/metrics 访问到访问到 metrics 页面,然后通过 ip:[metric_ports]/headscale_ports 不出意料也能进入 ui 页面。

此时我们在 ssh 处申请一个 api:

1
docker-compose exec headscale headscale apikeys create

记住这里的 api。因为后面无法再获取这个 api,如果忘记了只能重新获取;我们待会就要用这个 api 登录网页端,更换设备,清除 cookies 都需要重新输入这一串长长的随机 api。所以建议保存起来。

在网页端的 settings->headscale api key 中填入刚刚获取的 api。在 user view 中就能看到我们创建的 headscale 节点名字了。device view 处则能管理已授权的设备。

在 user view 处点开节点,点 preauth keys 前面的加号

此时能看到三个选项,分辨是 key 的过期时间、是否可以重复使用、是否为 Ephemeral key(即我上面说的临时 key),选择需要的配置,点击 create preauth key 即可创建成功。将 key 套入命令中在命令行里输入即可成功添加,上一章说过了,不再赘述。

反代 https

当然 headscale-ui 和 metrics 也可以通过 caddy 反代了。另外为了安全我还在 headscale-ui 页面外面套了一层用户和密码。修改 caddyfile:

1
2
3
4
5
6
7
8
9
10
https://xxx.xxx.xyz {
reverse_proxy localhost:[server_ports]
tls /etc/ssl/xxx.crt /etc/ssl/xxx.key
reverse_proxy /metrics* 127.0.0.1:[metric_ports]
reverse_proxy /web* 127.0.0.1:[headscale_ports]
basicauth /web* {
# 加密密码获取方法:caddy hash-password,输入密码
[username] $2a$14$/rQHsSabPqQ3zpnGtU2ZQeGSBYozSCwA5HLUEwwp78OEhQVwECGyC #123456
}
}

重启 caddy,即可用 https://xxx.xxx.xyz/metricshttps://xxx.xxx.xyz/web` 访问 metrics 页面和 headscale-ui 页面了。

自定义 derp

参考文章:Tailscale 基础教程:部署私有 DERP 中继服务器 – 云原生实验室 - Kubernetes|Docker|Istio|Envoy|Hugo|Golang|云原生

上面说的只是搭建 headscale 控制器,而连接的流量节点是 tailscale 的那些默认服务器。也可以自建流量节点,我们称为 derp,不过一般用不着,如果特别卡的话,再说。

有两种方法,一种是 derp 和 headscale 控制器节点在同一台机器上,一种是无所谓在不在一台机器上都可以使用。不管那一种,需要开一个和 headscale 控制器不同的三级域名。

简易版自体 derp

参考文章:“嵌入式 DERP(和 STUN)服务器”提供哪些具体服务 · 问题 #1326 · juanfont/headscale

derp 和 tailscale 节点在同一台机器上的情况下,只需要调整 config.yaml 的 derp 相关配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
derp:
server:
# 把这个改成true
enabled: true

# 改不改无所谓
region_id: 999

# 在 tailscale 命令中看到的自己的 derp 的名字和,以及 derp 的全名
region_code: "rn_emb"
region_name: "racknerd DERP"

# 待会在 docker-compose 中需要开放的端口
stun_listen_addr: "0.0.0.0:3478"

然后修改 docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
version: '3.5'
services:
headscale:
image: headscale/headscale:latest
volumes:
- ./config:/etc/headscale/
- ./data:/var/lib/headscale
ports:
- [server_ports]:8080
- '3478:3478/udp'
command: headscale serve
restart: unless-stopped

最后重启 docker-compose

1
2
docker-compose down
dockekr-compose up -d

在客户端处使用 tailscale netcheck 检查,出现 derp 节点即为成功。

通用 derp 配置

参考文章:slchris/derp-server:Tailscale/Headscale derp 服务器

需要配置一个 docker 的 derp 容器。网上有挺多教程用的是单独开的 derp 域名然后用这个域名去申请证书,申请到的证书名字前缀命名和域名一致,或者用证书工具来申请。但我还是照例,用我之前申请的泛域 ssl 证书。新建一个 derp 文件夹,里面放上 docker-compose.yml 文件。假设我的 derp 域名为 derp.xxx.xyz:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '3.3'
services:
derp-server:
restart: always
container_name: derper
ports:
- 12345:443
- '3478:3478/udp'
volumes:
- '/etc/ssl/xxx.crt:/app/certs/derp.xxx.xyz.crt'
- '/etc/ssl/xxx.key:/app/certs/derp.xxx.xyz.key'
environment:
- DERP_CERT_MODE=manual
- DERP_DOMAIN=derp.xxx.xyz
image: 'ghcr.io/slchris/derp-server:v1'

启动容器 docker-compose up -d,访问 https://derp.xxx.xyz:12345 查看 12345 端口是否能访问。

然后修改 config.yaml 文件的 derp 部分,其实只用改那个 paths 就行,另外提一嘴,如果只需要用自建的 derp 而不需要 tailscale 提供的公共 derp,只需要注释掉 urls 里面的那行网址就行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
derp:
server:
enabled: false

region_id: 999

region_code: "headscale"
region_name: "Headscale Embedded DERP"

stun_listen_addr: "0.0.0.0:3478"

urls:
# 只想用自建 derp 的话,注释掉下面这行
- https://controlplane.tailscale.com/derpmap/default

paths:
# 只需要加这句
- /etc/headscale/derp.yaml

config.yaml 文件夹内新增一个 derp.yaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
regions:
# 改不改无所谓
900:
regionid: 900
# 在 tailscale 命令中看到的自己的 derp 的名字和,以及 derp 的全名
regioncode: rn
regionname: racknerd American
nodes:
- name: 900a
regionid: 900
hostname: derp.baobaobao.xyz
stunport: 3478
stunonly: false
derpport: 12345

最后重启 docker-compose

1
2
docker-compose down
dockekr-compose up -d

在客户端处使用 tailscale netcheck 检查,出现 derp 节点即为成功。

关于两种方法的混用问题

这是一个小发现:如果使用了第一种方法通过 docker 把 3478 端口透出来了,那么第二部如果是在同一台机子上,derp 容器是无法启动的。但反过来,自体 derp 如果没有映射 3478 端口的话,使用 tailscale netcheck 来看节点是没有启动的(没有 ping 值),但!此时我们启动 derp 容器,不用再做后面修改 config.yamlderp.yaml 的步骤,会发现节点也连上了。利用这种方法部署,也未尝不可。

防止 DERP 被滥用

从配置上看,人家只要拿到我们的 derp 网址,探测到端口就可以白嫖了(虽然还是有一定门槛)。有什么办法可以阻拦一下呢?还真有,那就是客户端需要连上和 derp 一样的 headscale 节点才能使用 derp,然后开启一个叫 DERP_VERIFY_CLIENTS 的参数。修改 derp 节点的 docker-compose.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
version: '3.3'
services:
derp-server:
restart: always
container_name: derper
ports:
- 12345:443
- '3478:3478/udp'
volumes:
- '/etc/ssl/xxx.crt:/app/certs/derp.xxx.xyz.crt'
- '/etc/ssl/xxx.key:/app/certs/derp.xxx.xyz.key'
environment:
- DERP_CERT_MODE=manual
- DERP_DOMAIN=derp.xxx.xyz
# 加这一行就行
- DERP_VERIFY_CLIENTS=true
image: 'ghcr.io/slchris/derp-server:v1'

如果 derp 和 headscale 控制器不在一起的话,就需要把 derp 所在的这个节点也用 tailscale 客户端连上自己的 headscale 控制器才可以。可以使用 apt 等工具包安装 tailscale,也可以使用 docker 安装 tailscale 客户端,这里我使用后者,新建一个 docker-compose.yml 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
version: '3.1'

services:
Headscaled:
container_name: Headscaled_Client
image: tailscale/tailscale
network_mode: host
privileged: true
restart: always
cap_add:
- net_admin
- sys_module
volumes:
- ./lib:/var/lib
- /dev/net/tun:/dev/net/tun
command: sh -c "mkdir -p /var/run/tailscale && ln -s /tmp/tailscaled.sock /var/run/tailscale/tailscaled.sock && tailscaled"

启动 docker-compose up -d

用 ui 配置一个一次性的 key,然后用 key 连接到 headscale:

1
docker-compose exec Headscaled tailscale up --auth-key=[key_value] --login-server=[server_url] --accept-dns=false

最终完全版 headscale 控制器参考配置

开启 headscale-ui,https 反代,derp。

docker-compose.yml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
version: '3.5'
services:
headscale:
image: headscale/headscale:latest
volumes:
- ./config:/etc/headscale/
- ./data:/var/lib/headscale
ports:
- 10443:8080
- 9090:9090
- '3478:3478/udp'
command: headscale serve
restart: unless-stopped
headscale-ui:
container_name: headscale-ui
image: ghcr.io/gurucomputing/headscale-ui:latest
pull_policy: always
restart: unless-stopped
ports:
- 10444:80

caddyfile:

1
2
3
4
5
6
7
8
9
https://xxx.xxx.xyz {
reverse_proxy localhost:10443
tls /etc/ssl/xxx.crt /etc/ssl/xxx.key
reverse_proxy /metrics* 127.0.0.1:9090
reverse_proxy /web* 127.0.0.1:10444
basicauth /web* {
me $2a$14$/rQHsSabPqQ3zpnGtU2ZQeGSBYozSCwA5HLUEwwp78OEhQVwECGyC #123456
}
}

config.yaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
server_url: https://xxx.xxx.xyz
listen_addr: 0.0.0.0:8080
metrics_listen_addr: 0.0.0.0:9090
grpc_listen_addr: 0.0.0.0:50443
grpc_allow_insecure: false
private_key_path: /var/lib/headscale/private.key
noise:
private_key_path: /var/lib/headscale/noise_private.key
ip_prefixes:
- 10.1.0.0/16
derp:
server:
enabled: true
region_id: 999
region_code: "my"
region_name: "my DERP"
stun_listen_addr: "0.0.0.0:3478"
urls:
- https://controlplane.tailscale.com/derpmap/default
paths: []
auto_update_enabled: true
update_frequency: 24h
disable_check_updates: false
ephemeral_node_inactivity_timeout: 30m
node_update_check_interval: 10s
db_type: sqlite3
db_path: /var/lib/headscale/db.sqlite
acme_url: https://acme-v02.api.letsencrypt.org/directory
acme_email: ""
tls_letsencrypt_hostname: ""
tls_client_auth_mode: relaxed
tls_letsencrypt_cache_dir: /var/lib/headscale/cache
tls_letsencrypt_challenge_type: HTTP-01
tls_letsencrypt_listen: ":http"
tls_cert_path: ""
tls_key_path: ""
log:
format: text
level: info
acl_policy_path: ""
dns_config:
nameservers:
- 1.1.1.1
domains: []
magic_dns: false
base_domain: example.com
unix_socket: /var/run/headscale.sock
unix_socket_permission: "0770"
logtail:
enabled: false
randomize_client_port: true

注意事项

  1. 不要在国内云上架设 headscale!试过秒封(良心云,直接封机子,申诉还要上传身份证)。

  2. windows 端,如果发现使用局域网速度缓慢,或者连接不上,可以通过:

    • disconnect 再 connect,重新刷新链接
    • 使用命令 tailscale netchecktailscale status 查看连接情况。
  3. 如果有哪个节点配置错误或需要删除,可以通过下面的命令删除

    1
    2
    3
    #已过期
    #docker-compose exec headscale headscale nodes delete yyyy
    docker-compose exec headscale headscale nodes delete -i [节点序号]

    yyy 的内容就是 docker-compose exec headscale headscale nodes list 第三列显示的设备名(即 Name)。

其他参考文章(仅留档)