Skip to content

Instantly share code, notes, and snippets.

@zhangguanzhang
Created January 25, 2025 01:40
Show Gist options
  • Save zhangguanzhang/db9f6862eef4c82f6918852953a48458 to your computer and use it in GitHub Desktop.
Save zhangguanzhang/db9f6862eef4c82f6918852953a48458 to your computer and use it in GitHub Desktop.
headscale部署文章存档
@zhangguanzhang
Copy link
Author

v0.22.3

headscale 部署和设置

参考官方文档 running-headscale-linux-manual ,因为 headscale 不依赖 CGO,所以我使用二进制部署,官方这个文档也是隐藏起来的,怎么部署都可以,不一定要和我一样。

截至 2024/07/25 ,从官方仓库看,正式的 release 版本是 v0.22.3,而 main、pre-release 版本的配置文件和老版本文件有一些不兼容的字段,很多文章都是从 main 下载 config-example.yaml 文件后按照他们文章修改字段对应不上。

# 自行解决下载问题
wget -O /usr/local/bin/headscale https://github.com/juanfont/headscale/releases/download/v0.22.3/headscale_0.22.3_linux_amd64
# 有些老系统的 PATH 里没 /usr/local/bin/ ,可以放其他路径里
chmod a+x /usr/local/bin/headscale

mkdir /etc/headscale/

# 下载二进制同版本的示例配置文件
wget -O /etc/headscale/config.yaml https://raw.githubusercontent.com/juanfont/headscale/v0.22.3/config-example.yaml
#

创建 headscale daemon systemd 后台文件:

cat > /etc/systemd/system/headscale.service << EOF
[Unit]
Description=headscale controller
After=syslog.target
After=network.target

[Service]
Type=simple
User=headscale
Group=headscale
ExecStart=/usr/local/bin/headscale serve
Restart=always
RestartSec=5

# Optional security enhancements
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
WorkingDirectory=/var/lib/headscale
ReadWritePaths=/var/lib/headscale /var/run/headscale
AmbientCapabilities=CAP_NET_BIND_SERVICE
RuntimeDirectory=headscale

[Install]
WantedBy=multi-user.target
EOF

因为 headscale 是一个控制中心,不需要特权,我们运行在非 root 用户下,添加用户:

useradd \
  --create-home \
  --home-dir /var/lib/headscale/ \
  --system \
  --user-group \
  --shell /usr/sbin/nologin \
  headscale

mkdir -p /var/run/headscale/

#创建空的 SQLite 数据库文件和 derp 文件:
touch /var/lib/headscale/db.sqlite /etc/headscale/derp.yaml
chown -R headscale:headscale /var/run/headscale/ /var/lib/headscale
chmod a+r /etc/headscale/config.yaml /etc/headscale/derp.yaml

接下来 vi /etc/headscale/config.yaml 修改配置文件一些内容:

# server_url 写外网访问的 ip+端口
# 由于是ecs,所以写公网 IP,以及后面的端口要和 listen_addr 的一致
# 80 443 8080 需要备案
server_url: http://<ecs_public_ip>:8081
# 改为四个0,或者对应公网 IP 的内网网卡 IP 都行
listen_addr: 0.0.0.0:8081

# ip_prefixes 下的 ipv6 关闭了
ip_prefixes:
#  - fd7a:115c:a1e0::/48
# 如果使用 Aliyun ecs,ipv4 网段需要修改成其他的,因为 Aliyun 底层的 apt 源等都在这个范围内
# 如果 Aliyun ecs 修改此网段,建议看完本章文章再操作,因为后续 tailscale 要修改源码
  - 100.64.0.0/10

derp:
  server:
    # 不适用 headscale 内嵌的 derper 服务器
    enabled: false
  # 注释掉不使用官方的 derp 服务
#  urls:
#    - https://controlplane.tailscale.com/derpmap/default
# 把 paths: [] 修改成使用本地 derp 信息
  paths: [/etc/headscale/derp.yaml]

# 关闭 headscale 自动更新,避免 break change 无法启动
disable_check_updates: true

dns_config:
# 改为 false 不覆盖本地 DNS
  override_local_dns: false
  # 关闭 magic_dns
  magic_dns: false
  # 设置为你自己的标识,否则后续 tailscale 端连接上显示是 [email protected]
  base_domain: xxx

# 随机端口要打开, tailscale 客户端会使用41641 端口建立 wireguard 链接,这个端口会被中间网络设备阻止
randomize_client_port: true

安装和设置 headscale 补全,因为 headscale 是 daemon 和 cli 两部分,daemon 起来后 cli 很多命令可以操作:

