部署到服务器

上线前有三件事先定下来,后面所有选择都是它们的推论。

  1. 数据放在哪。 应用文件(data/)和数据库是两处存储,备份与恢复都要成对处理。
  2. TLS 在哪终止。 推荐由反向代理终止,Lumo 只监听 127.0.0.1
  3. 凭据谁管。 数据库连接串只走环境变量;SMTP 口令与对象存储密钥在后台填、加密入库。

方式一:二进制 + systemd

部署产物是单个静态二进制,不需要目标机器上有任何运行库。

sudo useradd --system --home /srv/lumo --shell /usr/sbin/nologin lumo
sudo install -d -o lumo -g lumo /srv/lumo /srv/lumo/data
sudo install -m 0755 -o lumo -g lumo lumo /srv/lumo/lumo

/etc/lumo.env权限设成 0600,里面是数据库口令):

LUMO_DATA_DIR=/srv/lumo/data
LUMO_DATABASE_DSN=postgres://lumo:口令@127.0.0.1:5432/lumo?sslmode=disable
LUMO_SECURE_COOKIES=true
LUMO_TRUSTED_PROXIES=127.0.0.1/32

/etc/systemd/system/lumo.service

[Unit]
Description=Lumo CMS
After=network-online.target postgresql.service
Wants=network-online.target

[Service]
Type=simple
User=lumo
Group=lumo
WorkingDirectory=/srv/lumo
EnvironmentFile=/etc/lumo.env
ExecStart=/srv/lumo/lumo serve
Restart=on-failure
RestartSec=5
# 停机时留出时间排空邮件队列与在途请求
TimeoutStopSec=40

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload && sudo systemctl enable --now lumo
sudo journalctl -u lumo -f

首次启动会在日志里打出安装向导地址;也可以在 /etc/lumo.env 里预先设好 DSN,跳过向导、改用 lumo admin create-user 建管理员。

方式二:Docker Compose

仓库的 deploy/ 下有整套文件:多阶段构建的 Dockerfile、带 PostgreSQL 17 的 docker-compose.yml.env.example

cd deploy
cp .env.example .env          # 至少要填 POSTGRES_PASSWORD
docker compose up -d
docker compose logs -f lumo

几个刻意如此的默认值:

默认 为什么
端口只绑 127.0.0.1:8080 TLS 交给前面的反向代理;要直接对外就改 .env 里的 LUMO_BIND
PostgreSQL 不映射端口 它只需被同一 compose 网络内的应用访问
POSTGRES_PASSWORD 无默认值 没填就让 compose 当场报错,而不是用弱口令把库跑起来
LUMO_SECURE_COOKIES=false 纯 HTTP 下开它会让登录「成功后立刻失效」;上了 HTTPS 必须改成 true
LUMO_TRUSTED_PROXIES 为空 不采信任何 X-Forwarded-For,避免客户端伪造来源 IP

两处容易踩的坑:

  • 数据库口令含特殊字符@ : / ? # % 等)时要分开填两个变量:POSTGRES_PASSWORD 保持原始口令,LUMO_DATABASE_DSN 单独填只对口令段做百分号编码的完整 URL。把编码后的串填进 POSTGRES_PASSWORD 会让首次部署必然失败。
  • 镜像里没有 shell(distroless),排障只能靠 docker compose logs。容器内也无法做 HEALTHCHECK,探活从外部请求 /healthz(进程存活)或 /readyz(额外探测数据库,可用于负载均衡摘流)。

反向代理与 HTTPS

Lumo 用一次性 cookie 与 SameSite=Lax,在 HTTPS 后面需要两项配置:

LUMO_SECURE_COOKIES=true          # 会话 Cookie 加 Secure 属性,并启用 __Host- 前缀
LUMO_TRUSTED_PROXIES=127.0.0.1/32 # 可信反代的 CIDR

LUMO_SECURE_COOKIES 不只是加个 Secure 属性——它会连带把 cookie 名换成 __Host- 前缀,防的是同域下别的子域往你这里写会话 cookie。站点在某个域名的子域上时,这条是真在防东西。

开着它时改动的效果是现有的浏览器登录全部失效(cookie 名变了),重新登录一次即可;API 令牌不受影响。

