Docker版headscale&derp&tailscale&npm&pve&ros附一键安装脚本

三合一脚本支持alpine&ubuntu&debian系统

Docker

安装与准备:

docker一键安装脚本

curl -fsSL https://raw.githubusercontent.com/hanigege/scripts/main/docker.sh -o docker.sh && sh docker.sh

Nginx Proxy Manager (简称:npm)

安装与准备

mkdir -p ~/data/docker_data/nginxproxymanager   # 创建一个npm的文件夹

cd ~/data/docker_data/nginxproxymanager    # 进入该文件夹

nano docker-compose.yaml

docker-compose.yaml 配置:

services:
  app:
    image: 'docker.io/jc21/nginx-proxy-manager:latest'
    restart: unless-stopped
    ports:
      - '12332:80'
      - '81:81'
      - '12335:443'
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt

默认登陆的用户名:admin@example.com 密码:changeme 第一次登陆会提示更改用户名和密码,建议修改一个复杂一点的密码。 至此,我们已经完成了Nginx Proxy Manager的搭建。

(修改为你的内网对应的vm地址)

  • 登录平台内网地址:http://10.20.20.8:81

添加三个对应域名,开启https:由于是内网,上面的配置中可以看到,443端口映射到12335端口了。例:

域名ip功能
hs.jgaga.tk10.20.20.8headscale的注册服务
derps.jgaga.tk10.20.20.8derp的服务地址
hs-ui.jgaga.tk10.20.20.8headscale的管理平台地址

npm域名配置功能里里添加各域名的高级配置(右上脚的小齿轮图标)(必须)

hs.jgaga.tk

# 1. 针对 UI 的 API 请求,允许跨域(解决你说的保存密钥问题)
location ~ ^/(admin|web|api) {
    proxy_pass http://10.20.20.8:8080;
    proxy_http_version 1.1;
    proxy_set_header Host $http_host;

    # 跨域头只在这些路径生效
    add_header Access-Control-Allow-Origin * always;
    add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE" always;
    add_header Access-Control-Allow-Headers * always;

    if ($request_method = OPTIONS) {
        return 204;
    }
}

# 2. 针对手机客户端的 Noise 协议通讯,保持纯净(解决 Out of sync)
location / {
    proxy_pass http://10.20.20.8:8080;
    proxy_http_version 1.1;

    # 核心:必须使用动态 Upgrade,不要硬编码 Connection "upgrade"
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_upgrade; 

    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # 实时性优化
    proxy_buffering off;
    proxy_request_buffering off;
    proxy_socket_keepalive on;
    tcp_nodelay on;

    # 延长超时
    proxy_connect_timeout 60s;
    proxy_read_timeout 600s;
    proxy_send_timeout 600s;
}

derps.jgaga.tk