apt update
apt install -y bash-completion
headscale completion bash > /etc/bash_completion.d/headscale
. /etc/bash_completion.d/headscale

启动 headscale daemon 进程:

# 测试文件
headscale configtest

headscale serve
# 配置文件没问题就 ctrl +c 取消掉使用 systemd 启动
chown -R headscale:headscale /var/lib/headscale
systemctl daemon-reload
systemctl enable --now headscale

derper 部署

headscale 是控制层面,下发信息和路由配置,而 derper 是中继和打洞服务器,利用修改版本的 stun 协议打洞,例如两个无公网 IP 但是可以访问到公网的客户端,客户端和另一个客户端建立连接都是先 连 derp 看看自己和对端能否打洞成功,成功就直连对方,否则就走 derper 中继来转发。

官方推荐的使用 https 绿锁证书部署,但是我们没有。并且官方不提供 derp 编译,所以需要 hack 和编译,相关文件在 compile-and-packages/tailscale,基于 alpine 镜像,并且能自动生成 https 证书。

这里使用编译好的 docker 镜像部署,二进制部署因为默认使用一些路径,所以坑比较少,这里使用 docker host 网络部署并说明一些东西。

先查看机器上的 iptables 模式:

$ iptables -w -V
iptables v1.4.21
iptables v1.8.4 (legacy)
#----
iptables v1.8.9 (nf_tables)

前者都是 legacy 模式,后者是 nf_tables,下面的 tailscale 需要设置 firewall-modeiptables 或者 nftables

cat > docker-compose.yml << EOF
services:
  derper:
    container_name: derper
    image: registry.aliyuncs.com/zhangguanzhang/derper:v1.70.0
    restart: unless-stopped
    network_mode: host
    volumes:
      - /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime:ro
      # 容器生成的证书存放,如果是自己的绿锁证书,存放文件名为
      # $DERP_DOMAIN.key $DERP_DOMAIN.crt
      - ./cert:/cert 
      -  /var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock:ro
    environment:
      DERP_DOMAIN: my.mydomain.no # 域名,由于不是绿锁 https,随意写,和后面 derp.yaml 一致即可
      DERP_ADDR: ':12345' # https 端口
      DERP_STUN_PORT: '3478' # udp port
      DERP_HTTP_PORT: '-1'
      DERP_VERIFY_CLIENTS: "true"
      DERP_CERT_DIR: /cert
    depends_on:
      - tailscale

# https://tailscale.com/kb/1282/docker
  tailscale:
    hostname: tailscale
    container_name: tailscale
    restart: unless-stopped
    network_mode: host
    image: docker.m.daocloud.io/tailscale/tailscale:v1.70.0
    cap_add:
      - NET_ADMIN
      - NET_RAW
      - sys_module
    # 禁用收集或发送任何日志数据,会发往 https://log.tailscale.io
    command: tailscaled -no-logs-no-support
    volumes:
      - /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime:ro
      - /dev/net/tun:/dev/net/tun
      - /lib/modules:/lib/modules
      - /run/xtables.lock:/run/xtables.lock:rw
      - /var/run/tailscale/:/var/run/tailscale/
      - ./tailscale_data:/var/lib/tailscale
    environment:
      #TS_EXTRA_ARGS: --advertise-tags=tag:container
      TS_HOSTNAME: ecs
      TS_DEBUG_FIREWALL_MODE: iptables # 使用和宿主机模式一致的
EOF

为了防止 derper 被白嫖,所以 官方文档 推荐开启 --verify-clients 选项,然后同时部署一个 tailscale 客户端,derper 会从这个 tailscale 的 sock 文件获取所有认证过的 peer 信息,所以上面挂载 sock 和 depends_on 以及使用同一版本的 derper 和 tailscale 。

修改 headscale 指定的 derp 文件 vi /etc/headscale/derp.yaml 的内容:

# https://github.com/tailscale/tailscale/blob/main/tailcfg/derpmap.go#L71
regions:
# 900-999 的 region id 是给预留的
# 有条件的可以多个 derper
  900:
    regionid: 900
    regioncode: custom
    regionname: custom_name
    nodes:
      - name: 900a
        regionid: 900
        hostname: my.mydomain.no
        ipv4: <ecs_public_ip>
        #ipv6: "2604:a880:400:d1::828:b001"
        derpport: 12345 # https 端口
        stunport: 3478 # udp port
        stunonly: false
        insecurefortests: true
  # 901:
  #   regionid: 901
  #   regioncode: hs 
  #   regionname: Huawei Shanghai 
  #   nodes:
  #     - name: 901a
  #       regionid: 901
  #       hostname: xxxx
  #       ipv4: xxxx
  #       stunport: 3478
  #       stunonly: false
  #       derpport: 12345
  #       insecurefortests: false

