OpenVPN + TLJH + isso 从 Debian 迁移到 Ubuntu

2026-03-26 完成了从 Debian (jhub.dtype.info) 到 Ubuntu (mail) 的服务迁移记录。

一、OpenVPN 迁移

1.1 备份与传输

在源服务器上备份配置:

1
sudo tar -czvf openvpn-backup.tar.gz /etc/openvpn/

1.2 恢复时的问题与解决

问题 原因 解决方案
/etc/openvpn/server.conf 不存在 配置文件实际在 /etc/openvpn/server/server.conf 创建符号链接
Cannot pre-load keyfile (tc.key) 相对路径解析错误 改用绝对路径 /etc/openvpn/server/tc.key
Cannot assign requested address local 10.146.0.3 是旧服务器 IP 改为新服务器 IP 160.251.141.121

1.3 修复后的配置

1
2
3
4
5
6
7
8
9
10
# 创建符号链接
sudo ln -s /etc/openvpn/server/server.conf /etc/openvpn/server.conf

# 修改 server.conf 中的路径为绝对路径
ca /etc/openvpn/server/ca.crt
cert /etc/openvpn/server/server.crt
key /etc/openvpn/server/server.key
dh /etc/openvpn/server/dh.pem
tls-crypt /etc/openvpn/server/tc.key
crl-verify /etc/openvpn/server/crl.pem

1.4 NAT 转发配置

OpenVPN 客户端连接后无法上网,需要添加 NAT 规则:

1
2
3
4
5
6
# 添加 NAT 伪装
sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE

# 永久保存(安装 iptables-persistent)
sudo apt install iptables-persistent
sudo iptables-save > /etc/iptables/rules.v4

1.5 防火墙放行

1
2
3
sudo ufw allow 1194/tcp   # OpenVPN
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS

二、TLJH (The Littlest JupyterHub) 迁移

2.1 安装 TLJH

Ubuntu 上使用官方安装脚本:

1
2
curl -sL https://raw.githubusercontent.com/jupyterhub/the-littlest-jupyterhub/main/bootstrap/bootstrap.py -o /tmp/bootstrap.py
python3 /tmp/bootstrap.py

2.2 复制配置与状态

1
2
# 从 jhub 传输整个目录
ssh jhub "sudo tar -C /opt/tljh -czvf - config state" | ssh mail "sudo tar -xzvf - -C /opt/tljh/"

2.3 Traefik 配置问题

问题 1:端口 443 被 Tailscale Funnel 占用

1
2
3
4
5
6
# 解决:修改 traefik.toml 只监听 eth0 IP
[entryPoints.http]
address = "160.251.141.121:80"

[entryPoints.https]
address = "160.251.141.121:443"

问题 2:traefik v3 不支持 toPort 参数(HTTP 重定向)

删除 traefik.toml 中的 toPort = 443 配置。

问题 3:缺少 HTTP 路由器定义

rules.toml 中需要显式定义路由器:

1
2
3
4
[http.routers.router__2F]
service = "service__2F"
rule = "PathPrefix(`/`)"
entryPoints = [ "https",]

2.4 防火墙放行

1
2
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

三、isso 评论系统迁移

3.1 安装 isso

1
sudo pip3 install --break-system-packages isso gunicorn

3.2 创建 systemd 服务

1
2
3
4
5
6
7
8
9
10
11
12
# /etc/systemd/system/isso.service
[Unit]
Description=lightweight Disqus alternative

[Service]
User=root
Restart=always
Environment="ISSO_SETTINGS=/etc/isso.conf"
ExecStart=/usr/local/bin/gunicorn --bind 127.0.0.1:8000 --log-file /var/log/isso.log --preload isso.run

[Install]
WantedBy=multi-user.target

3.3 配置文件与数据

1
2
3
4
5
6
# 配置
scp jhub:/etc/isso.conf mail:/tmp/ && ssh mail "sudo mv /tmp/isso.conf /etc/isso.conf"

# 数据库(使用 scp 避免二进制文件损坏)
scp jhub:/var/lib/isso/comments.db mail:/tmp/comments.db
ssh mail "sudo mv /tmp/comments.db /var/lib/isso/comments.db"

3.4 Traefik 路由规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# /opt/tljh/state/rules/isso.toml
[http.routers.isso]
service = "isso"
rule = "Host(`jhub.dtype.info`) && PathPrefix(`/isso/`)"
entryPoints = [ "https",]
middlewares = ["isso"]

[http.middlewares]
[http.middlewares.isso.stripPrefix]
prefixes = ["/isso"]

[http.services.isso.loadBalancer]
passHostHeader = true
[[http.services.isso.loadBalancer.servers]]
url = "http://127.0.0.1:8000"

四、其他 Traefik 路由

4.1 LINE Webhook 转发

1
2
3
4
5
6
7
8
9
10
# /opt/tljh/state/rules/line.toml
[http.routers.line-webhook]
service = "line-webhook"
rule = "Host(`jhub.dtype.info`) && PathPrefix(`/line/`)"
entryPoints = [ "https",]

