# 服务器部署记录:nginx 欢迎页(lzwlab.cn) > 说明:服务器凭据见 `Authentication.md`,本文档中密码一律以 `$PASS` 代替。 > 执行方式:本地通过 Paramiko 以 SSH 密码方式登录远程执行(远程用户 ubuntu,sudo 组免密)。 ## 0. 环境信息 | 项目 | 值 | | --- | --- | | 服务器 IP | 1.15.225.173 | | 登录用户 | ubuntu(sudo 组,`sudo -n` 可免密) | | 系统 | Ubuntu 24.04.4 LTS(kernel 6.8.0-124-generic,腾讯云 VM) | | 域名 | lzwlab.cn / www.lzwlab.cn(DNS 已解析到 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.17(nginx + 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

🎉 欢迎访问 lzwlab.cn

Powered by Nginx ``` > 后续变更:2026-08-26 按需求移除了页面中的 `

网站搭建成功!这是部署在个人云服务器上的第一个页面。

` 提示信息(详见 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 ".*" # 欢迎访问 lzwlab.cn ``` 本地(公网域名)验证: ```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 ".*" # 欢迎访问 lzwlab.cn 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 ".*" # 欢迎访问 lzwlab.cn ``` 本地公网: ```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: h2(HTTP/2 已启用) ``` ✅ https://lzwlab.cn 与 https://www.lzwlab.cn 已启用 HTTPS;http 访问会自动 301 跳转到 https。 ## 8. 部署“文件快传”工具(/transfer/) ### 8.1 方案 - 后端:Flask 3.0.2 + gunicorn 20.1(apt 安装),监听 `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 库)。 - 端口(防火墙需放行): - hbbs:21115/TCP(NAT 类型测试)、21116/TCP+UDP(UDP 用于 ID 注册与心跳,TCP 用于打洞与连接服务)、21118/TCP(网页客户端); - hbbr:21117/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.3(apt 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 归 hbbs;tcp 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 '.*' # lzwlab Git 服务 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 加速。 ## 附:踩坑记录 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 当作远程命令的一部分传给服务器执行。