3年前 tailscale derp 就在 >= 1.20 的增加字段 derpportinsecurefortests

三年前的 headscale 版本是 0.14.0 里看到使用的 tailscale 版本是 v1.20.4,压根不存在网上一些 insecurefortests 只能写 json 里配置的说法(DERPPortInsecureForTests 都是 golang json tag,yaml 读取如果没 yaml tag 就使用 json tag),网上好多人互相抄袭还单独创建一个 web server 写 json 文件,我不理解。

docker-compose up -d
# 由于修改了 web index,所以是空的 200
curl -vk https://127.0.0.1:12345/

客户端接入

所有客户端有微皮恩的,需要把 ecs 的公网 IP 设置成直连不走代理,以及如果是家里宽带,可以把 Upnp 打开,这样打洞直连成功率会高些。

创建 authkeys

Tailscale 中有一个概念叫 tailnet,你可以理解成租户,租户与租户之间是相互隔离的,具体看参考 Tailscale 的官方文档: What is a tailnet
Headscale 也有类似的实现叫 user,即用户。我们需要先创建一个 user,以便后续客户端接入,例如:

headscale user create default

其他客户端接入需要首先在服务端生成 pre-authkey 的 key :

# 生成一个过期时间 365d 且可以重复使用的 authkey
$ headscale preauthkeys --user default create --reusable --expiration 365d 

# 查看已经生成的 key:
$ headscale preauthkeys --user default list
ID | Key                                              | Reusable | Ephemeral | Used  | Expiration          | Created             | Tags
1  | 49f9cd7f4e7b3e33023a9064xxxxxebf00778d2xxxxxxxxx | false    | false     | false | 2025-07-24 14:32:45 | 2024-07-24 14:32:45 | 

tailscale 也是分为 tailscaled 的 daemon 和 tailscale 的 cli 工具,windows、Linux 以及安卓的 Magisk 模块等都可以使用 cli 工具操作和排查,这点很重要。

下面是 tailscale up 时候一些常用通用选项:

  • --login-server: 指定使用的中央服务器地址(必填)
  • --advertise-routes: 向中央服务器报告当前客户端处于哪个内网网段下, 便于中央服务器让同内网设备直接内网直连(可选的)或者将其他设备指定流量路由到当前内网(可选),多条路由英文逗号隔开
  • --accept-routes: 是否接受中央服务器下发的用于路由到其他客户端内网的路由规则(可选)
  • --accept-dns: 是否使用中央服务器下发的 DNS 相关配置(可选, 推荐关闭)
  • --hostname: 设置 machine name,否则默认会以 hostname 注册上去,特别安卓的 hostname 无法修改

tailscale cli 官方文档 https://tailscale.com/kb/1080/cli,也可以自己 tailscale --help 看命令帮助。

Linux 接入

derp 上的客户端

先接入 derp 也就是 ecs 上那个 tailscale:

# 也可以拷贝出来宿主机上执行
# docker cp tailscale:/usr/local/bin/tailscale /usr/bin/tailscale
docker exec -ti tailscale sh

tailscale up --login-server=http://<HEADSCALE_PUB_ENDPOINT>:8081 --accept-routes=true --hostname ecs --accept-dns=false --authkey 49f9cd....

可以 headscale 上查看信息:

$ headscale node list
An updated version of Headscale has been found (0.23.0-beta1 vs. your current v0.22.3). Check it out https://github.com/juanfont/headscale/releases
ID | Hostname  | Name   | MachineKey | NodeKey | User    | IP addresses | Ephemeral | Last seen           | Expiration          | Online  | Expired
1  | ecs       | ecs    | [3ixoT]    | [VFZTB] | default | 100.64.0.1, | false     | 2024-07-25 05:20:50 | 0001-01-01 00:00:00 | online  | no

Linux 上 tailscale 会利用 tun 创建网卡,路由表在 52 里:

$ ip route show table 52

另外要注意,由于 derper 依赖这个 tailscale,所以这个 tailscale 不要乱重启(因为是授权信息来源),可能会导致其他端会断开一下子,例如我就遇到安卓端玩游戏会断开下。

Linux 客户端

官方相关文档:

curl -fsSL https://tailscale.com/install.sh | sh

然后自己 tailscale up --...... 登录。

windows