[http.services.line-webhook.loadBalancer]
passHostHeader = true
[[http.services.line-webhook.loadBalancer.servers]]
url = "http://100.94.198.8:18790"

五、服务状态

服务 端口 状态
OpenVPN TCP 1194 ✅ 运行中
Traefik 80/443 ✅ 运行中
JupyterHub 15001 ✅ 运行中
isso 8000 ✅ 运行中

六、注意事项

  1. SSL 证书:acme.json 从旧服务器复制,Let’s Encrypt 证书可继续使用
  2. 客户端配置:OpenVPN 和 JupyterHub 客户端的 remote 地址需要更新为新服务器 IP
  3. DNS:确保 jhub.dtype.inforegistry.dtype.info 指向新服务器 IP
  4. 二进制文件传输:使用 scp 而不是管道 cat,避免数据损坏
  5. iptables-persistent:替代 UFW 使用,更可靠地保存防火墙规则

Ubuntu Server + BCM4360 Wi-Fi + SSH + Tailscale + Claude Code + 免密 sudo

针对我的 MacBook Pro (Retina, 13-inch, Late 2013 / A1502) 按这次实际操作的顺序,整理成一份完整的关键命令清单。

1. 在 macOS 上确认机器和 USB

1
2
3
system_profiler SPHardwareDataType
diskutil list
diskutil info /dev/disk4

我的机器确认是:

1
2
3
MacBookPro11,1
MacBook Pro (Retina, 13-inch, Late 2013)
Model A1502

2. Ubuntu 安装后确认网络接口

1
2
ip link
ls /sys/class/net

最初只有:

1
lo

连接手机 USB 网络共享后出现:

1
enxXXXXXXXXXXXX

3. 用手机 USB 网络临时联网

1
2
sudo ip link set enxXXXXXXXXXXXX up
sudo dhcpcd enxXXXXXXXXXXXX

确认 IPv4:

1
ip -4 addr show enxXXXXXXXXXXXX

确认路由:

1
ip route

测试 Internet:

1
ping -c 4 8.8.8.8

4. 安装 BCM4360 驱动

确认内核:

1
uname -r

这次是:

1
6.8.0-100-generic

确认驱动版本:

1
apt policy bcmwl-kernel-source | grep Candidate

安装:

1
2
sudo apt update
sudo apt install bcmwl-kernel-source

5. 让 wl 接管 BCM4360

这台 Mac 的 BCM4360 最初被 b43/bcma 路径占用。

卸载 b43

1
sudo modprobe -r b43

加载 Broadcom 专有驱动:

1
sudo modprobe wl

确认:

1
lsmod | grep '^wl'

最终出现:

1
wl 6438064 0

并且内核日志出现:

1
2
wlan0: Broadcom BCM43a0 B02.11 Hybrid Wireless Controller 6.30.223.271
wl 0000:03:00.0 wlp3s0: renamed from wlan0

说明成功。

6. 启用 Wi-Fi

1
sudo ip link set wlp3s0 up

确认:

1
ip link show wlp3s0

7. 扫描 Wi-Fi

如果没有 iw

1
sudo apt install iw

扫描:

1
sudo iw dev wlp3s0 scan | grep SSID

8. 配置 NetworkManager

1
2
sudo apt install network-manager
sudo systemctl enable --now NetworkManager

查看:

1
nmcli device status

连接 Wi-Fi:

1
nmcli device wifi connect "我的SSID" password "我的WiFi密码"

确认:

1
nmcli device status

以及:

1
ping -c 4 8.8.8.8

此时就可以拔掉手机 USB 网络共享。

9. 安装并启动 SSH

1
sudo systemctl enable --now ssh

确认:

1
systemctl is-active ssh

应该得到:

1
active

查看 Wi-Fi/LAN IP:

1
hostname -I

从另一台电脑:

1
ssh seii@192.168.1.8

如果因为重装系统出现:

1
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!

执行:

1
ssh-keygen -R 192.168.1.8

然后重新:

1
ssh seii@192.168.1.8

10. 安装 Tailscale

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

登录:

1
sudo tailscale up

查看:

1
2
tailscale status
tailscale ip

11. 安装 Claude Code

1
curl -fsSL https://claude.ai/install.sh | bash

确认:

1
claude --version

启动:

1
claude

12. 设置 seii 免密 sudo

1
sudo visudo -f /etc/sudoers.d/seii

加入:

1
seii ALL=(ALL) NOPASSWD: ALL

保存时如果看到:

1
File Name to Write: /etc/sudoers.d/seii.tmp

直接按:

1
Enter

然后:

1
Ctrl + X

检查:

1
sudo visudo -c

测试:

1
2
sudo -k
sudo whoami

应该直接输出:

1
root

现在这台 A1502 的状态已经是:Ubuntu Server + BCM4360 Wi-Fi + SSH + Tailscale + Claude Code + 免密 sudo。后续就可以基本脱离 HDMI 外接显示器,通过 SSH/Tailscale 管理这台 MacBook。


