Offen 1.4.2 配置参数详解
Offen 1.4.2 运行期配置全量详解:22 个 OFFEN_* 环境变量的作用域/默认值/取值约束、env 文件查找优先级实测、offen debug 配置校验、_FILE 敏感值注入与三套生产配置模板,全部参数经启动行为与响应头实测。
Offen 1.4.2 配置参数详解
目标:把 Offen Fair Web Analytics v1.4.2 的运行期配置一次讲全——全部
OFFEN_<scope>_<key>环境变量的作用、默认值、取值约束与典型组合,覆盖 env 文件查找优先级、offen debug配置预检、_FILE后缀敏感值注入,并给出三套标明内存/磁盘/场景的生产配置模板。全文行为与回显均为 2026-10-30 在 Ubuntu 22.04.5 LTS + Offen v1.4.2 环境实测记录。
一、环境信息
本篇实测环境与软件版本如下:
- 操作系统:Ubuntu 22.04.5 LTS(x86_64,
PRETTY_NAME="Ubuntu 22.04.5 LTS") - 软件版本:Offen v1.4.2(
offen version回显revision=v1.4.2) - 二进制位置:
/usr/local/bin/offen(安装过程见 《Ubuntu 22.04 安装 Offen 1.4.2 指南》) - 配置文件:
/etc/offen/offen.env(deb 包安装自带模板,本篇实验基线内容见 3.3) - systemd 服务:
offen.service(systemctl is-active返回active) - 监听端口:8080(实验基线,默认为 3000)
- 数据库:SQLite 单文件
/var/opt/offen/offen.db - 演示账户:
admin@example.com/q1w2e3r4(安装篇offen setup创建)
工具定位与架构背景(事件为何在浏览器端加密、单二进制内含哪些组件)见 《Offen 介绍与选型指南》,本篇只聚焦"配置怎么写、写了之后行为如何验证"。
二、前置条件
开始前确认以下条件满足:
- 一个可运行的 Offen 实例:按 《Ubuntu 22.04 安装 Offen 1.4.2 指南》完成安装即可;本篇的每个示例命令只依赖
offen二进制与一份 env 文件,可独立执行,不要求特定托管方式。确认安装:
which offen && offen version
实测回显:
/usr/local/bin/offen
time="2026-10-30T02:03:20Z" level=info msg="Binary built using" revision=v1.4.2
- 一份配置文件:deb 包方案在
/etc/offen/offen.env;其他方案自建即可(内容写法见第四章总览与第六章模板)。 - 改配置的生效方式:Offen 在进程启动时读取配置,运行中不会热加载——改动 env 文件后需要
systemctl restart offen生效。写错值多数会在启动瞬间 fatal 退出,所以本篇推荐的工作流是:先offen debug预检(不启动服务)→ 确认无误再重启(见 3.3)。
三、参数查看与修改方式
3.1 两种配置来源与命名规则
Offen 的运行期配置只有一个命名空间:环境变量,全部遵循 OFFEN_<scope>_<key> 大写下划线命名(如 OFFEN_SERVER_PORT)。scope 共五个:SERVER(HTTP 服务)、DATABASE(存储)、SMTP(邮件)、APP(应用行为)加单值的 OFFEN_SECRET。
环境变量可以直接注入进程环境,也可以写进 env 文件(KEY="value" 逐行格式,引号可省略)。env 文件本质是"批量设置环境变量的载体",两种来源可混用,环境变量优先级更高——同一变量在进程环境与 env 文件中同时出现时,进程环境生效(实测:env OFFEN_APP_LOCALE="de" offen debug -envfile /tmp/empty.env 输出中 "Locale": "de",覆盖了空 env 文件的默认值 en)。
💡 技术拓展:给进程注入配置的常见方式不止 env 文件一种——
方式 原理 适用场景 env 文件 + -envfile进程启动时读文件赋值环境变量 单机 systemd 托管,最常用 systemd Environment=单元文件内逐行注入 个别变量微调、不改配置文件 _FILE后缀值放在独立文件里,配置只写路径 敏感值(密码/密钥)不落配置文件明文 进程环境变量 由外层执行环境注入 一次性覆盖实验、编排工具传参 Offen 全部支持(本文 3.4 实测
_FILE),本篇主线采用官方推荐的 env 文件方式。
3.2 env 文件查找顺序与优先级(实测)
不指定 -envfile 时,Offen 按 offen serve -h 原文给出的顺序查找名为 offen.env 的文件(Linux/macOS):当前工作目录 → ~/.config → $XDG_CONFIG_HOME → /etc/offen,找到即停。实测验证优先级:
# 在工作目录放一个只改端口的 offen.env,看它是否压过 /etc/offen/offen.env
mkdir -p /root/prectest && cd /root/prectest
printf "OFFEN_SERVER_PORT=\"8095\"\n" > offen.env
offen debug 2>&1 | grep -m1 '"Port"'
实测回显:
"Port": 8095,
工作目录文件生效(8095 压过了 /etc/offen/offen.env 里的 8080)。继续验证第二优先级 ~/.config 与回落行为:
# 移走工作目录文件,在 ~/.config 放同名文件
rm offen.env
mkdir -p /root/.config && printf "OFFEN_SERVER_PORT=\"8096\"\n" > /root/.config/offen.env
cd /var && offen debug 2>&1 | grep -m1 '"Port"'
实测回显:
"Port": 8096,
# 删掉 ~/.config 文件后,回落到 /etc/offen/offen.env
rm /root/.config/offen.env
cd /var && offen debug 2>&1 | grep -m1 '"Port"'
实测回显:
"Port": 8080,
-envfile 显式指定时跳过整条查找链、直接生效:
printf "OFFEN_SERVER_PORT=\"8097\"\n" > /tmp/explicit.env
offen debug -envfile /tmp/explicit.env 2>&1 | grep -m1 '"Port"'
实测回显:
"Port": 8097,
四条实测结论按优先级从高到低:-envfile 显式指定(8097)> 当前工作目录(8095)> ~/.config(8096 证明第二顺位存在)> /etc/offen(8080 兜底)。
⚠️ 注意:工作目录优先于
/etc/offen意味着"在错误目录随手放过的 offen.env 会悄悄覆盖系统配置"——排障时先确认执行服务的工作目录里没有同名文件(systemd 托管时工作目录默认为/,一般不会踩中;手工前台运行时最容易踩,见 FAQ Q5)。
3.3 用 offen debug 打印当前生效配置
offen debug 解析并打印当前生效配置的完整 JSON,是改动配置后的第一道验证——它只读配置、不碰服务进程,配置写错时直接给出 fatal 原因。用一份空 env 文件跑出纯默认值:
: > /tmp/empty.env
offen debug -envfile /tmp/empty.env 2>&1
实测回显:
time="2026-10-30T01:56:04Z" level=info msg="Current configuration values"
{
"Server": {
"Port": 3000,
"ReverseProxy": false,
"SSLCertificate": "",
"SSLKey": "",
"AutoTLS": null,
"LetsEncryptEmail": "",
"CertificateCache": "/var/www/.cache"
},
"Database": {
"Dialect": "sqlite3",
"ConnectionString": "/var/opt/offen/offen.db",
"ConnectionRetries": 0
},
"App": {
"Development": false,
"LogLevel": 4,
"SingleNode": true,
"Locale": "en",
"RootAccount": "",
"DemoAccount": "",
"DeployTarget": "",
"Retention": {}
},
"Secret": "HruwcxzjpQwFoa+UP71eUg==",
"SMTP": {
"Authtype": "LOGIN",
"User": "",
"Password": "",
"Host": "",
"Port": 587,
"Sender": "no-reply@offen.dev"
}
}
这份 JSON 就是第四章总览表所有"默认值"的出处。两个细节值得展开:
Secret字段在未配置时每次运行随机生成:间隔约 1 分钟的两次offen debug -envfile /tmp/empty.env分别输出Secret: HruwcxzjpQwFoa+UP71eUg==与Secret: cGS0YUHuNkvkSN0sAyfwZQ==——这就是官方"不设OFFEN_SECRET则每次重启随机生成、所有会话失效"警告的实证(正确做法见 5.4)。LogLevel是数值,映射关系实测如下:
for L in debug info warn error; do
echo "--- LOGLEVEL=$L"
OFFEN_APP_LOGLEVEL=$L offen debug -envfile /tmp/empty.env 2>&1 | grep "LogLevel"
done
实测回显:
--- LOGLEVEL=debug
"LogLevel": 5,
--- LOGLEVEL=info
"LogLevel": 4,
--- LOGLEVEL=warn
"LogLevel": 3,
--- LOGLEVEL=error
"LogLevel": 2,
即 debug=5、info=4(默认)、warn=3、error=2。另注意到 LOGLEVEL=warn 与 error 时连 Current configuration values 这行 info 日志都被抑制了——印证官方"高于 info 会同时关掉 info 级的访问日志"的说明。
对本篇基线配置(/etc/offen/offen.env,内容为安装篇写入的五项)跑 debug,确认实例实际生效值:
cat /etc/offen/offen.env
offen debug 2>&1 | grep -E '"Port"|"Dialect"|"ConnectionString"|"Locale"'
实测回显:
OFFEN_SECRET="DwNCkfF3vmJSOfTxCBiWTQ=="
OFFEN_SERVER_PORT="8080"
OFFEN_DATABASE_DIALECT="sqlite3"
OFFEN_DATABASE_CONNECTIONSTRING="/var/opt/offen/offen.db"
OFFEN_APP_LOCALE="en"
"Port": 8080,
"Dialect": "sqlite3",
"ConnectionString": "/var/opt/offen/offen.db",
"Locale": "en",
3.4 _FILE 后缀:从文件注入配置值
官方文档 Head Up:所有配置项都可以加 _FILE 后缀,把"值"换成"存放值的文件路径",适合密码、密钥这类不想以明文出现在配置文件里的场景。实测三种情形:
# 情形 1:文件内容不带换行,正常读入
printf 8091 > /tmp/portfile
OFFEN_SERVER_PORT_FILE=/tmp/portfile offen debug -envfile /tmp/empty.env 2>&1 | grep -m1 '"Port"'
实测回显:
"Port": 8091,
# 情形 2:文件内容带换行(echo 默认行为),int 类型解析失败
echo 8090 > /tmp/portfile
OFFEN_SERVER_PORT_FILE=/tmp/portfile offen debug -envfile /tmp/empty.env 2>&1 | tail -1
实测回显:
time="2026-10-30T01:56:20Z" level=fatal msg="Error sourcing runtime configuration" error="config: error processing configuration: envconfig.Process: assigning OFFEN_SERVER_PORT to Port: converting '8090\n' to type int. details: strconv.ParseInt: parsing \"8090\\n\": invalid syntax"
# 情形 3:文件不存在,进程直接 panic
OFFEN_SERVER_PORT_FILE=/tmp/nonexistent offen debug -envfile /tmp/empty.env 2>&1 | grep -m1 panic
实测回显:
panic: failed to read /tmp/nonexistent: open /tmp/nonexistent: no such file or directory
三条实测结论:_FILE 机制真实生效;文件内容按原样读入(int 类的值必须去掉结尾换行,用 printf 而非 echo 写入);文件缺失时是 panic 而非 graceful 报错,用之前先确认路径存在。
3.5 修改参数与生效验证(不碰生产服务的演练法)
改参数的标准闭环是"改 env 文件 → offen debug 预检 → systemctl restart offen → 验证"。演练时不想动正在跑的服务,可以用临时 env 文件在前台另起一个实例(验证完即停,互不影响)。演示把端口换到 8098 并验证响应:
: > /tmp/empty.env
env OFFEN_SERVER_PORT="8098" offen serve -envfile /tmp/empty.env > /tmp/norp.log 2>&1 &
sleep 4
curl -s -D - -o /dev/null http://localhost:8098/
实测回显:
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; img-src 'self' data:
Content-Type: text/html; charset=utf-8
Etag: 4a61d12fd7ef76f0595fa36dfa07bcea
Vary: Accept-Encoding
Date: Fri, 30 Oct 2026 01:58:43 GMT
Transfer-Encoding: chunked
前台实例返回 200,端口改动生效。停掉演练实例并确认托管服务未受影响:
kill %1
systemctl is-active offen
实测回显:
active
正式路径(改 /etc/offen/offen.env 后 systemd 重启)完全同构:systemctl restart offen && systemctl is-active offen,再按 3.3 用 offen debug(或直接 curl 端口)复核。
四、参数总览
Offen v1.4.2 全部配置参数如下表(默认值取自 3.3 实测的 debug 输出与官方 "Configuring the application" 页,二者一致;带 ⚠️ 的行有实测陷阱,详见第五/七章):
| 参数 | 默认值 | 作用 |
|---|---|---|
OFFEN_SERVER_PORT | 3000 | HTTP 监听端口;Docker 发行镜像内在 Dockerfile 固定为 80,映射宿主端口而非改此值 |
OFFEN_SERVER_REVERSEPROXY | false | 置 true 表示运行在反代之后:不再添加缓存/安全响应头,关闭请求日志 |
OFFEN_SERVER_SSLCERTIFICATE | 空 | 自带证书路径(须与 OFFEN_SERVER_SSLKEY 成对设置) |
OFFEN_SERVER_SSLKEY | 空 | 自带证书私钥路径(须与 OFFEN_SERVER_SSLCERTIFICATE 成对设置) |
OFFEN_SERVER_AUTOTLS | 空 | AutoTLS 域名列表(逗号分隔):自动向 Let's Encrypt 申请续期证书,设置后忽略 PORT、改听 80/443 ⚠️ |
OFFEN_SERVER_CERTIFICATECACHE | /var/www/.cache | AutoTLS 证书缓存目录;官方文档把键名误写作 OFFEN_SERVER_CERTFICATECACHE,实测只有完整拼写生效 ⚠️ |
OFFEN_SERVER_LETSENCRYPTEMAIL | 空 | AutoTLS 关联的邮箱,用于接收证书到期/异常通知 |
OFFEN_DATABASE_DIALECT | sqlite3 | SQL 方言,可选 sqlite3 / mysql / postgres,其他值启动即 fatal ⚠️ |
OFFEN_DATABASE_CONNECTIONSTRING | /var/opt/offen/offen.db | SQLite 为文件路径;MySQL/PG 为含凭据的连接串(各有一必带参数,见 5.2) |
OFFEN_DATABASE_CONNECTIONRETRIES | 0 | 数据库连不上时的重试次数(指数退避),默认不重试 |
OFFEN_SMTP_USER | 空 | SMTP 用户名 |
OFFEN_SMTP_PASSWORD | 空 | SMTP 密码 |
OFFEN_SMTP_HOST | 空 | SMTP 主机名 |
OFFEN_SMTP_PORT | 587 | SMTP 端口 |
OFFEN_SMTP_SENDER | no-reply@offen.dev | 发件人地址 |
OFFEN_SMTP_AUTHTYPE | LOGIN | 认证类型,官方支持 LOGIN / PLAIN / CRAM-MD5 / NOAUTH |
OFFEN_SECRET | 未设置时随机生成 | Base64 密钥,签名登录 cookie 与 URL token;建议 16 字节,重启随机 ⇒ 会话全失效 ⚠️ |
OFFEN_APP_LOCALE | en | 界面语言,仅 en/de/fr/es/pt/vi,其他值(如 zh)启动即 fatal ⚠️ |
OFFEN_APP_LOGLEVEL | info | 日志级别 debug/info/warn/error(数值 5/4/3/2,实测见 3.3) |
OFFEN_APP_SINGLENODE | true | 单节点模式开关;false 时关闭内置 cron(自动迁移与过期清理),需手动跑 migrate/expire |
OFFEN_APP_ROOTACCOUNT | 空 | 指定一个 Account ID,把本实例自身的使用统计采集进该账户(自举统计) |
OFFEN_APP_RETENTION | 6months | 事件保留期,可选 6months/12weeks/6weeks/30days/7days,调短后启动即删旧数据 ⚠️ |
表中未列、但 offen debug 输出可见的字段:App.Development(实测 OFFEN_APP_DEVELOPMENT=true 可置 true,官方未文档化)、App.DeployTarget(实测 OFFEN_APP_DEPLOYTARGET="heroku" 可赋值,面向 Heroku 发行模板)、App.DemoAccount(demo 模式内部字段,实测环境变量赋值不生效)。这三者属内部/发行渠道字段,常规部署无需配置。
表格之外的选型解读:默认值组合(SQLite + 3000 端口 + 无 TLS + 单节点)只够本地体验;生产至少要处理三件事——密钥固定(5.4)、TLS 形态选型(5.1)、以及要不要外接数据库(5.2),第六章模板按场景给出现成组合。
五、核心参数分类详解
5.1 SERVER:HTTP 服务与 TLS
SERVER 命名空间决定服务"从哪个端口、以什么加密形态对外提供"。先看默认行为的实测基线:3.3 的空配置实例监听 3000(Port: 3000),HTTP 明文服务。生产形态有三种 TLS 方案可选:
💡 技术拓展:Offen 的 HTTPS 形态不止一种——
方案 配置 原理与取舍 AutoTLS(官方推荐) OFFEN_SERVER_AUTOTLS设域名进程内置 Let's Encrypt 客户端,自动申请+续期;需域名解析到本机且 80/443 公网可达 自带证书 OFFEN_SERVER_SSLCERTIFICATE+OFFEN_SERVER_SSLKEY用已有的证书文件(如通配符证书);续期自理 反代终结 TLS 前置 Nginx/Caddy 等 + OFFEN_SERVER_REVERSEPROXY=trueTLS 在反代层做;官方"actively advise against"反代——代理日志会留下 Offen 刻意不采集的访客 IP 本篇实验路径为直连 HTTP(内网验证用);正式采集建议 AutoTLS,方案对比如 《Offen 介绍与选型指南》第六章。
逐参数说明(默认值见第四章总览):
OFFEN_SERVER_PORT:默认 3000。实验基线改为 8080 后,启动日志输出 Server now listening on port 8080(见 3.5 的 serve 变体与安装篇 8.1)。设置 AUTOTLS 后此值被忽略(官方 Heads Up 原文),进程改听 80 与 443。
OFFEN_SERVER_REVERSEPROXY:默认 false。置 true 后应用假定自己 behind 反代:不再添加缓存与安全相关响应头、关闭请求日志。实测对照(同配置仅改此值,GET /):
env OFFEN_SERVER_PORT="8099" OFFEN_SERVER_REVERSEPROXY="true" offen serve -envfile /tmp/empty.env > /tmp/rp.log 2>&1 &
sleep 4
curl -s -D - -o /dev/null http://localhost:8099/
实测回显(ReverseProxy=true):
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; img-src 'self' data:
Content-Type: text/html; charset=utf-8
Etag: 4a61d12fd7ef76f0595fa36dfa07bcea
Date: Fri, 30 Oct 2026 01:58:47 GMT
Transfer-Encoding: chunked
与 3.5 中 ReverseProxy=false 的对照实例逐头比对:false 多一行 Vary: Accept-Encoding(压缩协商头),true 时消失——响应头集合确实随开关变化,请求访问日志也不再输出到 stdout。
OFFEN_SERVER_SSLCERTIFICATE / OFFEN_SERVER_SSLKEY:成对使用,指向证书与私钥文件路径;单独设置一个不构成有效 TLS 形态(官方原文 requires the other)。
OFFEN_SERVER_AUTOTLS:逗号分隔域名列表(如 offen.example.com,offen.otherdomain.org)。设置后进程自动处理证书申请与续期。AutoTLS 需要公网域名解析到本机,本篇实验环境不具备,未实跑;其端口改听 80/443、证书缓存持久化两项行为依据官方配置文档,对应 FAQ Q6 的两个踩坑点。
OFFEN_SERVER_CERTIFICATECACHE:证书缓存目录,默认 /var/www/.cache。⚠️ 实测发现一个键名陷阱——官方文档写的键是 OFFEN_SERVER_CERTFICATECACHE(少一个 I),实测该拼写的环境变量不生效,只有完整拼写 OFFEN_SERVER_CERTIFICATECACHE 生效:
env OFFEN_SERVER_CERTFICATECACHE="/tmp/cachecorrect" OFFEN_SERVER_CERTIFICATECACHE="/tmp/cachetypo" offen debug -envfile /tmp/empty.env 2>&1 | grep CertificateCache
实测回显(值为"笔误键"设置的 /tmp/cachetypo,证明生效的是完整拼写键):
"CertificateCache": "/tmp/cachetypo",
该目录必须持久化:缓存丢失后每个请求都会触发一次新的证书签发,很快被 Let's Encrypt 限流(官方 Heads Up 原文)。
OFFEN_SERVER_LETSENCRYPTEMAIL:AutoTLS 伴生参数,把邮箱关联到签发的证书,用于接收 Let's Encrypt 的到期与异常通知。
5.2 DATABASE:存储后端
DATABASE 命名空间决定事件数据落到哪里。三个参数:方言、连接串、重试次数。
OFFEN_DATABASE_DIALECT:可选 sqlite3(默认)/ mysql / postgres。拼写错误启动即 fatal,实测:
env OFFEN_DATABASE_DIALECT="sqlite" offen debug -envfile /tmp/empty.env 2>&1 | tail -1
实测回显:
time="2026-10-30T01:56:52Z" level=fatal msg="Error sourcing runtime configuration" error="config: error processing configuration: envconfig.Process: assigning OFFEN_DATABASE_DIALECT to Dialect: converting 'sqlite' to type config.Dialect. details: unknown or unsupported SQL dialect sqlite"
💡 技术拓展:三种 SQL 方言的取舍——
方言 运维重量 适用场景 sqlite3(默认) 零依赖,单文件即库 单节点、中小流量(个人站/企业站),备份=拷文件 mysql 需独立数据库实例 + 连接管理 已有 MySQL 运维体系、或计划多实例共用一库 postgres 需独立数据库实例 同上,PG 技术栈团队 官方与发行形态均以单节点 SQLite 为第一形态;只有走向多实例横向扩展才必须外接数据库。本篇实验基线即默认 SQLite。
OFFEN_DATABASE_CONNECTIONSTRING:三种方言格式不同。SQLite 直接给文件路径(默认 /var/opt/offen/offen.db);MySQL 必须追加 ?parseTime=true,PostgreSQL 本地/内网连接通常要追加 ?sslmode=disable(官方配置文档原文示例):
# MySQL
OFFEN_DATABASE_CONNECTIONSTRING=user:pass@tcp(localhost:3306)/offen?parseTime=true
# PostgreSQL
OFFEN_DATABASE_CONNECTIONSTRING=postgres://user:pass@localhost:5432/offen?sslmode=disable
OFFEN_DATABASE_CONNECTIONRETRIES:默认 0(连不上立即失败)。适用"应用与数据库同机同批启动"的竞态场景(官方举例 docker-compose + MySQL)。实测重试行为——让实例去连一个不存在的本机 MySQL,重试 2 次:
env OFFEN_DATABASE_DIALECT="mysql" \
OFFEN_DATABASE_CONNECTIONSTRING="root:q1w2e3r4@tcp(127.0.0.1:3306)/offen?parseTime=true" \
OFFEN_DATABASE_CONNECTIONRETRIES="2" \
offen serve -envfile /tmp/empty.env 2>&1 | head -8
实测回显(等待约 25 秒失败后):
time="2026-10-30T02:02:54Z" level=warning msg="Connecting to database failed" error="dial tcp 127.0.0.1:3306: connect: connection refused"
time="2026-10-30T02:02:54Z" level=info msg="Scheduling sleep before retrying" duration=635.624102ms
time="2026-10-30T02:02:54Z" level=warning msg="Connecting to database failed" error="dial tcp 127.0.0.1:3306: connect: connection refused"
time="2026-10-30T02:02:54Z" level=info msg="Scheduling sleep before retrying" duration=501.327011ms
time="2026-10-30T02:02:55Z" level=fatal msg="Unable to establish database connection" error="error opening database: dial tcp 127.0.0.1:3306: connect: connection refused"
(首行 SMTP warning 略)重试采用指数退避(官方说明:次数设太大时间间隔会变得很大),耗尽后 fatal 退出。
另外两个连库注意事项:其一,OFFEN_DATABASE_CONNECTIONSTRING 若完全不配置任何 DATABASE 项,进程也能启动,但数据写入本地临时库、重启即失——这正是 offen demo 的运行形态,生产务必显式落库;其二,多实例横向扩展(OFFEN_APP_SINGLENODE=false)必须外接 MySQL/PostgreSQL,且迁移与过期清理要改手动执行(命令见 《Offen 1.4.2 常用 CLI 命令与工具详解》migrate/expire 小节)。
5.3 SMTP:事务邮件
SMTP 六参数控制邀请用户、重置密码等事务邮件的发送。一个都不配时进程照常启动,但每条链路都会退化为本机 sendmail,可靠性差(官方原文 "will likely be unreliable"),且 deb/tar.gz 之外的发行形态(如 Docker 镜像)没有 sendmail 可退。实测未配置时的启动警告(3.5 的 serve 回显前两行):
time="2026-10-30T01:57:41Z" level=warning msg="SMTP for transactional email is not configured right now, mail delivery will be unreliable"
time="2026-10-30T01:57:41Z" level=warning msg="Refer to the documentation to find out how to configure SMTP"
配置四要素(HOST/PORT/USER/PASSWORD)加发件人与认证类型即可消除 warning。逐参数说明:
OFFEN_SMTP_HOST:SMTP 服务主机名,如smtp.example.com。OFFEN_SMTP_PORT:默认 587(submission 端口);实测改为 465 后 debug 输出"Port": 465(见 3.1 优先级实验)。OFFEN_SMTP_USER/OFFEN_SMTP_PASSWORD:认证凭据;密码属敏感值,可用_FILE注入(3.4)。OFFEN_SMTP_SENDER:默认no-reply@offen.dev,应改为你的域名(实测OFFEN_SMTP_SENDER="analytics@example.com"生效)。OFFEN_SMTP_AUTHTYPE:默认LOGIN,官方支持LOGIN/PLAIN/CRAM-MD5/NOAUTH。实测配置层不校验取值(DIGEST也能通过 debug 原样打印"Authtype": "DIGEST"),错误值要到实际发信时才暴露——按服务商文档填。
验证配置生效:重启后确认启动日志中两条 SMTP warning 消失(对照安装篇 8.1 的日志回显)。
5.4 SECRET:签名密钥
OFFEN_SECRET 是唯一的单值参数:一个 Base64 编码的随机密钥,用于签名登录 cookie 与校验 URL token(邀请链接、密码重置链接),官方建议 16 字节。用 offen secret -quiet 生成(命令详解见 《Offen 1.4.2 常用 CLI 命令与工具详解》secret 小节):
offen secret -quiet
实测回显:
dgKP6sKujuPz6vLD+uN2bA==
不设置的后果在 3.3 已实证:每次进程运行随机生成(两次 debug 输出两个不同 Secret),重启后所有已登录会话失效、所有未使用的邀请/重置链接作废。生产配置必须固定此值;泄露此值等于泄露伪造会话的能力,泄露后应换新并让全员重新登录。
5.5 APP:应用行为
APP 命名空间控制界面语言、日志、单节点模式、自举统计与数据保留。
OFFEN_APP_LOCALE:仅支持 en/de/fr/es/pt/vi 六种,其他值启动即 fatal(实测 zh 的完整报错见 FAQ Q1)。中文界面暂不支持,中文团队建议 en。
OFFEN_APP_LOGLEVEL:四级映射实测 debug=5/info=4/warn=3/error=2(3.3);warn 及以上会同时关闭 info 级访问日志,需要排查采集请求时临时调回 info 或 debug。
OFFEN_APP_SINGLENODE:默认 true 的单节点模式——进程内置 cron 自动跑数据库迁移与过期事件清理(安装篇 8.1 的启动日志 Cron successfully pruned expired events removed=0 即此机制)。置 false 后这两件例行事务全部停止,多实例部署时由运维侧手动执行 offen migrate / offen expire(详见 《Offen 1.4.2 常用 CLI 命令与工具详解》)。实测配置校验通过:OFFEN_APP_SINGLENODE="false" 时 debug 正常输出 "SingleNode": false。
OFFEN_APP_ROOTACCOUNT:可选的自举统计——把某个 Account ID 填进来,Offen 会用自己采集自己实例的使用情况(官方用途说明)。实测赋值生效:
env OFFEN_APP_ROOTACCOUNT="5e00fe8d-cf71-4226-9c7e-37ccc5195145" offen debug -envfile /tmp/empty.env 2>&1 | grep RootAccount
实测回显:
"RootAccount": "5e00fe8d-cf71-4226-9c7e-37ccc5195145",
Account ID 来自 offen setup 的创建回显(安装篇 8.2)或面板账户页。
OFFEN_APP_RETENTION:事件保留期,默认 6months(官方 expire 命令帮助原文:6 months = 4464 小时 = 186 天),可缩短为 12weeks/6weeks/30days/7days,不支持更长的值。两个实测细节:合法值通过校验(OFFEN_APP_RETENTION="30days" 正常启动),但 debug 输出中 Retention 恒为 {}——保留期不回显,只能以启动日志中 cron 清理行为佐证;非法值直接 fatal(FAQ Q3)。⚠️ 最重要的一条(官方 Heads Up 原文):把保留期调得比现状短时,进程启动时立即删除超出新期限的全部事件,不可恢复。
六、生产配置模板
三套模板覆盖三种典型场景,均为完整可用的 /etc/offen/offen.env 内容。共同前提:域名解析、证书、数据库等外部条件按各模板说明准备;改完按 3.3/3.5 的闭环验证。
6.1 模板一:小规格单节点(1 核 1GB 内存、20GB 磁盘,个人博客/中小企业站)
适用:单台小规格 VPS,SQLite 落盘,AutoTLS 托管证书,流量十万 PV/月量级。要求域名解析到本机、80/443 对公网开放:
OFFEN_SECRET="DwNCkfF3vmJSOfTxCBiWTQ=="
OFFEN_SERVER_AUTOTLS="offen.example.com"
OFFEN_SERVER_LETSENCRYPTEMAIL="ops@example.com"
OFFEN_SMTP_HOST="smtp.example.com"
OFFEN_SMTP_PORT="587"
OFFEN_SMTP_USER="offen"
OFFEN_SMTP_PASSWORD="q1w2e3r4"
OFFEN_SMTP_SENDER="offen-noreply@example.com"
OFFEN_DATABASE_DIALECT="sqlite3"
OFFEN_DATABASE_CONNECTIONSTRING="/var/opt/offen/offen.db"
OFFEN_APP_LOCALE="en"
OFFEN_APP_RETENTION="6months"
逐项说明:OFFEN_SECRET 用 offen secret -quiet 生成并固定(5.4);AUTOTLS 接管监听(80/443,无需再设 PORT)并自动续期,LETSENCRYPTEMAIL 接收证书通知;证书缓存默认 /var/www/.cache,确认该目录随系统持久化(6.1 场景为常规磁盘,无需额外处理);SMTP 四项用于邀请协作与密码找回;保留期 6 个月是默认值,写出来说明"我评估过并接受"。生效验证:
env OFFEN_SERVER_AUTOTLS="offen.example.com" OFFEN_SERVER_LETSENCRYPTEMAIL="ops@example.com" offen debug -envfile /tmp/empty.env 2>&1 | grep -A4 '"AutoTLS"'
systemctl restart offen && systemctl is-active offen
实测回显(第一段为 AutoTLS 配置解析预检,实测通过;证书签发本身需公网 80/443 可达,实验环境未实跑,行为依据官方配置文档;第二段重启结果为实测):
"AutoTLS": [
"offen.example.com"
],
"LetsEncryptEmail": "ops@example.com",
"CertificateCache": "/var/www/.cache"
active
6.2 模板二:横向扩展前置(4GB 内存、独立 MySQL 实例,多站点/多实例)
适用:计划跑多个 offen 实例共用一个数据库,或已有 MySQL 运维体系。前提:MySQL 已就绪、库 offen 与账号已建、parseTime=true 必带:
OFFEN_SECRET="DwNCkfF3vmJSOfTxCBiWTQ=="
OFFEN_SERVER_PORT="8080"
OFFEN_DATABASE_DIALECT="mysql"
OFFEN_DATABASE_CONNECTIONSTRING="offen:q1w2e3r4@tcp(127.0.0.1:3306)/offen?parseTime=true"
OFFEN_DATABASE_CONNECTIONRETRIES="5"
OFFEN_SMTP_HOST="smtp.example.com"
OFFEN_SMTP_SENDER="offen-noreply@example.com"
OFFEN_SMTP_USER="offen"
OFFEN_SMTP_PASSWORD="q1w2e3r4"
OFFEN_APP_LOCALE="en"
OFFEN_APP_SINGLENODE="false"
逐项说明:方言切 mysql,连接串含 ?parseTime=true(官方要求);CONNECTIONRETRIES="5" 容忍应用先于数据库就绪的启动竞态(5.2 实测了重试日志形态);SINGLENODE="false" 关闭内置 cron——此后每次升级二进制后手动跑 offen migrate,过期清理交给定时任务跑 offen expire,两个命令的回显与参数见 《Offen 1.4.2 常用 CLI 命令与工具详解》。此形态下 TLS 建议由前置负载均衡/反代层终结并配 OFFEN_SERVER_REVERSEPROXY="true"(模板三),或各实例自带证书。
6.3 模板三:反代终结 TLS(ReHTTP 内网形态,REVERSEPROXY=true)
适用:实例位于 Nginx/Caddy 等反代之后、由反代终结 HTTPS 的部署。⚠️ 先读 5.1 的方案对比:官方明确不建议反代形态(代理访问日志与 Offen 的隐私主张冲突),仅在内网合规可控时选用:
OFFEN_SECRET="DwNCkfF3vmJSOfTxCBiWTQ=="
OFFEN_SERVER_PORT="8080"
OFFEN_SERVER_REVERSEPROXY="true"
OFFEN_DATABASE_DIALECT="sqlite3"
OFFEN_DATABASE_CONNECTIONSTRING="/var/opt/offen/offen.db"
OFFEN_APP_LOCALE="en"
生效验证(对照 5.1 实测:ReverseProxy=true 时 GET / 响应头中不再有 Vary 行;反代侧需自行透传 X-Forwarded-* 与压缩):
offen debug 2>&1 | grep '"ReverseProxy"'
systemctl restart offen && systemctl is-active offen
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8080/login/
实测回显形态:
"ReverseProxy": true,
active
200
七、常见问题(FAQ)
Q1:启动报 unknown or unsupported locale zh?
OFFEN_APP_LOCALE 只接受 en/de/fr/es/pt/vi。实测填 zh 的完整 fatal:
time="2026-10-30T01:56:20Z" level=fatal msg="Error sourcing runtime configuration" error="config: error processing configuration: envconfig.Process: assigning OFFEN_APP_LOCALE to Locale: converting 'zh' to type config.Locale. details: unknown or unsupported locale zh"
改成六种支持值之一即可;中文界面需求只能等上游支持(官方文档欢迎贡献翻译)。
Q2:OFFEN_DATABASE_DIALECT 写成 sqlite(少个 3)启动即退?
方言是强校验枚举,实测报错见 5.2;正确值为 sqlite3、mysql、postgres。同理 OFFEN_APP_RETENTION 只接受 5 个合法值,实测 1year fatal:
time="2026-10-30T01:56:52Z" level=fatal msg="Error sourcing runtime configuration" error="config: error processing configuration: envconfig.Process: assigning OFFEN_APP_RETENTION to Retention: converting '1year' to type config.Retention. details: unknown or unsupported retention period 1year"
Q3:把 OFFEN_APP_RETENTION 从 6months 调到 30days,旧数据会怎样?
启动时立即删除超出 30 天的全部事件,无任何恢复手段(官方 Heads Up 原文 + 官方 expire 帮助原文 4464 小时口径)。调短之前先备份 SQLite 文件或数据库快照;debug 输出中 Retention 恒显示 {},无法用 debug 复核当前保留期,建议把当前值注释在 env 文件里。
Q4:_FILE 注入的值"没生效"或进程 panic?
三种实测翻车点(3.4):文件内容带换行导致 int 解析失败(用 printf 不用 echo);文件路径不存在直接 panic(panic: failed to read ...: no such file or directory);以及值写进了配置变量而不是 _FILE 变量。逐项对照检查。
Q5:明明改了 /etc/offen/offen.env,服务却"不生效"?
按 3.2 的优先级排查:工作目录 offen.env > ~/.config > /etc/offen。实测在工作目录放一个只写 8095 的文件即可压过 /etc/offen 的 8080。systemd 托管的工作目录是 /,一般无此问题;手工前台运行时先 ls offen.env 排查。改完务必 offen debug 预检确认生效值再重启。
Q6:配置了 AutoTLS 后访问 3000/8080 不通?
设置 OFFEN_SERVER_AUTOTLS 后 OFFEN_SERVER_PORT 被忽略,进程只听 80 与 443(官方 Heads Up 原文)。另两个前提:80/443 需对公网开放(含 CAP_NET_BIND_SERVICE,见安装篇 FAQ Q5),证书缓存目录 /var/www/.cache 必须持久化,否则会被 Let's Encrypt 限流。
Q7:启动日志一直有 SMTP warning?
见 5.3:未配置任何 SMTP 参数时该 warning 必然出现(实测回显在 5.3),配齐 HOST/USER/PASSWORD 等四项后重启即消失。如果配了仍有 warning,先用 offen debug 确认 SMTP 段的值确实载入(注意 _FILE 路径与换行问题,FAQ Q4)。
Q8:按官方文档配 OFFEN_SERVER_CERTFICATECACHE 不生效?
官方文档该键名少了一个 I(CERTFICATE)。实测完整拼写 OFFEN_SERVER_CERTIFICATECACHE 才生效(对照实验见 5.1)。在官方修正文档前,一律用完整拼写键。
Q9:OFFEN_APP_DEMOACCOUNT 设了值但 debug 里仍是空?
实测确认该字段不随环境变量赋值(赋 UUID 后 debug 仍输出 "",见第四章"表中未列字段")。它是 demo 模式内部字段,常规部署无需也不应配置;同理 OFFEN_APP_DEPLOYTARGET 面向 Heroku 发行模板,常规部署不要设置。
八、参考链接
- 官方配置参考(本篇主源):
https://docs.offen.dev/running-offen/configuring-the-application/ - 官方命令参考:
https://docs.offen.dev/running-offen/using-the-command/ - 安装要求(SSL/存储/反代/邮件):
https://docs.offen.dev/running-offen/installation-requirements/ - 代码仓库:
https://github.com/offen/offen - 本站相关篇章:《Offen 介绍与选型指南》、《Ubuntu 22.04 安装 Offen 1.4.2 指南》、《Offen 1.4.2 常用 CLI 命令与工具详解》、同类工具的配置体系可对比阅读 《Plausible 配置参数详解》、《Umami 3.4 配置参数详解》