https://tailscale.com/download 下载安装(安装失败换 msi 安装包 https://pkgs.tailscale.com/stable/#windows ),如果 msi 安装包启动报错 iphelp 啥的相关,在 services.msc 里把 ip helper 属性设置自启动后启动下。

启动后,官方文档 windows-client 说需要 powershell 修改下面的注册表:

New-Item -Path "HKLM:\SOFTWARE\Tailscale IPN"
New-ItemProperty -Path 'HKLM:\Software\Tailscale IPN' -Name UnattendedMode -PropertyType String -Value always
New-ItemProperty -Path 'HKLM:\Software\Tailscale IPN' -Name LoginURL -PropertyType String -Value http://YOUR-HEADSCALE-URL:8081

但是没必要,而且系统托盘图标点击 login 可能无法弹出浏览器页面,可以 powershell 或者 gitbash 里执行 tailscale cli 注册:

tailscale up --login-server http://xxx:8081 --hostname laptop --accept-routes=true --accept-dns=false --authkey 49f9cd....

如果执行后卡住的,在 services.msc 里把 tailscale 重启下后再试试。

windows 开启 --unattended 锁屏不会断开连接,一些 windows 早期版本问题见下面 issue :

windows 关闭上传日志到 log.tailscale.io 可以在 C:\ProgramData\Tailscale 下新增 tailscaled-env.txt 写入 env:

TS_NO_LOGS_NO_SUPPORT=true

安卓

https://github.com/tailscale/tailscale-android

推荐 https://f-droid.org/packages/com.tailscale.ipn/ 下载,如果 headscale 是 v0.22.3 请下载 1.72.0 ,我尝试开发版本 1.78.1-t89039 版本无法注册。

安装后在 右上角 - Accounts - 三个点 - Settings Accounts Use an alternate server,输入 http://xxx:8081,然后下面 Use an auth key 可能会没使用 authkey 跳转到浏览器出现一个 headscale nodes register ... --key nodekey:xxxx ,所以我们需要回到 headscale 上命令授权:

# 日志里会打印长串 key,所以不需要在其他端复制 nodekey
journalctl -xe --no-pager -u headscale | grep nodekey

headscale nodes register --user default --key nodekey:xxxxx

对于一些没有浏览器也没 tailscale cli 的都可以这样手动授权下。安卓上点击每个 peer 进去的右上角图标等于 tailscale ping xxx ,会显示能否直连和延迟。也可以后续使用 Magisk tailscale,那样可以有 cli 了,另外 apk 是使用 V-P-N 形式,断网和切换流量会断开,而 Magisk tailscale 则不会。

adb 启动

adb shell am start -n com.tailscale.ipn/com.tailscale.ipn.MainActivity

openwrt

参照 https://github.com/adyanth/openwrt-tailscale-enabler,需要有 kmod-tun 模块包。

VER=1.70.0
wget https://pkgs.tailscale.com/stable/tailscale_${VER}_arm64.tgz
tar zxvf tailscale_*_arm64.tgz
mkdir -p /etc/tailscale/
mv tailscale_*_arm64/tailscal* /usr/bin
rm -rf tailscale_*_arm64

cat << EOF > /etc/init.d/tailscale
#!/bin/sh /etc/rc.common

# Copyright 2020 Google LLC.
# SPDX-License-Identifier: Apache-2.0

USE_PROCD=1
START=99
STOP=1

start_service() {
  procd_open_instance
  procd_set_param command /usr/bin/tailscaled

  # Set the port to listen on for incoming VPN packets.
  # Remote nodes will automatically be informed about the new port number,
  # but you might want to configure this in order to set external firewall
  # settings.
  # procd_append_param command --port 41641

  # OpenWRT /var is a symlink to /tmp, so write persistent state elsewhere.
  procd_append_param command --state /etc/config/tailscaled.state
  
  # Persist files for TLS cert & Taildrop files
  procd_append_param command --statedir /etc/tailscale/
  procd_append_param command -no-logs-no-support

  procd_set_param respawn
  procd_set_param stdout 1
  procd_set_param stderr 1

  procd_close_instance
}

stop_service() {
  /usr/bin/tailscaled --cleanup
}
EOF


/etc/init.d/tailscale enable
/etc/init.d/tailscale start

tailscale status

登录

tailscale up --login-server=http://<HEADSCALE_PUB_ENDPOINT>:8081 --accept-routes=true --hostname openwrt --accept-dns=false --authkey 49f9cd....

tailscale status

查看日志:

logread tailscaled

tailscale client 使用

每个 tailscale 端都可以执行命令来查看和排查一些信息。

# debug 命令隐藏在 --help 了,可以 tailscale debug --help 自行查看
# 打印 derp-map 信息
tailscale debug derp-map