MacBook WiFi 服务器 SSH 断连排查与修复

适用机器:macbook-a1502(老款 MacBook,Broadcom BCM4360 无线网卡,用 WiFi 连接当服务器使用)。

症状

机器作为服务器运行时,NetworkManager 偶发进入异常状态,直接导致 SSH 断开。手动 sudo systemctl restart NetworkManager 可以恢复。

诊断方法

直接翻日志找证据:

1
2
3
4
5
6
7
8
9
10
11
12
# 该 wifi 连接自身的活动/状态变化
journalctl -u NetworkManager --no-pager -p warning

# wpa_supplicant 的关联/断线细节(deauth reason code 在这里看)
journalctl -u wpa_supplicant --no-pager

# 内核层驱动/固件报错
journalctl -k --no-pager | grep -iE "wl0|brcm|firmware|wlp3s0"

# 当前 powersave 是否生效
nmcli -f 802-11-wireless.powersave connection show "<连接名>"
cat /etc/NetworkManager/conf.d/*.conf

根因

  1. 网卡驱动本身不稳定:这颗 BCM4360(PCI ID 14e4:43a0)用的是专有闭源 wl(broadcom-sta,DKMS 编译)驱动,跟现代内核 + wpa_supplicant 的 nl80211 接口配合不好。日志里能看到周期性(约 10~15 分钟一次)的本机自发 deauth:

    1
    2
    3
    CTRL-EVENT-DISCONNECTED bssid=... reason=3 locally_generated=1
    nl80211: deinit ifname=wlp3s0 ...
    (几秒后自动重新关联)

    reason=3 + locally_generated=1 = 驱动自己主动断开重连,不是路由器或信号问题。多数情况下几秒内自愈。

  2. 偶发不能自愈的两种情况(这是需要手动重启 NM 的真正原因):

    • wpa_supplicant 直接从 D-Bus 掉线:Couldn't initialize supplicant interface: Failed to D-Bus activate wpa_supplicant service / Name owner lost
    • 关联彻底超时:link timed out / Activation: failed

    这两种情况下 NetworkManager 状态机卡死,不会自己恢复。

  3. 次要因素/etc/NetworkManager/conf.d/default-wifi-powersave-on.confnetwork-manager 包自带)全局开着 wifi.powersave = 3,对这颗本就不稳的驱动可能是加剧因素。

已实施的缓解措施

1. 关闭 WiFi 省电模式

包自带的 default-wifi-powersave-on.conf 是 dpkg 管理的 conffile,不要直接改,加一个文件名排在它后面(按字母序覆盖):

1
2
3
4
sudo tee /etc/NetworkManager/conf.d/zz-disable-wifi-powersave.conf > /dev/null <<'EOF'
[connection]
wifi.powersave = 2
EOF

2 = 禁用省电(0=用驱动默认,1=不改,3=启用)。

2. 自动看门狗

/usr/local/bin/network-watchdog.sh:每次被调用时 ping 默认网关,连续 3 次失败(间隔 5 秒,共约 15 秒)才判定为真掉线,5 分钟内不重复触发(防抖动):

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
#!/bin/bash
set -euo pipefail
STATE_FILE="/run/network-watchdog.last-restart"
LOG_TAG="network-watchdog"
COOLDOWN_SECS=300
RETRIES=3
RETRY_INTERVAL=5

GATEWAY=$(ip route show default 0.0.0.0/0 2>/dev/null | awk '/default/ {print $3; exit}')
[ -z "$GATEWAY" ] && { logger -t "$LOG_TAG" "no default gateway found"; exit 0; }

fail_count=0
for _ in $(seq 1 "$RETRIES"); do
ping -c1 -W2 "$GATEWAY" >/dev/null 2>&1 && exit 0
fail_count=$((fail_count + 1)); sleep "$RETRY_INTERVAL"
done

now=$(date +%s)
last_restart=0
[ -f "$STATE_FILE" ] && last_restart=$(cat "$STATE_FILE")
if [ $((now - last_restart)) -lt "$COOLDOWN_SECS" ]; then
logger -t "$LOG_TAG" "unreachable but restarted recently, skip"; exit 0
fi

logger -t "$LOG_TAG" "gateway $GATEWAY unreachable after $fail_count attempts, restarting NetworkManager"
echo "$now" > "$STATE_FILE"
systemctl restart NetworkManager

配套 network-watchdog.service(oneshot)+ network-watchdog.timer(每 2 分钟跑一次),sudo systemctl enable --now network-watchdog.timer 启用。

查看日志:journalctl -t network-watchdog -f

局限:ping 网关是反应式检测,测的是『本机到 AP 这一跳』,测不到自愈型的短暂 deauth(通常等不到检测窗口就已经好了),但恰好能抓住那两种真正需要重启才能恢复的情况。更精确的判据可以直接查 nmcli device statuswlp3s0 的状态或 busctl status fi.w1.wpa_supplicant1 看 wpa_supplicant 是否还在 D-Bus 上,比纯 ping 更贴近症状本身。

Comments

2026-03-26