location / {
    proxy_pass http://10.20.20.8:8082;
    proxy_http_version 1.1;

    # --- 关键修正:去掉端口号,仅透传域名 ---
    # 这样可以匹配 DERP 容器内部的证书/域名校验
    proxy_set_header Host $host; 
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    # --- 保持长连接稳定性 ---
    proxy_buffering off;
    proxy_request_buffering off;
    proxy_socket_keepalive on;
    tcp_nodelay on;

    # 超时设置,防止感叹号
    proxy_connect_timeout 60s;
    proxy_read_timeout 600s;
    proxy_send_timeout 600s;

    # --- 必须:WebSocket 协议升级 ---
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

headscale&derp&tailscale

安装与准备

headscale&derp&tailscale一键安装脚本(包含开启tun及ip转发功能)

mkdir -p ~/data/docker_data/hdt # 创建一个npm的文件夹

cd ~/data/docker_data/hdt    # 进入该文件夹

nano hdt.sh

添加以下内容:

#!/bin/bash
set -e

echo "=================================================="
echo "    Headscale + Derper 自动化交互部署脚本"
echo "    (包含底层内核与路由环境自动配置)"
echo "=================================================="
echo ""

# 0. 权限检查
if [ "$EUID" -ne 0 ]; then 
  echo "错误: 请使用 root 用户或 sudo 执行此脚本。"
  exit 1
fi

# 1. 采集用户输入
read -p "1. 请输入 DERP 节点的外部访问域名 (例如 derps.jgaga.xyz): " DERP_DOMAIN
read -p "2. 请输入 Headscale 控制端的外部访问域名 (例如 hs.jgaga.xyz): " HS_DOMAIN
read -p "3. 请输入 Headscale UI 界面的外部访问域名 (例如 hs-ui.jgaga.xyz): " UI_DOMAIN
read -p "4. 请输入当前宿主机的内网物理 IP (例如 10.20.20.8): " HOST_IP
read -p "5. 请输入内网自定义 DNS 服务的 IP (例如 10.20.20.6): " DNS_IP

echo ""
echo "正在配置宿主机底层网络与内核转发..."

# [新增核心模块] 自动配置内核与网络
# 开启 IPv4 转发
if ! grep -q "^net.ipv4.ip_forward.*=.*1" /etc/sysctl.conf; then
    echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
    sysctl -p >/dev/null 2>&1 || true
    echo " -> 已开启 IPv4 路由转发"
fi

# 尝试加载 TUN 和 iptables 模块 (兼容 Alpine/Ubuntu)
modprobe tun 2>/dev/null || true
modprobe ip_tables 2>/dev/null || true
modprobe iptable_filter 2>/dev/null || true
modprobe iptable_nat 2>/dev/null || true

# 确保 /dev/net/tun 设备存在且权限正确
if [ ! -c /dev/net/tun ]; then
    mkdir -p /dev/net
    mknod /dev/net/tun c 10 200
    chmod 666 /dev/net/tun
    echo " -> 已创建并赋权虚拟网卡设备 /dev/net/tun"
fi

# 针对 Alpine 系统自动写入开机自启名单
if [ -f /etc/alpine-release ]; then
    for mod in tun ip_tables iptable_filter iptable_nat; do
        if ! grep -q "^${mod}$" /etc/modules; then
            echo "${mod}" >> /etc/modules
        fi
    done
    echo " -> 已为 Alpine 系统配置内核模块开机自启"
fi

echo ""
echo "正在生成目录结构与 Docker 配置文件..."

# 2. 创建目录结构
mkdir -p headscale/config headscale/data tailscale-state

# 3. 生成 docker-compose.yml
cat < docker-compose.yml
services:
  headscale:
    image: headscale/headscale:latest
    container_name: headscale-server
    restart: always
    volumes:
      - ./headscale/config:/etc/headscale
      - ./headscale/data:/var/lib/headscale
    ports:
      - "8080:8080"
      - "9090:9090"
    command: serve

  headscale-ui:
    image: ghcr.io/gurucomputing/headscale-ui:latest
    container_name: headscale-ui
    restart: always
    ports:
      - "9443:8080"
    depends_on:
      - headscale

  derper:
    image: fredliang/derper
    container_name: derper
    restart: always
    ports:
      - "8082:80"         # 内部 HTTP 监听,供 NPM 转发
      - "3478:3478/udp"   # STUN 打洞端口
    command: /app/derper --hostname=${DERP_DOMAIN} --a=:80 --http-port=-1 --verify-clients=false

  tailscale:
    image: tailscale/tailscale:latest
    container_name: tailscale-node
    hostname: home-gateway
    restart: always
    network_mode: "host"
    cap_add:
      - NET_ADMIN
      - NET_RAW
    devices:
      - /dev/net/tun:/dev/net/tun
    volumes:
      - ./tailscale-state:/var/lib/tailscale
      - /lib/modules:/lib/modules:ro
    environment:
      - TS_STATE_DIR=/var/lib/tailscale
      - TS_USERSPACE=false
    command: tailscaled --port=7829
EOF

# 4. 生成 Headscale 的 derp.yaml 节点配置
cat < headscale/config/derp.yaml
regions:
  900:
    regionid: 900
    regioncode: Gary2Derp
    regionname: Gary2Derp
    nodes:
      - name: MyDerp-office
        regionid: 900
        hostname: ${DERP_DOMAIN}
        derpport: 12335
        stunport: 3478
        stunonly: false
EOF

# 5. 动态生成 Headscale 的 config.yaml 主配置文件
cat < headscale/config/config.yaml
# Headscale 容器版完整配置
server_url: https://${HS_DOMAIN}:12335

# 容器内必须监听 0.0.0.0 才能接收来自 NPM 的转发
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

noise:
  # 路径映射到容器内的 /var/lib/headscale
  private_key_path: /var/lib/headscale/noise_private.key

prefixes:
  v4: 100.64.0.0/10
  allocation: sequential

derp:
  server:
    enabled: false # 独立容器运行 DERP,此处关闭
    automatically_add_embedded_derp_region: false

  urls: [] # 禁用官方节点,专注自建
  paths:
    - /etc/headscale/derp.yaml # 映射在 config 目录下的文件
  auto_update_enabled: true
  update_frequency: 1h

database:
  type: sqlite
  sqlite:
    path: /var/lib/headscale/db.sqlite
    write_ahead_log: true

# 日志级别设为 info,确保能看到 DERP 加载记录
log:
  level: info
  format: text

dns:
  magic_dns: true
  base_domain: i.love.you
  override_local_dns: true    # 核心:设为 true,强行接管手机的所有 DNS 请求
  nameservers:
    global:
      - ${DNS_IP}             # 唯一出口:所有解析全部扔进隧道,交给内网 MosDNS 处理
      - 1.1.1.1               # 救命稻草,负责在隧道断开时解析控制端域名

unix_socket: /var/run/headscale/headscale.sock
unix_socket_permission: "0770"

randomize_client_port: false
taildrop:
  enabled: true
EOF

echo "=================================================="
echo "    所有配置与系统环境准备已完成!"
echo "    请立即执行: docker compose up -d"
echo "=================================================="
echo ""
echo "【NPM 反向代理配置终极清单】"
echo "请登录 Nginx Proxy Manager,添加/修改对应的 Proxy Host:"
echo ""
echo ">> [1] DERP 节点配置 (对应域名: ${DERP_DOMAIN})"
echo "   - Details 标签页:"
echo "       * Scheme:               http"
echo "       * Forward Hostname/IP:  ${HOST_IP}"
echo "       * Forward Port:         8082"
echo "       * Websockets Support:   开启 (必须)"
echo "   - SSL 标签页:               请求/选择有效证书并开启 Force SSL"
echo ""
echo ">> [2] Headscale 控制端配置 (对应域名: ${HS_DOMAIN})"
echo "   - Details 标签页:"
echo "       * Scheme:               http"
echo "       * Forward Hostname/IP:  ${HOST_IP}"
echo "       * Forward Port:         8080"
echo "       * Websockets Support:   开启 (必须)"
echo "   - SSL 标签页:               请求/选择有效证书并开启 Force SSL"
echo ""
echo ">> [3] Headscale UI 界面配置 (对应域名: ${UI_DOMAIN})"
echo "   - Details 标签页:"
echo "       * Scheme:               http"
echo "       * Forward Hostname/IP:  ${HOST_IP}"
echo "       * Forward Port:         9443"
echo "       * Websockets Support:   开启"
echo "   - SSL 标签页:               请求/选择有效证书并开启 Force SSL"
echo "=================================================="

添加执行权限并执行安装

chmod +x hdt.sh

./hdt.sh

ROS

开启ros转发端口 (请修改你相应的端口和ip)

# 1. 【清场】删除所有之前乱掉的 Tailscale 和 DERP 规则和脚本
/ip firewall nat remove [find comment~"Tailscale"]
/ip firewall nat remove [find comment~"DERP"]
/ip firewall mangle remove [find comment~"Tailscale"]
/ip firewall raw remove [find comment~"Tailscale"]
/system scheduler remove [find name="Sync_Tailscale_WAN"]
/system script remove [find name="Update_Tailscale_NAT"]

# 2. 【核心规则】出站精准锁定 (必须加上 src-port=7829 防止误杀其他 UDP)
/ip firewall nat add chain=srcnat action=src-nat to-addresses=1.1.1.1 to-ports=7829 \
    protocol=udp src-address=10.20.20.8 src-port=7829 comment="Tailscale_Outbound_Fixed" place-before=0

# 3. 添加:映射 Tailscale 直连 (UDP 7829)
/ip firewall nat add chain=dstnat action=dst-nat protocol=udp dst-port=7829 \
    to-addresses=10.20.20.8 to-ports=7829 comment="Tailscale_Inbound_Fixed"

# 4. 添加:映射 DERP STUN 打洞探测 (UDP 3478)
/ip firewall nat add chain=dstnat action=dst-nat protocol=udp dst-port=3478 \
    to-addresses=10.20.20.8 to-ports=3478 comment="DERP_STUN_Inbound"

# 5. 添加:映射 DERP TCP 业务流 (TCP 12335)
/ip firewall nat add chain=dstnat action=dst-nat protocol=tcp dst-port=12335 \
    to-addresses=10.20.20.8 to-ports=12335 comment="DERP_TCP_Inbound"

# 6. 【组合脚本】获取 pppoe-out1 的实时公网 IP 并填入 Rule 0
/system script add name="Update_Tailscale_NAT" source={
    :local currentIP [/ip address get [find interface=pppoe-out1] address];
    :set currentIP [:pick $currentIP 0 [:find $currentIP "/"]];
    :if ([:len $currentIP] > 0) do={
        /ip firewall nat set [find comment="Tailscale_Outbound_Fixed"] to-addresses=$currentIP;
        # :log info ("Tailscale NAT 已同步为公网 IP: " . $currentIP);
    }
}

# 6. 【自动化】设置每分钟运行一次脚本
/system scheduler add name="Sync_Tailscale_WAN" interval=1m on-event="Update_Tailscale_NAT"

# 7. 【立即执行】刷新状态
/system script run Update_Tailscale_NAT

### 终端下输入:
  • 99年密钥生成:(hs-ui.jgaga.tk:12335平台注册使用)
docker exec headscale-server headscale apikeys create --expiration 99y

然后登录headscale管理平台UI,例: hs-ui.jgaga.tk:12335 进行配置,包含注册系统,添加用户设备等,请自行操作

  • 终端下进行tailscale启动路由命令:(包括重新注册–force-reauth ,第一次使用可以把这个去除)
docker exec tailscale-node tailscale up --login-server https://hs.116160.xyz:12335 --advertise-routes=fd88::/64,10.20.20.0/24,f2b0::/18,28.0.0.0/8,100.100.200.0/24,91.108.56.0/22,91.108.4.0/22,91.108.8.0/22,91.108.16.0/22,91.108.12.0/22,149.154.160.0/20,91.105.192.0/23,91.108.20.0/22,185.76.151.0/24,2001:67c:4e8::/48,2001:b28:f23f::/48,2a0a:f280::/32,2001:b28:f23c::/48,2001:b28:f23d::/48  --accept-dns=false -reset  --force-reauth 

手机客户端等自行安装配置。 完事~

附:

上面的一键脚本包含了对alpine系统及ubuntu系统的判断,提供相应的tun开启服务以及ip转发的配置,如果想手动操作,上面的三合一脚本删除,按下面的操作:

开始安装

docker一键安装

curl -fsSL https://raw.githubusercontent.com/hanigege/scripts/main/docker.sh -o docker.sh && sh docker.sh

docker.sh

该脚本用于自动安装 docker 以及 docker 插件(composes、buildx 等), 同时该脚本将会自动配置 /etc/docker/daemon.json 文件, 目前针对 docker daemon 的配置逻辑如下:

默认开启 init 支持, 防止容器产生僵尸进程

切换 docker 存储目录为 /data/docker, 此后所有 docker 数据将会存放于此

限制容器日志文件大小, 单个容器最多 5 个日志文件, 单个日志文件最大 10m, 防止容器 stdout 过量输出导致占满磁盘空间

开启 live-restore 防止 docker daemon 意外重启导致容器重启

Debian、Redhat 系列系统默认 CGroups Driver 切换为 systemd, Alpine 系统保持默认 cgroupfs v2

该脚本安装完成后默认会 Pin 住 docker 相关软件包, 防止自动升级导致容器重启或出现不可逆故障.

如果是linux下的用普通用户操作的,请记的添加到docker组:

  • 一步解决

把用户加入 docker 组:

sudo usermod -aG docker $USER

然后 重新登录一次系统(很重要)。

如果是 SSH:

exit

再重新登录。

然后再测试

docker ps

如果成功,会显示:

CONTAINER ID   IMAGE   COMMAND   CREATED   STATUS   PORTS   NAMES

1. 宿主机底层网络与内核准备 (TUN & iptables)

在部署 Tailscale 容器前,必须确保宿主机内核支持虚拟网卡和底层防火墙路由。

针对 Alpine 系统(当前环境):

Alpine 内核精简,需手动加载模块并设置开机自启。在宿主机终端执行:

加载虚拟网卡与防火墙模块

modprobe tun
modprobe ip_tables
modprobe iptable_filter
modprobe iptable_nat

写入开机自启名单

echo "tun" >> /etc/modules
echo "ip_tables" >> /etc/modules
echo "iptable_filter" >> /etc/modules
echo "iptable_nat" >> /etc/modules

开启 IPv4 转发

echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf

sysctl -p

针对 Ubuntu 系统(迁移备用):

Ubuntu 默认已内置并加载了所需的 tun 和 iptables 模块,无需手动 modprobe。仅需开启内核 IP 转发即可:

#### 开启 IPv4 转发
echo "net.ipv4.ip_forward = 1" | sudo tee -a /etc/sysctl.conf

sudo sysctl -p

2.

三合一一键脚本:

headscale&derp&tailscale一键安装脚本(不包含开启tun及ip转发功能)

mkdir -p ~/data/docker_data/hdt # 创建一个npm的文件夹

cd ~/data/docker_data/hdt    # 进入该文件夹

nano hdt.sh
#!/bin/bash
set -e

echo "=================================================="
echo "    Headscale + Derper 自动化交互部署脚本"
echo "=================================================="
echo ""

# 1. 采集用户输入
read -p "1. 请输入 DERP 节点的外部访问域名 (例如 derps.jgaga.xyz): " DERP_DOMAIN
read -p "2. 请输入 Headscale 控制端的外部访问域名 (例如 hs.jgaga.xyz): " HS_DOMAIN
read -p "3. 请输入 Headscale UI 界面的外部访问域名 (例如 hs-ui.jgaga.xyz): " UI_DOMAIN
read -p "4. 请输入当前宿主机的内网物理 IP (例如 10.20.20.8): " HOST_IP
read -p "5. 请输入内网自定义 DNS 服务的 IP (例如 10.20.20.6): " DNS_IP

echo ""
echo "正在生成目录结构与配置文件..."

# 2. 创建目录结构
mkdir -p headscale/config headscale/data tailscale-state

# 3. 生成 docker-compose.yml
cat < docker-compose.yml
services:
  headscale:
    image: headscale/headscale:latest
    container_name: headscale-server
    restart: always
    volumes:
      - ./headscale/config:/etc/headscale
      - ./headscale/data:/var/lib/headscale
    ports:
      - "8080:8080"
      - "9090:9090"
    command: serve

  headscale-ui:
    image: ghcr.io/gurucomputing/headscale-ui:latest
    container_name: headscale-ui
    restart: always
    ports:
      - "9443:8080"
    depends_on:
      - headscale

  derper:
    image: fredliang/derper
    container_name: derper
    restart: always
    ports:
      - "8082:80"         # 内部 HTTP 监听,供 NPM 转发
      - "3478:3478/udp"   # STUN 打洞端口
    command: /app/derper --hostname=${DERP_DOMAIN} --a=:80 --http-port=-1 --verify-clients=false

  tailscale:
    image: tailscale/tailscale:latest
    container_name: tailscale-node
    hostname: home-gateway
    restart: always
    network_mode: "host"
    cap_add:
      - NET_ADMIN
      - NET_RAW
    devices:
      - /dev/net/tun:/dev/net/tun
    volumes:
      - ./tailscale-state:/var/lib/tailscale
      - /lib/modules:/lib/modules:ro
    environment:
      - TS_STATE_DIR=/var/lib/tailscale
      - TS_USERSPACE=false
    command: tailscaled --port=7829
EOF

# 4. 生成 Headscale 的 derp.yaml 节点配置
cat < headscale/config/derp.yaml
regions:
  900:
    regionid: 900
    regioncode: Gary2Derp
    regionname: Gary2Derp
    nodes:
      - name: MyDerp-office
        regionid: 900
        hostname: ${DERP_DOMAIN}
        derpport: 12335
        stunport: 3478
        stunonly: false
EOF

# 5. 动态生成 Headscale 的 config.yaml 主配置文件
cat < headscale/config/config.yaml
# Headscale 容器版完整配置
server_url: https://${HS_DOMAIN}:12335

# 容器内必须监听 0.0.0.0 才能接收来自 NPM 的转发
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

noise:
  # 路径映射到容器内的 /var/lib/headscale
  private_key_path: /var/lib/headscale/noise_private.key

prefixes:
  v4: 100.64.0.0/10
  allocation: sequential

derp:
  server:
    enabled: false # 独立容器运行 DERP,此处关闭
    automatically_add_embedded_derp_region: false

  urls: [] # 禁用官方节点,专注自建
  paths:
    - /etc/headscale/derp.yaml # 映射在 config 目录下的文件
  auto_update_enabled: true
  update_frequency: 1h

database:
  type: sqlite
  sqlite:
    path: /var/lib/headscale/db.sqlite
    write_ahead_log: true

# 日志级别设为 info,确保能看到 DERP 加载记录
log:
  level: info
  format: text

dns:
  magic_dns: true
  base_domain: i.love.you
  override_local_dns: true    # 核心:设为 true,强行接管手机的所有 DNS 请求
  nameservers:
    global:
      - ${DNS_IP}             # 优先级最高,所有解析全部扔进隧道,交给内网 MosDNS 处理
      - 1.1.1.1               # 救命稻草,负责在隧道断开时解析控制端域名

unix_socket: /var/run/headscale/headscale.sock
unix_socket_permission: "0770"

randomize_client_port: false
taildrop:
  enabled: true
EOF

echo "=================================================="
echo "    所有配置文件 (包含 config.yaml) 已精准生成!"
echo "    请立即执行: docker compose up -d"
echo "=================================================="
echo ""
echo "【NPM 反向代理配置终极清单】"
echo "请登录 Nginx Proxy Manager,添加/修改对应的 Proxy Host:"
echo ""
echo ">> [1] DERP 节点配置 (对应域名: ${DERP_DOMAIN})"
echo "   - Details 标签页:"
echo "       * Scheme:               http"
echo "       * Forward Hostname/IP:  ${HOST_IP}"
echo "       * Forward Port:         8082"
echo "       * Websockets Support:   开启 (必须)"
echo "   - SSL 标签页:               请求/选择有效证书并开启 Force SSL"
echo ""
echo ">> [2] Headscale 控制端配置 (对应域名: ${HS_DOMAIN})"
echo "   - Details 标签页:"
echo "       * Scheme:               http"
echo "       * Forward Hostname/IP:  ${HOST_IP}"
echo "       * Forward Port:         8080"
echo "       * Websockets Support:   开启 (必须)"
echo "   - SSL 标签页:               请求/选择有效证书并开启 Force SSL"
echo ""
echo ">> [3] Headscale UI 界面配置 (对应域名: ${UI_DOMAIN})"
echo "   - Details 标签页:"
echo "       * Scheme:               http"
echo "       * Forward Hostname/IP:  ${HOST_IP}"
echo "       * Forward Port:         9443"
echo "       * Websockets Support:   开启"
echo "   - SSL 标签页:               请求/选择有效证书并开启 Force SSL"
echo "=================================================="