查看和检测当前网络,会输出当前 derp 服务器信息:

$ tailscale netcheck

Report:
	* UDP: true
	* IPv4: yes, xx.x.xx.xxx:54417
	* IPv6: no, but OS has support
	* MappingVariesByDestIP: 
	* PortMapping: 
	* Nearest DERP: tx
	* DERP latency:
		- custom: 500µs   (tx)

tailscale ping 命令可以用于测试 IP 连通性, 同时可以看到时如何连接目标节点的. 默认情况下 Ping 命令首先会使用 Derper 中继节点通信, 然后尝试 P2P 连接; 一旦 P2P 连接成功则自动停止 Ping:

$ tailscale ping 100.64.0.3
pong from redmi8 (100.64.0.3) via DERP(custom) in 30ms
pong from redmi8 (100.64.0.3) via 192.168.0.107:47316 in 32ms

status 查看以及 peer 的信息:

tailscale status
tailscale status --json

修改当前节点信息,支持修改的属性 --help 自行查看:

# tailscale set --help
tailscale set --hostname=xxx

或者可以 down 后 up 带单一需要修改的参数执行下:

tailscale down
tailscale up --xxx
# 然后会打印全部参数,复制执行下

查看当前的参数列表,官方没有 cmdline 形式打印,只有 json 的:

tailscale debug prefs

derp 调试

curl -vk https://127.0.0.1:12345/debug/vars

打通内网

Linux 端都要开启转发,windows 和安卓转发自行查找怎么配置。

echo 'net.ipv4.ip_forward = 1' | tee /etc/sysctl.d/ipforwarding.conf
echo 'net.ipv6.conf.all.forwarding = 1' | tee -a /etc/sysctl.d/ipforwarding.conf
sysctl -p /etc/sysctl.d/ipforwarding.conf

然后在 server 端查看 node ID :

$ headscale node list
An updated version of Headscale has been found (0.23.0-beta1 vs. your current v0.22.3). Check it out https://github.com/juanfont/headscale/releases
ID | Hostname  | Name   | MachineKey | NodeKey | User    | IP addresses | Ephemeral | Last seen           | Expiration          | Online  | Expired
1  | ecs       | ecs    | [3ixoT]    | [VFZTB] | default | 100.64.0.1, | false     | 2024-07-26 07:12:50 | 0001-01-01 00:00:00 | online  | no
2  | localhost | ax18   | [egfcx]    | [LzS5J] | default | 100.64.0.2, | false     | 2024-07-26 07:13:14 | 0001-01-01 00:00:00 | online  | no
3  | localhost | redmi8 | [uqIVP]    | [l6sL7] | default | 100.64.0.3, | false     | 2024-07-26 07:13:06 | 0001-01-01 00:00:00 | online  | no

假设 ID==1 的局域网是 192.168.31.0/24 网段,我们希望其他 ID 设备上能访问到,先查看路由:

$ headscale routes list
ID | Machine | Prefix          | Advertised | Enabled | Primary
.....
1  | ax18    | 192.168.31.0/24 | true       | false   | false
headscale routes enable -r 1

其他节点查看路由结果:

$ ip route show table 52 | grep "192.168.31.0/24"
192.168.31.0/24 dev tailscale0

其他节点启动时需要增加 --accept-routes=true 选项来声明 “我接受外部其他节点发布的路由”。

现在你在任何一个 Tailscale 客户端所在的节点都可以 ping 通家庭内网的机器了,你在公司或者星巴克也可以像在家里一样用同样的 IP 随意访问家中的任何一个设备。

一个正在运行的节点增加路由可以使用 set 命令:

# 多条用英文逗号间隔
tailscale set --advertise-routes xx.xx.xx.0/24,xx,xxx.xxx.00.00/16

信息修改

例如安卓的 hostname 由于没有 cli 无法修改,而默认 db 使用 sqlite,可以修改:

$ sqlite3 /var/lib/headscale/db.sqlite
.tables
update machines set given_name="redmi8" where id=3;

# ip_addresses="100.64.0.3"
修改 ip 需要先 tailscale down 改好后再 tailscale up,修改后配置会同步到所有 tailscale client 上,特别是游戏可能会闪断下

其他的修改自己琢磨。

源码修改 tailscale 网段

修改 tailscale 避免阿里云 100.64.0.0/16 上出问题,见漠然博客阿里云安装客户端后无法更新软件

一些说明

这里只介绍异地组网部分,其他的去看官方文档。

参考

请有自己的理解能力,不要随便照抄,端口啥的也可以自定义下。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment