Files
lzwlab_cn/setup.md
T

621 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 服务器部署记录:nginx 欢迎页(lzwlab.cn
> 说明:服务器凭据见 `Authentication.md`,本文档中密码一律以 `$PASS` 代替。
> 执行方式:本地通过 Paramiko 以 SSH 密码方式登录远程执行(远程用户 ubuntu,sudo 组免密)。
## 0. 环境信息
| 项目 | 值 |
| --- | --- |
| 服务器 IP | 1.15.225.173 |
| 登录用户 | ubuntusudo 组,`sudo -n` 可免密) |
| 系统 | Ubuntu 24.04.4 LTSkernel 6.8.0-124-generic,腾讯云 VM |
| 域名 | lzwlab.cn / www.lzwlab.cnDNS 已解析到 1.15.225.173 |
| 防火墙 | ufw inactive(无需额外放行) |
| 磁盘 | 50G,已用 5.3G |
## 1. 连接与初始状态检查
```bash
# 本地连接(密码 $PASS,取自 Authentication.md
ssh ubuntu@1.15.225.173
# 登录后检查系统
whoami && id && hostname && uname -a
cat /etc/os-release | head -3
# 检查 sudo 免密
sudo -n true && echo sudo-ok
# 检查 nginx(结果:未安装)
command -v nginx && nginx -v
# 检查 80/443 端口占用(结果:空闲)
ss -tlnp | grep -E ":(80|443)" || echo no-80-443
# 检查防火墙(结果:inactive)
sudo -n ufw status
# 检查域名解析(结果:均解析到 1.15.225.173
getent hosts lzwlab.cn www.lzwlab.cn
```
本机验证 DNS(结果:Address: 1.15.225.173):
```bash
nslookup lzwlab.cn
nslookup www.lzwlab.cn
```
## 2. 安装 nginx
```bash
sudo apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y nginx
```
结果:安装 nginx 1.24.0-2ubuntu7.17nginx + nginx-common)。
安装完成后 systemd 已自动启动 nginx 并注册开机自启(默认站点为 /var/www/html 的欢迎页)。
## 3. 创建站点内容
本地创建 `index.html`(欢迎页,中文标题“欢迎访问 lzwlab.cn”,1219 字节),
通过 SFTP 上传到服务器 /tmp,再用 root 安装到站点目录:
```bash
# 本地:上传文件
# Paramiko SFTP: index.html -> /tmp/lzwlab-index.html
# 远程:
sudo mkdir -p /var/www/lzwlab.cn
sudo install -m 644 -o root -g root /tmp/lzwlab-index.html /var/www/lzwlab.cn/index.html
```
页面内容要点(详见仓库内 `index.html`):
```html
<h1>🎉 欢迎访问 lzwlab.cn</h1>
<span class="badge">Powered by Nginx</span>
```
> 后续变更:2026-08-26 按需求移除了页面中的 `<p>网站搭建成功!这是部署在个人云服务器上的第一个页面。</p>` 提示信息(详见 CHANGELOG)。
## 4. 配置 nginx 站点
创建站点配置 `/etc/nginx/sites-available/lzwlab.cn`(内容同仓库内 `lzwlab.nginx.conf`):
```nginx
server {
listen 80;
listen [::]:80;
server_name lzwlab.cn www.lzwlab.cn;
root /var/www/lzwlab.cn;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
```
启用站点并移除默认站点:
```bash
# 上传配置
# Paramiko SFTP: lzwlab.nginx.conf -> /tmp/lzwlab.conf
# 远程:
sudo install -m 644 -o root -g root /tmp/lzwlab.conf /etc/nginx/sites-available/lzwlab.cn
sudo ln -sf /etc/nginx/sites-available/lzwlab.cn /etc/nginx/sites-enabled/lzwlab.cn
sudo rm -f /etc/nginx/sites-enabled/default
sudo rm -f /tmp/lzwlab-index.html /tmp/lzwlab.conf
# 校验配置语法
sudo nginx -t
# 结果:syntax is ok / test is successful
```
## 5. 启动 / 加载配置
```bash
sudo systemctl enable nginx
sudo systemctl start nginx
# 注意:apt 安装时 nginx 已启动并加载了默认配置,
# 所以修改配置后必须 reload 才会生效:
sudo nginx -t
sudo systemctl reload nginx
```
检查监听状态(结果:0.0.0.0:80 与 [::]:80 均已监听):
```bash
sudo systemctl is-active nginx # active
ss -tlnp | grep :80
```
## 6. 验证
服务器本机验证:
```bash
curl -s -o /dev/null -w "status=%{http_code} size=%{size_download}\n" http://127.0.0.1/ -H "Host: lzwlab.cn"
# status=200 size=1219
curl -s http://127.0.0.1/ -H "Host: lzwlab.cn" | grep -o "<title>.*</title>"
# <title>欢迎访问 lzwlab.cn</title>
```
本地(公网域名)验证:
```bash
curl -s -o /dev/null -w "status=%{http_code} size=%{size_download}\n" http://lzwlab.cn/
# status=200 size=1219
curl -s http://lzwlab.cn/ | grep -o "<title>.*</title>"
# <title>欢迎访问 lzwlab.cn</title>
curl -s -o /dev/null -w "status=%{http_code}\n" http://www.lzwlab.cn/
# status=200
```
✅ 部署完成:http://lzwlab.cn 与 http://www.lzwlab.cn 均已正常展示欢迎页。
## 7. 部署 HTTPS 证书并配置 443 自动跳转
### 7.1 证书信息
本地证书目录 `lzwlab.cn_nginx/`
| 文件 | 用途 |
| --- | --- |
| `lzwlab.cn.key` | RSA 私钥(1700 字节) |
| `lzwlab.cn_bundle.crt` / `.pem` | 证书 + 证书链(两者内容相同) |
| `lzwlab.cn.csr` | 证书签名请求(部署用不到) |
证书校验(本机 openssl):
```bash
openssl x509 -in lzwlab.cn_nginx/lzwlab.cn_bundle.crt -noout -subject -issuer -dates
# subject=CN=lzwlab.cn
# issuer=TrustAsia DV TLS RSA CA 2024
# 有效期:2026-08-24 ~ 2026-11-22
openssl rsa -in lzwlab.cn_nginx/lzwlab.cn.key -check -noout # RSA key ok
```
SAN 包含:`DNS:lzwlab.cn``DNS:www.lzwlab.cn`
### 7.2 上传证书与配置到服务器
```bash
# 本地:SFTP 上传到 /tmp
# lzwlab.cn_nginx/lzwlab.cn.key -> /tmp/lzwlab.cn.key
# lzwlab.cn_nginx/lzwlab.cn_bundle.crt -> /tmp/lzwlab.cn_bundle.crt
# lzwlab.nginx.conf -> /tmp/lzwlab.conf
# 远程:安装证书/私钥并更新站点配置
sudo mkdir -p /etc/nginx/ssl
sudo install -m 600 -o root -g root /tmp/lzwlab.cn.key /etc/nginx/ssl/lzwlab.cn.key
sudo install -m 644 -o root -g root /tmp/lzwlab.cn_bundle.crt /etc/nginx/ssl/lzwlab.cn_bundle.crt
sudo cp /etc/nginx/sites-available/lzwlab.cn /etc/nginx/sites-available/lzwlab.cn.bak
sudo install -m 644 -o root -g root /tmp/lzwlab.conf /etc/nginx/sites-available/lzwlab.cn
sudo rm -f /tmp/lzwlab.cn.key /tmp/lzwlab.cn_bundle.crt /tmp/lzwlab.conf
```
私钥权限 600、root:root;证书 644。旧配置备份为 `lzwlab.cn.bak`
### 7.3 更新后的站点配置(`/etc/nginx/sites-available/lzwlab.cn`
```nginx
server {
listen 80;
listen [::]:80;
server_name lzwlab.cn www.lzwlab.cn;
return 301 https://$host$request_uri; # HTTP 全部 301 跳转到 HTTPS
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name lzwlab.cn www.lzwlab.cn;
ssl_certificate /etc/nginx/ssl/lzwlab.cn_bundle.crt;
ssl_certificate_key /etc/nginx/ssl/lzwlab.cn.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
root /var/www/lzwlab.cn;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
```
### 7.4 校验并加载
```bash
sudo nginx -t # syntax is ok / test is successful
sudo systemctl reload nginx
```
### 7.5 验证结果
服务器本机:
```bash
curl -s -o /dev/null -w "http status=%{http_code} redirect=%{redirect_url}\n" http://127.0.0.1/ -H "Host: lzwlab.cn"
# status=301 redirect=https://lzwlab.cn/
curl -s https://127.0.0.1/ -k -H "Host: lzwlab.cn" | grep -o "<title>.*</title>"
# <title>欢迎访问 lzwlab.cn</title>
```
本地公网:
```bash
curl -s -o /dev/null -w "status=%{http_code} -> %{redirect_url}\n" http://lzwlab.cn/
# status=301 -> https://lzwlab.cn/
curl -s -o /dev/null -w "status=%{http_code} ssl_verify=%{ssl_verify_result}\n" https://lzwlab.cn/
# status=200 ssl_verify=0(证书链受信任)
curl -s -o /dev/null -w "status=%{http_code}\n" https://www.lzwlab.cn/
# status=200
echo | openssl s_client -connect lzwlab.cn:443 -servername lzwlab.cn -alpn h2,http/1.1 2>/dev/null | grep "ALPN"
# ALPN protocol: h2HTTP/2 已启用)
```
✅ https://lzwlab.cn 与 https://www.lzwlab.cn 已启用 HTTPShttp 访问会自动 301 跳转到 https。
## 8. 部署“文件快传”工具(/transfer/)
### 8.1 方案
- 后端:Flask 3.0.2 + gunicorn 20.1apt 安装),监听 `127.0.0.1:8090`systemd 服务 `lzwlab-transfer.service`
- 前端:单页 HTML(内嵌 CSS/JS),PC 与手机自适应,登录后可用。
- 存储:上传文件保存在 `/var/lib/lzwlab-transfer/files/`,元数据在 SQLite `/var/lib/lzwlab-transfer/files.db`
- 配置:`/etc/lzwlab-transfer/config.json`root:lzwlab 640),登录密码只保存 scrypt 哈希,明文经部署环境变量传入。
- 清理:`lzwlab-transfer-cleanup.timer` 每 10 分钟执行 `cleanup.py` 删除过期文件;访问列表/手动清理时也会触发检查。
- nginx`/transfer/` 反向代理到 8090`proxy_request_buffering off` + `proxy_buffering off` 保证大文件上传下载流式传输,`client_max_body_size 5120m`
### 8.2 部署命令(本地运维机)
```bash
# 依赖脚本见仓库 deploy_transfer.py;密码经环境变量传入,不会写入仓库
LZWLAB_APP_PASSWORD='$PASS' python deploy_transfer.py --username admin
```
脚本会依次完成:apt 安装依赖 → 创建系统用户 `lzwlab` 与目录 → 上传/安装应用、配置、systemd 单元 → 启动服务与清理定时器 → 校验并 reload nginx。重复执行幂等。
### 8.3 验证结果
```bash
systemctl is-active lzwlab-transfer.service lzwlab-transfer-cleanup.timer # active active
curl -s -o /dev/null -w "%{http_code}\n" https://lzwlab.cn/transfer/ # 200(登录页)
curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" http://lzwlab.cn/transfer/ # 301 -> https
```
公网 API 实测:登录成功返回会话 Cookie;未登录访问 `/api/files` 返回 401;上传 25 字节测试文件后列表可查,下载返回 200 且支持 `Range`(206);删除后列表为空;将测试文件过期时间强制置为过去并启动清理服务后,日志输出“清理完成:过期文件 1 个”,列表随即清空。
### 8.4 踩坑
- `deploy_transfer.py` 在 Windows 本地生成远端路径时不能使用 `os.path.join`(会把 `/` 变成 `\`),SQLite 路径必须手工拼接 Linux 路径,否则服务报 `unable to open database file`
- gunicorn 手动排障时不要在 SSH 会话里前台运行(会话断开会留下占端口的孤儿进程),应直接通过 `systemctl` 管理;排障后可 `sudo pkill -u lzwlab -f gunicorn && sudo systemctl restart lzwlab-transfer.service`
- Windows 下 curl 的 `-F 'file=@...;filename=中文名'` 对非 ASCII 文件名支持不佳,测试时用 ASCII 文件名,浏览器上传不受影响。
## 9. 部署 RustDesk 自建服务器(Docker
### 9.1 方案
- 组件:RustDesk Server OSS 版两个容器——hbbs(ID/注册服务器)与 hbbr(中继服务器),镜像 `rustdesk/rustdesk-server:latest`
- 网络:`network_mode: host`(官方推荐,无需 `-p` 映射),数据卷 `./data` 挂载到容器 `/root`(密钥、SQLite 库)。
- 端口(防火墙需放行):
- hbbs21115/TCPNAT 类型测试)、21116/TCP+UDPUDP 用于 ID 注册与心跳,TCP 用于打洞与连接服务)、21118/TCP(网页客户端);
- hbbr21117/TCP(中继服务)、21119/TCP(网页客户端);
- 21114/TCP 网页控制台仅 Pro 版需要,OSS 版不监听。
- 参考文档:https://rustdesk.com/docs/zh-cn/self-host/rustdesk-server-oss/docker/ ;客户端配置:https://rustdesk.com/docs/zh-cn/self-host/client-configuration/
### 9.2 安装 Docker 并配置镜像加速
```bash
sudo apt-get update
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y docker.io docker-compose-v2
sudo systemctl enable --now docker
docker --version # Docker version 29.1.3apt docker.io 自带守护进程服务)
```
踩坑:腾讯云访问 Docker Hub 被拒(`registry-1.docker.io` connection refused),必须配置镜像加速:
```bash
echo '{"registry-mirrors":["https://docker.m.daocloud.io"]}' | sudo tee /etc/docker/daemon.json
sudo systemctl restart docker
```
### 9.3 部署 hbbs/hbbr
Compose 文件为仓库内 `rustdesk/docker-compose.yml`
```yaml
services:
hbbs:
container_name: hbbs
image: rustdesk/rustdesk-server:latest
command: hbbs
volumes:
- ./data:/root
network_mode: "host"
depends_on:
- hbbr
restart: unless-stopped
hbbr:
container_name: hbbr
image: rustdesk/rustdesk-server:latest
command: hbbr
volumes:
- ./data:/root
network_mode: "host"
restart: unless-stopped
```
部署命令:
```bash
# 本地:SFTP 上传 rustdesk/docker-compose.yml 到 /tmp
sudo mkdir -p /opt/rustdesk
sudo install -m 644 -o root -g root /tmp/rustdesk-compose.yml /opt/rustdesk/compose.yml
cd /opt/rustdesk && sudo docker compose up -d
```
验证结果:
```bash
sudo docker ps # hbbs、hbbr 均 Up
sudo ss -tlnup | grep -E '2111[4-9]'
# tcp 21115/21116、udp 21116、tcp 21118 归 hbbstcp 21117/21119 归 hbbr
sudo docker logs --tail 5 hbbs
# Key: UB1fNGcVYA9NTIEA7ra3Nm0R6o2m64lNQE24L7CIAkY=
# Listening on tcp/udp :21116 / Listening on tcp :21115 / Listening on websocket :21118
```
### 9.4 客户端配置
RustDesk 客户端(设置 → 网络,点击“解锁”后可编辑)填写:
| 项目 | 值 |
| --- | --- |
| ID 服务器 | `lzwlab.cn`hbbs,端口 21116 |
| 中继服务器 | 留空自动推导;或填 `lzwlab.cn`hbbr,端口 21117 |
| Key | `UB1fNGcVYA9NTIEA7ra3Nm0R6o2m64lNQE24L7CIAkY=` |
| API 服务器 | 留空(仅 Pro 版需要) |
- Key 由 hbbs 首次启动自动生成,保存在服务器 `/opt/rustdesk/data/id_ed25519.pub`(容器内 `/root/id_ed25519.pub`),重建容器会复用;Key 需分发给每个客户端。
- 连通性:本地已验证 21115-21119 TCP 公网可达;hbbs 日志可见外部客户端注册记录。UDP 21116 用于 ID 注册与心跳,若腾讯云控制台防火墙未放行 UDP 21116,客户端将无法注册。
### 9.5 运维
```bash
# 更新镜像并重建
cd /opt/rustdesk && sudo docker compose pull && sudo docker compose up -d
# 查看日志
sudo docker logs -f hbbs
sudo docker logs -f hbbr
# 查看密钥
sudo cat /opt/rustdesk/data/id_ed25519.pub
```
## 10. 部署 Gitea Git 服务(/git/
### 10.1 方案
- 组件:Gitea 1.27.2 官方镜像 `docker.gitea.com/gitea:1.27.2`Docker Compose,容器名 `gitea`),数据库 SQLite,数据卷 `./gitea:/data`(持久化在服务器 `/opt/gitea/gitea/`)。
- 入口:服务器没有 `git.lzwlab.cn` 子域名解析,按 Gitea 官方反向代理文档的 sub-path 方案部署在 `https://lzwlab.cn/git/``ROOT_URL=https://lzwlab.cn/git/`)。
- 端口:Web 仅监听 `127.0.0.1:3000`(不对公网直接暴露),由 nginx 将 `/git/` 反代到 3000;SSH 端口与主机 sshd 冲突,容器内 22 映射到主机 2222。
- 账号:管理员私有单实例——关闭开放注册(`DISABLE_REGISTRATION=true`)、锁定安装器(`INSTALL_LOCK=true`),管理员 `admin`(密码见 Authentication.md)。
- 参考文档:https://docs.gitea.com/installation/install-with-docker/ 与 https://docs.gitea.com/administration/reverse-proxies/ (“Nginx with a sub-path”)。
### 10.2 部署命令
Compose 文件为仓库内 `gitea/docker-compose.yml`
```bash
# 本地:SFTP 上传 gitea/docker-compose.yml 到 /tmp
sudo mkdir -p /opt/gitea
sudo install -m 644 -o root -g root /tmp/gitea-compose.yml /opt/gitea/compose.yml
sudo rm -f /tmp/gitea-compose.yml
cd /opt/gitea && sudo docker compose up -d
```
创建管理员并设置站点名:
```bash
# 管理员(密码经变量传入,不写入仓库;首次启动完成后执行)
sudo docker exec -u git gitea gitea admin user create \
--username admin --password '$PASS' --email admin@lzwlab.cn \
--admin --must-change-password=false
# 站点名写入 app.ini 的 [DEFAULT] 段后重建容器
sudo sed -i '1s/.*/APP_NAME = lzwlab Git 服务/' /opt/gitea/gitea/gitea/conf/app.ini
cd /opt/gitea && sudo docker compose up -d
```
### 10.3 nginx 反代(/git/
按官方 “Nginx with a sub-path” 方案(保持 `%2F` 原样并剥离 `/git` 前缀),在 443 server 块新增:
```nginx
location ~ ^/(git)($|/) {
client_max_body_size 512M;
rewrite ^ $request_uri;
rewrite ^/(git($|/))?(.*) /$3 break;
proxy_pass http://127.0.0.1:3000$uri;
proxy_http_version 1.1;
proxy_set_header Connection $http_connection;
proxy_set_header Upgrade $http_upgrade;
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_read_timeout 300s;
}
```
该配置已并入仓库内 `lzwlab.nginx.conf`,服务器 `/etc/nginx/sites-available/lzwlab.cn` 同步更新(改前自动备份 `lzwlab.cn.bak-YYYYMMDD-HHMMSS`),`sudo nginx -t` 通过后 reload。
### 10.4 验证结果
```bash
curl -s -o /dev/null -w "%{http_code}\n" https://lzwlab.cn/git/ # 200
curl -s https://lzwlab.cn/git/api/v1/version # {"version":"1.27.2"}
curl -s https://lzwlab.cn/git/ | grep -o '<title>.*</title>' # <title>lzwlab Git 服务</title>
curl -s https://lzwlab.cn/ | grep -o '进入 Git 服务' # 进入 Git 服务(首页按钮已生效)
# git 智能 HTTP 协议(创建测试仓库 hello 后):
curl -s 'https://lzwlab.cn/git/admin/hello.git/info/refs?service=git-upload-pack'
# 返回 service=git-upload-pack 与 HEAD 引用;测试仓库与临时令牌验证后已删除
```
关键配置项(`/opt/gitea/gitea/gitea/conf/app.ini`):`ROOT_URL=https://lzwlab.cn/git/``DOMAIN/SSH_DOMAIN=lzwlab.cn``SSH_PORT=2222``SSH_LISTEN_PORT=22`(容器内)、`INSTALL_LOCK=true``DISABLE_REGISTRATION=true``APP_NAME=lzwlab Git 服务`
### 10.5 使用与运维
- 克隆地址:
- HTTPS(公网可用):`https://lzwlab.cn/git/<用户名>/<仓库>.git`
- SSH(需先放行安全组,见 10.6):`ssh://git@lzwlab.cn:2222/<用户名>/<仓库>.git`
- 运维命令:
```bash
sudo docker compose -f /opt/gitea/compose.yml ps # 容器状态
sudo docker logs -f gitea # 日志
cd /opt/gitea && sudo docker compose pull && sudo docker compose up -d # 升级镜像
sudo docker exec -u git gitea gitea admin user list # 用户列表
```
- 备份:SQLite 库、仓库与配置全部在数据卷内,直接备份 `/opt/gitea/gitea/` 即可。
### 10.6 踩坑
- `GITEA__APP_NAME` 环境变量传入容器后未生效(站点名仍显示默认值),站点名需直接写入 `app.ini``[DEFAULT]` 段(`APP_NAME = ...`)并重建容器。
- 腾讯云安全组只放行 22/443/21115-21119,主机映射的 2222 端口公网不可达(本机探测 TIMEOUT,服务器本地可达):SSH 克隆需在腾讯云控制台为 TCP 2222 添加入站规则;HTTPS 克隆与网页访问不受影响。
- Gitea 子路径部署必须同时满足两点:`ROOT_URL``/git/` 后缀,且 nginx 使用官方 rewrite 方案剥离前缀(直接 `proxy_pass http://127.0.0.1:3000/;` 会破坏前缀与 `%2F` 转义)。
- 镜像使用官方新仓库 `docker.gitea.com/gitea:1.27.2`(腾讯云可直连,无需镜像加速);若改用 Docker Hub 的 `gitea/gitea` 则会走 daocloud 加速。
## 11. Komari Lite 服务器监控(已卸载)
> 该服务曾部署到 https://status.lzwlab.cn/,后按需求从服务器卸载,首页入口已删除。此前的部署配置与完整记录见 git 历史(commit bdffcee / 60b9ce1)。nginx server 级 return 的踩坑保留在下方“附:踩坑记录”第 6 条。
卸载命令:
```bash
cd /opt/komari && sudo docker compose down
sudo docker rmi ghcr.io/nuomiiiii/komari:latest ghcr.nju.edu.cn/nuomiiiii/komari:latest
sudo rm -f /etc/nginx/sites-enabled/status.lzwlab.cn /etc/nginx/sites-available/status.lzwlab.cn /etc/nginx/sites-available/status.lzwlab.cn-http /etc/nginx/sites-available/status.lzwlab.cn.bak-*
sudo certbot delete --cert-name status.lzwlab.cn --non-interactive
sudo rm -rf /var/www/certbot /opt/komari
sudo nginx -t && sudo systemctl reload nginx
```
## 12. 部署 Beszel 服务器监控(https://lzwlab.cn/beszel/
### 12.1 方案与要点
- 组件:Beszel **hub**(基于 PocketBase 的 Web 面板,Docker 镜像 `henrygd/beszel:latest`)。每个被监控服务器再运行一个 **agent**`henrygd/beszel-agent`),由用户在 Web 面板中添加系统时生成配置。
- 入口:按需求将 Beszel 主页反代到主站子路径 `https://lzwlab.cn/beszel/`(首页欢迎页“📊 服务器状态”按钮)。
- 端口:Beszel 默认端口 8090 与本机文件快传的 gunicorn127.0.0.1:8090)冲突,因此容器 8090 只映射到宿主机 `127.0.0.1:8092`
- 子路径关键配置:hub 的 `APP_URL` 必须设为 `https://lzwlab.cn/beszel`,否则通知链接与 Agent 配置生成会指向错误地址。
- 参考文档:https://www.beszel.dev/zh/guide/getting-started 、https://www.beszel.dev/zh/guide/hub-installation 、https://www.beszel.dev/zh/guide/reverse-proxy 。
### 12.2 部署命令
Compose 文件为仓库内 `beszel/docker-compose.yml`,服务器安装到 `/opt/beszel/`。首次启动通过 `.env`root:root 0600)设置 `USER_EMAIL` / `USER_PASSWORD` 自动创建第一个管理员:
```bash
# 本地:SFTP 上传 beszel/docker-compose.yml 与临时 .env 到 /tmp
# 远程:
sudo mkdir -p /opt/beszel
sudo install -m 600 -o root -g root /tmp/beszel.env /opt/beszel/.env
sudo install -m 644 -o root -g root /tmp/beszel-compose.yml /opt/beszel/docker-compose.yml
sudo rm -f /tmp/beszel.env /tmp/beszel-compose.yml
cd /opt/beszel && sudo docker compose up -d
# 验证管理员已创建后,清空 .env 中的明文密码(管理员账号已持久化在 beszel_data
sudo sh -c 'echo > /opt/beszel/.env' && sudo chmod 600 /opt/beszel/.env
cd /opt/beszel && sudo docker compose up -d
```
`.env` 内容(仅首次启动需要,密码记录在 `Authentication.md`):
```dotenv
USER_EMAIL=beszel@lzwlab.cn
USER_PASSWORD=$PASS
```
### 12.3 nginx 反代(/beszel/
按 Beszel 官方 “使用 Nginx 在子路径提供服务” 方案,在 `lzwlab.nginx.conf` 的 443 server 块新增(已并入仓库配置):
```nginx
location = /beszel {
return 301 /beszel/;
}
location /beszel/ {
client_max_body_size 10M;
proxy_read_timeout 360s;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
rewrite /beszel/(.*) /$1 break;
proxy_pass http://127.0.0.1:8092;
}
```
改配置后执行 `sudo nginx -t && sudo systemctl reload nginx`
### 12.4 验证结果
```bash
sudo docker ps --filter name=beszel # Up (healthy)127.0.0.1:8092->8090/tcp
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8092/ # 200(本机 hub
# 公网:
curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" https://lzwlab.cn/beszel
# 301 -> https://lzwlab.cn/beszel/
curl -s -o /dev/null -w "%{http_code}\n" https://lzwlab.cn/beszel/ # 200
curl -sL https://lzwlab.cn/beszel/ | grep -o '<title>.*</title>' # <title>Beszel</title>
curl -s https://lzwlab.cn/ | grep -o '服务器状态' # 服务器状态(首页按钮已生效)
```
### 12.5 添加监控节点(Agent
1. 打开 `https://lzwlab.cn/beszel/`,用 `Authentication.md` 中的管理员邮箱/密码登录。
2. 点击右上角 **Add System**,填写系统名称,选择 WebSocket 或 SSH 连接方式,复制对话框生成的 Docker Compose 或二进制安装命令到目标服务器执行。
3. 若目标服务器就是本机,且用 Docker 运行 agentagent 默认监听 45876,与本机其他服务无端口冲突;WebSocket 模式将 `HUB_URL` 设为 `https://lzwlab.cn/beszel``KEY`/`TOKEN` 使用对话框生成的值。若想监控宿主机网络/容器,需给 agent 挂载 `/var/run/docker.sock:ro` 并使用 host 网络。
4. 完成后回到对话框点击 **Add System**,系统状态变为绿色即接入成功。
### 12.6 运维
```bash
cd /opt/beszel && sudo docker compose ps # hub 容器状态
sudo docker logs -f beszel # hub 日志
cd /opt/beszel && sudo docker compose pull && sudo docker compose up -d # 升级 hub
# 备份:PocketBase 数据在数据卷内,直接备份 /opt/beszel/beszel_data/ 即可
```
### 12.7 踩坑
- Beszel hub 默认端口 8090 与本机文件快传 gunicorn 冲突,必须改映射(本项目使用 127.0.0.1:8092:8090)。
- 子路径部署必须设置 `APP_URL=https://lzwlab.cn/beszel`,并让 nginx 用 rewrite 去掉 `/beszel` 前缀;否则通知链接和 Agent 配置会指向错误地址。
- `USER_EMAIL` / `USER_PASSWORD` 只在首次启动(尚无用户)时用于创建管理员;创建完成后应清空 `.env`,避免明文密码残留在服务器配置文件中。
## 附:踩坑记录
1. 本地 Git Bash 调用 Windows Python 时,MSYS 会把以 `/` 开头的命令行参数(如 `/tmp/...`)转换成 Windows 路径,
导致 SFTP 上传远端路径错误(ENOENT)。解决:调用前加 `MSYS_NO_PATHCONV=1`,或把路径写进脚本内。
2. apt 安装 nginx 会自动启动服务并加载默认站点配置;改完站点配置后 `systemctl start` 不会重新加载,
必须 `systemctl reload nginx`(或 `restart`),否则看到的仍是默认欢迎页。
3. 该服务器上的 nginx 1.24(Ubuntu 构建)不识别独立指令 `http2 on;``nginx -t` 报 unknown directive "http2"
启用 HTTP/2 应写成 `listen 443 ssl http2;`
4. 腾讯云服务器访问 Docker Hub 被拒(pull 报 connection refused),通过 `/etc/docker/daemon.json` 配置
`registry-mirrors`docker.m.daocloud.io)后解决。
5. `remote.py``--timeout` 参数必须放在远程命令之前(`python remote.py --timeout 500 "命令"`);
放在命令之后会被 argparse 当作远程命令的一部分传给服务器执行。
6. nginx 中 server 级的 `return` 在 rewrite 阶段(location 匹配之前)执行并直接终结请求:`server { location /.well-known/acme-challenge/ {...} return 301 ...; }` 的挑战路径照样会被 301,导致 certbot 签发失败(LE 跟随 301 到 HTTPS 后得到 404)。必须把重定向写进 `location / { return 301 ...; }` 内,让 location 先参与匹配。