LUMO_TRUSTED_PROXIES 不配的话,程序在日志里会提示「未配置可信代理,将忽略 X-Forwarded-For 并使用直连地址」——所有访客在它眼里是同一个来源 IP,评论的 IP 限流会退化成全站共用一个额度。填反代的来源地址:反代和 Lumo 在同一台机器上就是 127.0.0.1/32,反代在容器里则是 docker 网关地址。

nginx 侧最小配置:

location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    client_max_body_size 64m;   # 不小于 Lumo 的 multipart 上传上限
}

client_max_body_size 别忘。 Lumo 的普通请求与 multipart 上传是两条独立上限(默认 10 MiB / 64 MiB),代理层卡住会比应用层先报 413,现象是「小文件能传、大文件 413」。

站点地址要在后台「设置 → 站点信息」里填成最终对外的地址(含 https://),canonical、sitemap.xmlfeed.xml 里的绝对链接都取自它。

备份

数据库、data/ 目录、以及环境变量文件三样都要备。 密钥只存在于 data/secret.key 里,库和卷里都没有;/etc/lumo.envdeploy/.env 含数据库口令,必须单独备份。

要让库与文件落在同一时点,先停应用再备

sudo systemctl stop lumo

pg_dump -Fc lumo > lumo-db-$(date +%Y%m%d-%H%M%S).dump
tar czf lumodata-$(date +%Y%m%d-%H%M%S).tar.gz -C /srv/lumo/data uploads themes plugins secret.key install.json

sudo systemctl start lumo

备份里含口令哈希、会话与令牌的哈希,按密钥对待:不要放公开目录,传输与长期保存都要加密。

附件存在对象存储里时,上传文件都不在 data/ 下,只备卷等于没备附件——给桶开版本控制并设足够的保留期,或定期导出。恢复时桶内容必须与数据库 dump 对应同一时点。

恢复

先停应用。删库重建,而不是只加 --clean——--clean 清不掉上一版新建的表。

sudo systemctl stop lumo

dropdb --force lumo && createdb -O lumo lumo
pg_restore -d lumo lumo-db.dump

# 卷:清掉旧内容再解开归档
rm -rf /srv/lumo/data/{uploads,themes,plugins}
tar xzf lumodata.tar.gz -C /srv/lumo/data

sudo systemctl start lumo

Docker 部署时把属主改回 65532:65532(镜像里的 nonroot),否则文件写不进去。

恢复后逐项检查/healthz/readyz 返回 200;日志里没有迁移报错;原账号能登录;抽一篇已发布文章前台渲染正常;附件可访问、媒体库能列出;评论正常显示。

升级与回滚

先把镜像或二进制钉到具体版本,备份三样之后再动手。

sudo systemctl stop lumo
sudo install -m 0755 -o lumo -g lumo lumo-new /srv/lumo/lumo
sudo systemctl start lumo        # 启动时自动执行迁移

迁移只向前。 新版本还没跑过迁移时,换回旧二进制即可回滚;一旦跑了迁移,就必须恢复升级前的数据库 dump 与 data/ 归档,再切回旧版本(顺序:停 → 换版本 → 恢复库 → 恢复文件 → 起)。

Docker 部署同理:docker compose pull lumo && docker compose up -d lumo;回滚要把 compose 里的 image: 钉回具体版本或摘要。

排障

现象 先看什么
启动即退出 日志里会打出首个错误;install.json 不存在时会转入安装模式,不是故障
登录成功但立刻失效 纯 HTTP 下 LUMO_SECURE_COOKIES=true 的表现,或 HTTPS 下反代没传 X-Forwarded-Proto
大文件上传 413 反代的 client_max_body_size 小于 Lumo 的上传上限
访客 IP 全一样 没配 LUMO_TRUSTED_PROXIES
/readyz 返回 503 数据库连不上,看 DSN 与网络;/healthz 只探测进程存活
前台样式丢失 反向代理没放行 /theme-assets/**/uploads/**
后台打开是构建提示 二进制构建前没有生成前端产物,task all 而不是 task build

评论

还没有评论,来说两句。