Claper 3.2.0 配置参数详解
Claper 3.2.0 三层配置体系(.env 环境变量、CONFIG_DIR 文件注入、global_configs 数据库)详解:40+ 环境变量默认值与启动校验规则、布尔与整数解析陷阱、PRESENTATION_STORAGE_DIR 编译期烘焙实测、生产配置模板与九则排障实录。
Claper 3.2.0 配置参数详解
目标:吃透 Claper 3.2.0 的三层配置体系——
.env环境变量、CONFIG_DIR文件注入、global_configs数据库运行时配置,给出全部 40+ 参数的默认值、校验规则与生效方式。本文默认值逐项核对自官方仓库 v3.2.0runtime.exs/.env.sample(查询日期 2026-11-01),全部改参与报错结论在 Ubuntu 22.04 的claper-3.2.0-stack实测环境验证(部署过程见 《Claper Docker Compose 部署指南》)。
一、环境信息
| 项目 | 内容 |
|---|---|
| 操作系统 | Ubuntu 22.04(x86_64),Docker Engine 29.x + Compose v2 |
| Claper 版本 | 3.2.0(镜像 ghcr.io/claperco/claper:3.2.0,Elixir 1.18.4 / OTP 28 / Phoenix 1.7.24) |
| 数据库 | postgres:15,库/用户/口令均为 claper |
| 部署形态 | Compose 栈 claper-3.2.0-stack,项目目录 /opt/claper,应用经 env_file: .env 注入配置 |
| 对外地址 | BASE_URL=http://localhost:8129(宿主 8129 → 容器内 Cowboy 4000) |
| 配置文件 | /opt/claper/.env(权限 600);数据库层 global_configs 表 |
| 参数来源核对 | 官方仓库 v3.2.0 runtime.exs、lib/claper/helpers/config.ex、.env.sample、config/config.exs、Dockerfile(jsDelivr CDN 获取,查询日期 2026-11-01) |
💡 说明:本文所有「实测」均以宿主机
/opt/claper下的 compose 栈为载体;实验性改参结束后已全部恢复基线.env(见 7.9 恢复实录),读者按章节顺序复现即可。
二、前置条件
- 栈已按部署篇拉起且两个服务均 healthy:
cd /opt/claper && docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
claper-3.2.0-app ghcr.io/claperco/claper:3.2.0 "sh -c '/app/bin/cla…" app 23 seconds ago Up 12 seconds (healthy) 0.0.0.0:8129->4000/tcp, [::]:8129->4000/tcp
- HTTP 可达(后续改参验证均以此为基线):
curl -sI http://localhost:8129 | head -1
HTTP/1.1 200 OK
- 具备查看运行时配置的通道(release 管理脚本,原理见部署篇「二、镜像选型说明」):
docker compose exec app /app/bin/claper rpc 'IO.puts("rpc-ok")'
rpc-ok
三、参数查看与修改方式
3.1 三层配置体系总览
Claper 的配置按「读取时机与存放位置」分三层,优先级与生效方式各不相同:
| 层 | 载体 | 读取时机 | 修改后生效方式 | 典型内容 |
|---|---|---|---|---|
| 第一层:环境变量 | /opt/claper/.env(compose env_file: 注入) | 每次进程启动时由 runtime.exs 求值 | 重建 app 服务(docker compose up -d app,改 .env 后 compose 会重建容器;restart 不会重读) | BASE_URL、SECRET_KEY_BASE、POOL_SIZE 等全部 40+ 参数 |
| 第二层:CONFIG_DIR 文件注入 | 默认 /run/secrets/<变量名>,每文件一个值 | 与第一层同一次启动求值,文件优先于环境变量 | 写入/删除文件后 docker compose restart app 即可(restart 重新求值 runtime.exs) | 与第一层同名全部参数(适合 swarm secrets / K8s Secret 挂载) |
| 第三层:数据库运行时配置 | PostgreSQL global_configs 表 + 事件相关表 | 每次业务调用实时读库 | 管理面板 /admin/settings 保存即生效,无需重启 | transcription_enabled、lti_enabled、转写 API key;每事件的聊天/投票开关等 |
三层的关系一句话概括:第一/二层决定「进程怎么起」,第三层决定「功能怎么跑」。第一、二层参数非法会直接拒绝启动(见 7.1–7.5 实录);第三层参数改错只会功能异常,不影响进程存活。
3.2 CONFIG_DIR 文件注入机制
第一、二层的读取都由同一个辅助模块完成(来源:官方仓库 v3.2.0 lib/claper/helpers/config.ex,本地留存核对):
defmodule Claper.ConfigHelpers do
def get_var_from_path_or_env(config_dir, var_name, default \\ nil) do
var_path = Path.join(config_dir, var_name)
if File.exists?(var_path) do
File.read!(var_path) |> String.trim()
else
System.get_env(var_name, default)
end
end
def get_int_from_path_or_env(config_dir, var_name, default \\ nil) do
var = get_var_from_path_or_env(config_dir, var_name)
case var do
nil ->
default
var ->
case Integer.parse(var) do
{int, ""} -> int
_ -> raise "Config variable #{var_name} must be an integer. Got #{var}"
end
end
end
end
要点解读:
runtime.exs第一行config_dir = System.get_env("CONFIG_DIR", "/run/secrets")——目录本身只能由环境变量指定,默认/run/secrets;- 取值顺序:
$CONFIG_DIR/<变量名>文件(内容String.trim后整串作为值)→ 环境变量 → 代码内默认值; - 整数类参数(
MAX_FILE_SIZE_MB、POOL_SIZE、QUEUE_TARGET、PORT等)走get_int_from_path_or_env,非整数直接 raise(实测见 7.5); - 布尔类参数走
String.to_existing_atom(),只接受true/false字面量,其他值产生的原子不是布尔,启动能过但运行到相关页面才炸(实测 500 见 7.6)。
文件注入优先于环境变量,实测(在 app 服务内落三个文件后 restart——restart 会重新求值 runtime.exs 从而读到文件,但沿用原环境变量集合):
docker compose exec app mkdir -p /run/secrets
docker compose exec app sh -c 'echo 42 > /run/secrets/MAX_FILE_SIZE_MB; echo 800x800 > /run/secrets/GS_JPG_RESOLUTION; echo local > /run/secrets/PRESENTATION_STORAGE'
docker compose restart app
docker compose exec app /app/bin/claper rpc 'IO.inspect(Application.get_env(:claper, :presentations))'
[
max_file_size: 42,
storage: "local",
s3_bucket: nil,
resolution: "800x800",
s3_public_url: "https://.s3..amazonaws.com"
]
.env 中 MAX_FILE_SIZE_MB 未设(默认应为 15)、GS_JPG_RESOLUTION 未设(默认应为 300x300),而运行时取到了文件里的 42 与 800x800——文件注入生效且优先级更高。自定义目录同样实测:把存放值文件的目录 bind 进 app 服务并在 .env 设 CONFIG_DIR 指向它,重建后 rpc 查询 {max_file_size, pool_size} 返回 {42, 10}(42 来自文件,10 为 POOL_SIZE 默认值),证明 CONFIG_DIR 本身可从环境变量指定。实验完成后删除文件与 CONFIG_DIR 行并恢复基线。
⚠️ 注意:exec 临时写入的
/run/secrets位于容器可写层,restart后仍在,但up -d --force-recreate重建容器即丢失。生产用文件注入务必以挂载(bind volume / swarm secrets / K8s Secret 卷默认就挂在/run/secrets)方式落文件,不要手工 exec 写入。
3.3 查看运行时生效值
最权威的方式是让正在跑的进程自己报配置。全量 dump(节选,实测首尾):
docker compose exec app /app/bin/claper rpc 'Application.get_all_env(:claper) |> IO.inspect(limit: :infinity)'
[
{Oban,
[
engine: Oban.Engines.Basic,
queues: [default: 10, mailers: 20],
repo: Claper.Repo,
plugins: [
{Oban.Plugins.Pruner, [max_age: 604800]},
{Oban.Plugins.Lifeline, [rescue_after: 1800000]}
]
]},
{:terms_url, nil},
{ClaperWeb.MailboxGuard, [username: nil, password: nil, enabled: false]},
{:enable_account_creation, true},
{Claper.Mailer, [adapter: Swoosh.Adapters.Local]},
{:languages, ["en", "fr", "es", "it", "de"]},
{:allow_unlink_external_provider, true},
{:remote_ip_proxies, []},
{:email_confirmation, false},
{:storage_dir, "/app/uploads"},
{:remote_ip_headers,
["forwarded", "x-forwarded-for", "x-client-ip", "x-real-ip"]},
{:logout_redirect_url, nil},
{Claper.Repo,
[
url: "postgres://claper:claper@db:5432/claper",
ssl: false,
ssl_opts: [verify: :verify_none],
prepare: :unnamed,
pool_size: 10,
queue_target: 5000
]},
...
{ClaperWeb.Endpoint,
[
...
url: [scheme: "http", host: "localhost", path: nil, port: 8129],
http: [
ip: {0, 0, 0, 0},
port: "4000",
transport_options: [max_connections: :infinity],
protocol_options: [
max_request_line_length: 8192,
max_header_value_length: 8192
]
],
secret_key_base: "xvAqu9LWzKb+S/MChF0Zgd1M6Ixz03yRWvf+aI6VBSM2IlnOy+ng3/oX//MNaljc",
same_site_cookie: "Lax",
secure_cookie: false
]},
{:presentations,
[
max_file_size: 15,
storage: "local",
s3_bucket: nil,
resolution: "300x300",
s3_public_url: "https://.s3..amazonaws.com"
]},
{ClaperWeb.Gettext,
[default_locale: "en", locales: ["fr", "en", "de", "es", "nl"]]}
]
解读三处细节:url.port: 8129 是从 BASE_URL 解析出来的对外自报地址,而 http.port: "4000" 才是实际监听端口(字符串形态直接来自 PORT 环境变量默认值);s3_public_url 在 local 模式下是 nil 插值出的占位串 https://.s3..amazonaws.com,无实际用途;locales 是编译期打进镜像的可用语言全集,languages 是运行时启用子集(见 5.5)。
日常排查建议只查目标键,避免整包 dump 泄露 secret_key_base:
docker compose exec app /app/bin/claper rpc 'IO.inspect({Application.get_env(:claper, :enable_account_creation), Application.get_env(:claper, :languages)}, label: "check")'
check: {true, ["en", "fr", "es", "it", "de"]}
3.4 修改与生效方式速查
| 修改对象 | 操作 | 生效命令 | 是否中断会话 |
|---|---|---|---|
.env 任一键 | 编辑 /opt/claper/.env | docker compose up -d app(compose 检测 env_file 变化自动重建) | 是,LiveView 连接断开重连 |
CONFIG_DIR 文件 | 增删挂载目录内文件 | 同上 | 是 |
global_configs 表 | /admin/settings 页面保存,或 SQL UPDATE | 即存即生效 | 否 |
| 每事件开关 | 演示/管理视图内切换 | 实时广播 | 否 |
⚠️ 注意:
docker compose restart app只重启进程但沿用旧环境变量集合,改了.env必须走up -d(重建)才生效——这是实测确认的行为差异(改MAX_FILE_SIZE_MB=42后 restart 仍读到 15,up -d 后读到 42)。
四、参数总览
以下清单逐项核对自官方仓库 v3.2.0 runtime.exs(查询日期 2026-11-01),「默认值」列为代码内建默认(未设环境变量且无 CONFIG_DIR 文件时生效)。带 ※ 的是 runtime.exs 支持但 .env.sample 未列出的隐藏参数。
| 变量 | 默认值 | 类型 | 作用 |
|---|---|---|---|
BASE_URL | http://localhost:4000 | URL | 对外基地址,解析出 scheme/host/path/port |
CONFIG_DIR | /run/secrets | 路径 | 文件注入目录(仅能用环境变量指定) |
LISTEN_IP ※ | 0.0.0.0 | IP | Cowboy 绑定地址,支持 IPv4/IPv6 |
PORT ※ | 4000 | 整数 | 容器内监听端口 |
SECRET_KEY_BASE | 无(prod 必填) | ≥32 字节串 | Phoenix 会话签名密钥 |
DATABASE_URL | postgres://claper:claper@localhost:5432/postgres | 连接串 | PostgreSQL |
DB_SSL ※ | false | 布尔字面量 | 数据库 TLS |
POOL_SIZE ※ | 10 | 整数 | Ecto 连接池 |
QUEUE_TARGET ※ | 5000 | 整数 | 取连接排队目标(毫秒) |
PRESENTATION_STORAGE | local | local/s3 | 讲稿存储后端 |
MAX_FILE_SIZE_MB | 15 | 整数 | 上传讲稿大小上限(MB) |
GS_JPG_RESOLUTION | 300x300 | WxH | 幻灯片缩略图分辨率 |
PRESENTATION_STORAGE_DIR | /app/uploads | 路径 | 编译期烘焙,运行时改 env 无效(见 5.4) |
S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY / S3_REGION / S3_BUCKET | nil | 串 | s3 模式四件套,缺一拒绝启动 |
S3_SCHEME / S3_HOST / S3_PORT | nil | 串 | 自建 S3 兼容端点(scheme 与 host 成对) |
S3_PUBLIC_URL | 按公式推导 | URL | 对象公开访问前缀 |
ENABLE_ACCOUNT_CREATION | true | 布尔字面量 | 自助注册开关 |
EMAIL_CONFIRMATION | false | 布尔字面量 | 注册邮箱验证 |
ALLOW_UNLINK_EXTERNAL_PROVIDER | true | 布尔字面量 | 允许解绑外部登录 |
LOGOUT_REDIRECT_URL | nil | URL | 登出后跳转 |
TERMS_URL / PRIVACY_URL | nil | URL | 注册页法务链接,两者都设才显示 |
LANGUAGES | en,fr,es,it,de | 逗号列表 | 启用界面语言 |
SAME_SITE_COOKIE | Lax | 串 | Cookie SameSite 策略 |
SECURE_COOKIE | false | 布尔字面量 | Cookie Secure 标志 |
REMOTE_IP_PROXIES | 空 | CIDR 列表 | 可信反代 IP(默认全不信任) |
REMOTE_IP_HEADERS | forwarded,x-forwarded-for,x-client-ip,x-real-ip | 逗号列表 | 取真实客户端 IP 的头 |
OIDC_ISSUER | https://accounts.google.com | URL | OIDC 签发方 |
OIDC_CLIENT_ID / OIDC_CLIENT_SECRET | nil | 串 | 两者非空才启用 OIDC |
OIDC_SCOPES | openid email profile | 空格列表 | 申请范围 |
OIDC_PROVIDER_NAME | OpenID Connect | 串 | 登录按钮显示名 |
OIDC_LOGO_URL | /images/icons/openid.png | 路径 | 按钮图标 |
OIDC_PROPERTY_MAPPINGS | nil | k:v,k:v | claims→用户属性映射 |
OIDC_AUTO_REDIRECT_LOGIN | false | 布尔字面量 | 登录页直接跳 IdP |
OIDC_TRUST_EMAIL_VERIFIED | false | =="true" 宽松比较 | 信任邮箱已验证声明 |
DISABLE_PASSWORD_LOGIN | false | 布尔字面量 | 仅 OIDC 生效时禁密码登录 |
MAIL_TRANSPORT | local | smtp/postmark/其他 | 邮件适配器 |
MAIL_FROM / MAIL_FROM_NAME | noreply@claper.co / Claper | 串 | 发件人 |
SMTP_RELAY / SMTP_PORT / SMTP_AUTH / SMTP_USERNAME / SMTP_PASSWORD | nil / 465 / always / 空 / 空 | — | SMTP 通道 |
SMTP_RETRIES / SMTP_NO_MX_LOOKUPS / SMTP_SSL / SMTP_TLS / SMTP_SSL_DEPTH / SMTP_SSL_SERVER | 1 / false / true / if_available / 2 / relay | — | SMTP 细节 |
POSTMARK_API_KEY | nil | 串 | Postmark 通道 |
ENABLE_MAILBOX_ROUTE / MAILBOX_USER / MAILBOX_PASSWORD | false / nil / nil | — | dev mailbox 预览(prod 镜像路由不存在,见 5.6) |
POOL_SIZE 之外无 Oban 环境变量 | — | — | 队列并发固定编译期(见 5.10) |
对照解读:.env.sample 只暴露了约一半参数,LISTEN_IP、PORT、DB_SSL、POOL_SIZE、QUEUE_TARGET、CONFIG_DIR 这 6 个「隐藏参数」在 runtime.exs 中同样支持——生产调优(连接池、监听地址)不必改镜像,直接用它们即可。
五、核心参数分类详解
5.1 网络与监听:BASE_URL、LISTEN_IP、PORT
BASE_URL 是 Claper 的「自我认知」地址:runtime.exs 用 URI.parse 拆出 scheme/host/path/port 写入 ClaperWeb.Endpoint.url,LiveView WebSocket 回连、邮件内链接、OIDC 回调都基于它。它不改变监听行为(监听由 LISTEN_IP/PORT 决定),只改变对外宣称的地址——反代场景两者必然不一致(容器监听 4000、对外 443),此时 BASE_URL=https://claper.example.com 而 PORT 保持 4000。
scheme 非法直接拒绝启动,实测(.env 改 BASE_URL=ftp://localhost 后重建):
docker compose up -d --force-recreate app 2>&1 | tail -3
docker compose logs app | grep -m1 "Runtime terminating"
Runtime terminating during boot ({#{message=><<"BASE_URL must start with `http` or `https`. Currently configured as `ftp://localhost`">>,'__struct__'=>'Elixir.RuntimeError','__exception__'=>true},[{elixir_eval,'__FILE__',1,[{file,"/app/releases/3.2.0/runtime.exs"},{line,62}]},...
Crash dump is being written to: erl_crash.dump...done
LISTEN_IP 支持 IPv4/IPv6,经 :inet.parse_address 校验,非法值同样拒绝启动(Invalid LISTEN_IP 'not-an-ip' error: :einval,见 7.4)。默认 0.0.0.0 适合容器内全绑定;裸机只想本机暴露可设 127.0.0.1。
PORT 是字符串直通的整数端口(dump 里可见 port: "4000" 保持字符串形态,Cowboy 能识别),改动后记得同步 compose 的 ports: 映射与 BASE_URL 的端口段。
Cowboy 层还有三个不可经环境变量调整的固定值(实测 dump):transport_options: [max_connections: :infinity](并发连接不设上限,LiveView 长连接场景必须)、max_request_line_length: 8192、max_header_value_length: 8192(反代注入超长 Cookie 时注意 8K 上限,超限 Cowboy 直接断连)。
5.2 安全与 Cookie:SECRET_KEY_BASE、SAME_SITE_COOKIE、SECURE_COOKIE
SECRET_KEY_BASE 是 Phoenix 会话/CSRF token 的签名密钥。prod 启动强制校验:未设报错、短于 32 字节报错(实测见 7.1)。生成方式:
openssl rand -base64 48
⚠️ 注意:更换
SECRET_KEY_BASE会使所有既有会话 cookie 失效(所有人重新登录);泄露则等同源码里.env.sample警告的「可伪造 admin cookie 直接登录」。每个环境独立生成、纳入备份、泄露即轮换。
SAME_SITE_COOKIE 与 SECURE_COOKIE 控制登录 cookie 的跨站策略,默认 Lax/false。实测把两者改为 None/true 并重建后,登录响应头:
set-cookie: _claper_key=SFMyNTY...; path=/; expires=Tue, 01 Dec 2026 10:57:57 GMT; max-age=2592000; secure; HttpOnly; SameSite=None
secure; SameSite=None 均已生效。取值组合规则:SameSite=None 必须搭配 Secure=true 且全站 HTTPS,否则浏览器直接丢弃 cookie;纯内网 HTTP 部署保持默认 Lax/false。
💡 技术拓展:SameSite 不止本文用到的两种——
取值 行为 适用 Lax(默认)跨站 POST 不带 cookie,顶层 GET 导航带 常规独立站点 Strict任何跨站请求都不带 高安全内网应用,代价是从邮件链接点进也要重登 None跨站全带,必须 Secure需要把 Claper 嵌入第三方页面 iframe 的场景 本实验演示
None+Secure是为验证参数贯通;生产默认保持Lax即可,观众端/e/:code页面本身不受 SameSite 影响(顶层导航)。
5.3 数据库与连接池:DATABASE_URL、DB_SSL、POOL_SIZE、QUEUE_TARGET
DATABASE_URL 为标准 PostgreSQL 连接串。DB_SSL 走 String.to_existing_atom(),只接受 true/false;开启后 ssl_opts 固定 verify: :verify_none(源码硬编码,不校验服务端证书),如需严格校验需自行改配置产物——内网直连可保持默认 false。
POOL_SIZE 是每个 app 进程的 Ecto 连接池大小(默认 10),QUEUE_TARGET 是池满后取连接的排队等待目标毫秒数(默认 5000,超时则报 timeout 错误)。估算口径:并发观众 LiveView 连接数 × 平均每连接查询密度远小于连接常驻数,真正吃池的是翻页/投票写入的瞬时并发;单实例 500 观众内默认 10 足够,PG 服务端默认 max_connections=151,多 app 副本时注意 POOL_SIZE × 副本数 < 151。
非法值拒绝启动,实测(.env 加 POOL_SIZE=abc 重建):
Runtime terminating during boot ({#{message=><<"Config variable POOL_SIZE must be an integer. Got abc">>,'__struct__'=>'Elixir.RuntimeError','__exception__'=>true},[{'Elixir.Claper.ConfigHelpers',get_int_from_path_or_env,3,[{file,"lib/claper/helpers/config.ex"},{line,22},...
报错直接点名变量与非法值,定位成本极低。
5.4 演示文稿与存储:PRESENTATION_STORAGE、MAX_FILE_SIZE_MB、GS_JPG_RESOLUTION、PRESENTATION_STORAGE_DIR、S3_*
PRESENTATION_STORAGE 仅接受 local 与 s3 两值,其他值拒绝启动,实测(PRESENTATION_STORAGE=gcs 重建):
Runtime terminating during boot ({#{message=><<"Invalid PRESENTATION_STORAGE value gcs">>,'__struct__'=>'Elixir.RuntimeError','__exception__'=>true},[{elixir_eval,'__FILE__',1,[{file,"/app/releases/3.2.0/runtime.exs"},{line,80}]},...
MAX_FILE_SIZE_MB(默认 15)是上传讲稿的硬上限,改 42 实测生效:
docker compose exec app /app/bin/claper rpc 'IO.puts("max_file_size=#{Application.get_env(:claper, :presentations)[:max_file_size]}")'
max_file_size=42
(恢复默认重建后同命令输出 max_file_size=15。)
GS_JPG_RESOLUTION(默认 300x300)控制 PDF 逐页转换的缩略图分辨率——名字里的 GS 指 Ghostscript,即服务端调它把讲稿转成逐页 JPG。投屏清晰度主要取决于原 PDF 渲染链路,此参数影响的是观众端/管理端列表里的小图;带宽紧张的大厅投影可调大(如 800x800,实测文件注入生效见 3.2),代价是转换耗时与存储翻倍。
PRESENTATION_STORAGE_DIR 是本文最重要的「反直觉」参数。.env.sample 把它列为普通环境变量(PRESENTATION_STORAGE_DIR=/app/uploads),但它的真实读取点在编译期:官方仓库 v3.2.0 config/config.exs 第 82 行
config :claper, :storage_dir, System.get_env("PRESENTATION_STORAGE_DIR", "priv/static")
config.exs 在 release 构建时求值并被烘焙进镜像,runtime.exs 全文不读这个变量。实测对照(一次性起临时进程,注入不同 env 值直接查配置):
docker run --rm -e PRESENTATION_STORAGE_DIR=/tmp/zzz -e SECRET_KEY_BASE="0123456789012345678901234567890123456789012345678901234567890123" \
--entrypoint /app/bin/claper ghcr.io/claperco/claper:3.2.0 \
eval 'IO.inspect(Application.get_env(:claper, :storage_dir), label: "storage_dir_with_env")'
storage_dir_with_env: "/app/uploads"
设了 PRESENTATION_STORAGE_DIR=/tmp/zzz,生效值仍是镜像构建期烘焙的 /app/uploads(官方 Dockerfile 第 46 行 ENV PRESENTATION_STORAGE_DIR /app/uploads)。结论:想换存储目录,唯一可行方式是把卷挂到 /app/uploads(部署篇 compose 已如此配置),改环境变量或 CONFIG_DIR 文件都无效。CONFIG_DIR 机制对它也不生效——探针显示环境变量有值而文件路径查询返回 nil(该变量根本不走 get_var_from_path_or_env):
STORAGE_DIR_env="/app/uploads" config_dir=nil
S3 模式四个必填项缺一拒绝启动(报错原文点名四个变量名);S3_SCHEME/S3_HOST/S3_PORT 用于自建 MinIO 等兼容端点(scheme 形如 https://,含结尾冒号斜杠,与 host 必须成对出现);S3_PUBLIC_URL 未设时按 https://<bucket>.s3.<region>.amazonaws.com 推导(AWS 标准路径风格),自建端点务必显式设置,否则观众端取图 404。
5.5 账户、注册与界面:ENABLE_ACCOUNT_CREATION、EMAIL_CONFIRMATION、TERMS_URL/PRIVACY_URL、LANGUAGES 等
ENABLE_ACCOUNT_CREATION=false 关闭自助注册,实测闭环(改 .env → 重建 → 注册页从 200 变 302):
curl -s -o /dev/null -w "BEFORE register: %{http_code}\n" http://localhost:8129/users/register
BEFORE register: 200
echo 'ENABLE_ACCOUNT_CREATION=false' >> .env && docker compose up -d app
curl -s -b jar.txt -o /dev/null -w "AFTER register: %{http_code}\n" http://localhost:8129/users/register
curl -s -b jar.txt -c jar.txt -o /dev/null -w "REGISTER_HTTP=%{http_code} REDIRECT=%{redirect_url}\n" \
-X POST http://localhost:8129/users/register \
--data-urlencode "user[email]=newbie@x.co" --data-urlencode "user[password]=q1w2e3r4" \
--data-urlencode "user[password_confirmation]=q1w2e3r4" --data-urlencode "_csrf_token=$CSRF"
AFTER register: 302
REGISTER_HTTP=302 REDIRECT=http://localhost:8129/
运行配置同步变为 enable_account_creation=: false。关闭后管理员仍可在 /admin/users 建号,属「白名单制」部署的标准姿势。
EMAIL_CONFIRMATION 默认 false(注意:.env.sample 示例注释是 true,与代码默认相反,以代码为准)。开启后注册需点邮件确认链接,因此必须同时配好 MAIL_TRANSPORT=smtp,否则确认邮件躺在 local adapter 内存里永远发不出去——这是组合约束,单独开它等于封死注册入口。
TERMS_URL/PRIVACY_URL 控制注册页法务链接,规则是两者都非空才显示(.env.sample 原文注明),且各自过 http/https scheme 校验。实测两步:只设 TERMS_URL 时注册页 grep 命中 0;两者齐设后 privacy 与 terms 各命中 1:
TERMS_ONLY: 0 hits
1 https://example.com/privacy
1 https://example.com/terms
LANGUAGES 逗号分隔启用语言子集,默认 en,fr,es,it,de;镜像内编译期可用的全集是 fr,en,de,es,nl(dump 中 ClaperWeb.Gettext.locales),超出全集的值不会报错但无效。实测收紧为 LANGUAGES=en,de 后运行时值变为 ["en", "de"],界面语言下拉同步只剩两项。
ALLOW_UNLINK_EXTERNAL_PROVIDER 代码默认 true(.env.sample 示例给 false,同样以代码为准),控制已绑 OIDC/LTI 外部身份的用户能否解绑——企业 SSO-only 场景设 false 防绕过。LOGOUT_REDIRECT_URL 设登出后跳转地址(默认 nil=留在本站登录页),配合对外门户统一出口时有用。
布尔类参数的解析陷阱(yes/no/1 等写法导致的 500)实测完整,见 7.6。
5.6 邮件:MAIL_TRANSPORT、SMTP_、POSTMARK_API_KEY、MAILBOX_
MAIL_TRANSPORT 三选一:smtp、postmark、其他任意值都落到 local adapter(源码 case _ -> 分支;实测 dump 显示默认 adapter: Swoosh.Adapters.Local)。local 模式邮件只进内存队列,对外不可达——注册确认、magic link、改密邮件全部静默丢弃,单机试用可以,生产必须接真实通道。
SMTP 分支参数与默认值(全部经 runtime.exs 实测核对):SMTP_RELAY(无默认,必填)、SMTP_PORT 默认 465、SMTP_AUTH 默认 always(可 if_available/never)、SMTP_USERNAME/SMTP_PASSWORD 默认空串、SMTP_RETRIES 默认 1、SMTP_NO_MX_LOOKUPS 默认 false、SMTP_SSL 默认 true、SMTP_TLS 默认 if_available、SMTP_SSL_DEPTH 默认 2、SMTP_SSL_SERVER 默认取 relay 值(SNI 用)。
值得注意:SMTP_SSL=true(默认)时 sockopts 硬编码 verify: :verify_peer + 系统 CA + TLS1.3/1.2——用自签证书的内网 SMTP 中继会握手失败,此时要么 SMTP_SSL=false 走 STARTTLS(SMTP_TLS=if_available),要么把中继证书喂进系统 CA 存储。这是「默认安全」取向的参数,排障时先想到它。
POSTMARK_API_KEY 仅 MAIL_TRANSPORT=postmark 时读取,同时源码切换 Swoosh api_client 为 Finch。
ENABLE_MAILBOX_ROUTE/MAILBOX_USER/MAILBOX_PASSWORD 是 dev 邮箱预览路由(/dev/mailbox)的 BasicAuth 开关,但路由本身在 router.ex 里包在 if Mix.env() == :dev 内——prod 镜像里该路由不存在,设了也不会有页面;dump 中 MailboxGuard 配置项仍在,属编译期残留,忽略即可。
5.7 OIDC 与 SSO:OIDC_* 全家与 DISABLE_PASSWORD_LOGIN
OIDC 启用判定是双非空:OIDC_CLIENT_ID 与 OIDC_CLIENT_SECRET 都是非空白字符串时 enabled: true,否则整条链路关闭(dump 实测 oidc=[enabled: false, ...] 时 issuer 仍显示默认值 https://accounts.google.com——未启用时其他 OIDC 参数只是占位)。
OIDC_SCOPES 按空格分隔(默认 openid email profile,与其他逗号分隔参数风格不同);OIDC_PROPERTY_MAPPINGS 按 k:v,k:v 解析成 claim 映射表(如 roles:custom_attributes.roles);OIDC_AUTO_REDIRECT_LOGIN=true 时访问登录页直接 302 去 IdP。
OIDC_TRUST_EMAIL_VERIFIED 的解析方式与其他布尔不同:源码用 String.trim |> String.downcase == "true" 宽松比较,TRUE/True 都能识别,不会踩 7.6 的原子陷阱;它跳过「OIDC 首登与同邮箱既有账号需邮件确认」的安全步骤,仅当 IdP 确实验证过每个地址(如 Google)才开。
DISABLE_PASSWORD_LOGIN 是运维最容易翻车的参数:逻辑为 请求值 and oidc_enabled——OIDC 未配置时设 true 无效,且启动日志给出显式警告(实测两条重复,boot 与 config 阶段各一次):
claper-3.2.0-app | warning: DISABLE_PASSWORD_LOGIN=true has no effect because OIDC is not configured (OIDC_CLIENT_ID / OIDC_CLIENT_SECRET are missing or blank). Password login stays enabled.
这个「无效」是救命的:若 OIDC 配好后再设 true,密码登录立即封死,包括种子管理员 admin@claper.co。.env.sample 给出的启用顺序必须严格遵守:先用种子 admin 登录 → 用目标 OIDC 账号登录一次 → 在管理面板把该 OIDC 账号提为 admin → 最后才设 DISABLE_PASSWORD_LOGIN=true。跳步的后果是全员(含自己)无法登录,只能进数据库手工提权。
5.8 反向代理与真实 IP:REMOTE_IP_PROXIES、REMOTE_IP_HEADERS
REMOTE_IP_PROXIES 逗号分隔的可信代理 IP/CIDR 列表(如 10.0.0.0/8),默认空=不信任任何转发头;REMOTE_IP_HEADERS 指定按序检查哪些头取真实客户端 IP,默认 forwarded,x-forwarded-for,x-client-ip,x-real-ip。不配 REMOTE_IP_PROXIES 时,Claper 日志与限流(登录接口 10 次/分钟,router.ex 内建 rate_limit_auth)看到的全部是反代 IP——公网反代部署必配,否则限流会误伤反代后面的所有用户。
实测基线 dump:remote_ip_proxies=[]、remote_ip_headers=["forwarded", "x-forwarded-for", "x-client-ip", "x-real-ip"],与代码默认一致。
5.9 数据库层运行时配置:global_configs 与每事件状态
第三层配置存 PostgreSQL,不经重启。管理面板 /admin/settings 读写 global_configs 表,实测查询:
docker compose exec db psql -U claper -d claper -c "SELECT key, value FROM global_configs ORDER BY key;"
key | value
--------------------------------+-------
lti_enabled | false
transcription_api_key |
transcription_default_language |
transcription_enabled | false
(4 rows)
直接 SQL 改也即时生效(适合脚本化),实测翻转实时字幕开关:
docker compose exec db psql -U claper -d claper -c "UPDATE global_configs SET value='true' WHERE key='transcription_enabled';"
docker compose exec db psql -U claper -d claper -c "SELECT key, value FROM global_configs WHERE key='transcription_enabled';"
UPDATE 1
key | value
-----------------------+-------
transcription_enabled | true
(1 row)
(实验后已按同样方式改回 false,回显 UPDATE 1 + transcription_enabled | false。)
事件级状态存于 presentation_states 表,每场演示的互动开关与默认值一查便知(实测 \d presentation_states 节选):
position | integer | | 0
chat_visible | boolean | | false
poll_visible | boolean | | false
join_screen_visible | boolean | | false
banned | character varying(255)[] | | ARRAY[]::character varying[]
chat_enabled | boolean | | true
anonymous_chat_enabled | boolean | | true
show_only_pinned | boolean | | false
message_reaction_enabled | boolean | | true
show_attendee_count | boolean | | true
即:聊天/匿名提问/表情反应/人数显示默认全开,大屏显示开关默认关,banned 数组存封禁观众标识。这些开关在演示/管理视图切换后实时广播,无需任何重启。事件主表 events 的 audience_peak(观众峰值)默认 1,code 非空唯一,外键级联删除覆盖 posts/polls/stats/presentation_files 等全部子表——删事件即删全场数据,备份口径见部署篇第五节。
5.10 BEAM 运行时与后台任务(不可经环境变量调整项)
三项与配置强相关但没有对应环境变量,在此点名避免读者徒劳试错:
- Oban 队列并发:
default: 10, mailers: 20,Pruner 保留 7 天(max_age: 604800),Lifeline 30 分钟救援(实测 dump 原文,见 3.3)——编译期固定,改需重建 release; ELIXIR_ERL_OPTIONS=+fnu:不是 Claper 参数而是 Erlang VM 开关,容器 locale 非 UTF-8 时必须注入,否则中文文件名/事件名双重编码(排障实录见部署篇 11.2);MIX_ENV:镜像构建期ENV MIX_ENV="prod"已烘焙,runtime.exs 的 prod 校验分支(SECRET_KEY_BASE 长度、BASE_URL scheme)靠它激活,运行时无需也不应改动。
六、生产配置模板
适用场景:公网 HTTPS 单实例,4GB 内存 / 40GB SSD,单场 ≤500 观众(更大规模先加 POOL_SIZE 与 PG max_connections,再考虑多副本)。/opt/claper/.env:
# —— 对外地址与监听 ——
BASE_URL=https://claper.example.com
LISTEN_IP=0.0.0.0
PORT=4000
# —— 安全 ——
SECRET_KEY_BASE=<openssl rand -base64 48 生成,勿复用>
SAME_SITE_COOKIE=Lax
SECURE_COOKIE=true
# —— 数据库 ——
DATABASE_URL=postgres://claper:<强口令>@db:5432/claper
DB_SSL=false
POOL_SIZE=10
QUEUE_TARGET=5000
# —— 存储 ——
PRESENTATION_STORAGE=local
MAX_FILE_SIZE_MB=50
GS_JPG_RESOLUTION=300x300
# —— 账户策略(白名单制) ——
ENABLE_ACCOUNT_CREATION=false
EMAIL_CONFIRMATION=false
ALLOW_UNLINK_EXTERNAL_PROVIDER=false
LANGUAGES=en,de,fr
# —— 邮件(注册确认/magic link 必须真实可达) ——
MAIL_TRANSPORT=smtp
SMTP_RELAY=smtp.example.com
SMTP_PORT=465
SMTP_AUTH=always
SMTP_USERNAME=claper@example.com
SMTP_PASSWORD=<注入,勿明文入库>
MAIL_FROM=noreply@example.com
MAIL_FROM_NAME=Claper
# —— 反代真实 IP ——
REMOTE_IP_PROXIES=10.89.56.0/24
REMOTE_IP_HEADERS=x-forwarded-for,x-real-ip
# —— 法务与跳转 ——
TERMS_URL=https://example.com/terms
PRIVACY_URL=https://example.com/privacy
LOGOUT_REDIRECT_URL=https://example.com
# —— BEAM 编码修复(中文环境必加) ——
ELIXIR_ERL_OPTIONS=+fnu
模板要点:SECURE_COOKIE=true 只在反代终结 TLS 后开(反代须传 X-Forwarded-Proto https,否则浏览器拒收 cookie 造成登录后死循环);ENABLE_ACCOUNT_CREATION=false + 管理员建号适合企业内训;REMOTE_IP_PROXIES 填反代所在网段(本例 compose 子网),配合 router.ex 内建登录限流才不误伤;S3 化时把存储四项换成 PRESENTATION_STORAGE=s3 + S3_* 四件套并显式设 S3_PUBLIC_URL。
七、常见问题
7.1 启动报 SECRET_KEY_BASE must be at least 32 bytes long
现象(实测原文,节选):
Runtime terminating during boot ({#{message=><<"SECRET_KEY_BASE must be at least 32 bytes long. See https://docs.claper.co/configuration.html#production-docker">>,'__struct__'=>'Elixir.RuntimeError','__exception__'=>true},[{elixir_eval,'__FILE__',1,[{file,"/app/releases/3.2.0/runtime.exs"},{line,39}]},...
Crash dump is being written to: erl_crash.dump...done
定位:runtime.exs 对 prod 的显式长度校验(byte_size(key) < 32 即 raise)。解决:openssl rand -base64 48 重新生成写入 .env,docker compose up -d app 重建。
7.2 启动报 Invalid LISTEN_IP 'not-an-ip' error: :einval
现象(实测原文,节选):
Runtime terminating during boot ({#{message=><<"Invalid LISTEN_IP 'not-an-ip' error: :einval">>,'__struct__'=>'Elixir.RuntimeError','__exception__'=>true},...{line,25}...
定位::inet.parse_address 校验失败,LISTEN_IP 必须是合法 IPv4/IPv6 字面量。解决:改回 0.0.0.0 或填具体地址;报错直接带原值,秒级定位。
7.3 启动报 Invalid PRESENTATION_STORAGE value gcs
见 5.4。合法值只有 local/s3;GCS/Azure 需走 S3 兼容端点(S3_SCHEME/S3_HOST 指向其兼容网关)。
7.4 启动报 Config variable POOL_SIZE must be an integer. Got abc
见 5.3。所有整数类参数(PORT/POOL_SIZE/QUEUE_TARGET/MAX_FILE_SIZE_MB/SMTP_PORT/SMTP_RETRIES/SMTP_SSL_DEPTH)共用 get_int_from_path_or_env,非纯数字直接 raise,报错点名变量。
7.5 BASE_URL 报 scheme 错误
见 5.1 与 7.1 同形态:BASE_URL must start with \http` or `https`. Currently configured as `ftp://localhost``。实测原文见 5.1 代码块。
7.6 注册页 500:BadBooleanError(布尔参数写 yes/no 的陷阱)
现象:把 ENABLE_ACCOUNT_CREATION 以 CONFIG_DIR 文件方式写成 yes,启动一切正常,访问注册页直接 500,日志实测原文:
claper-3.2.0-app | 11:02:30.555 request_id=GOPm5aF_WgPgFSwAAB4D [info] GET /users/register
claper-3.2.0-app | 11:02:30.555 [error] #PID<0.3053.0> running ClaperWeb.Endpoint (connection #PID<0.3052.0>, stream id 1) terminated
Server: localhost:8129 (http)
Request: GET /users/register
** (exit) an exception was raised:
** (BadBooleanError) expected a boolean on left-side of "and", got: :no
(claper 3.2.0) lib/claper_web/controllers/user_registration_controller.ex:60: ClaperWeb.UserRegistrationController.account_creation_allowed?/0
定位:布尔参数经 String.to_existing_atom() 解析——yes/no 恰好是 BEAM 预存原子,解析成 :yes/:no 能通过启动,直到控制器里 :no and ... 才在运行期炸出 500。探针确认了配置值形态:
value_of_yes=: :yes
register: 500
value_of_no=: :no
register_with_no: 500
更冷门的值(如 1、on)因对应原子不存在,to_existing_atom 会直接在启动期 ArgumentError——两种形态都是「写法不规范」的代价。
解决:布尔类参数(ENABLE_ACCOUNT_CREATION/EMAIL_CONFIRMATION/DB_SSL/SECURE_COOKIE/OIDC_AUTO_REDIRECT_LOGIN/DISABLE_PASSWORD_LOGIN/ALLOW_UNLINK_EXTERNAL_PROVIDER/ENABLE_MAILBOX_ROUTE)只写 true 或 false。改回规范值后实测恢复:register_restored: 200。
7.7 DISABLE_PASSWORD_LOGIN=true 设了没反应
启动日志已有答案(实测见 5.7):OIDC 未配置时该参数被 and oidc_enabled 短路,属故意设计——防止把包括种子 admin 在内的所有人锁在门外。先按 5.7 的四步顺序配好 OIDC 再启用。
7.8 改了 PRESENTATION_STORAGE_DIR 上传目录不变
不是 bug,是机制(实测见 5.4):该变量在 config.exs 编译期求值并烘焙进镜像,运行时环境变量与 CONFIG_DIR 文件都不生效。换目录 = 把卷挂到 /app/uploads;确需其他路径只能自 build 镜像时传 --build-arg/改 Dockerfile ENV。
7.9 恢复基线
本文全部实验(MAX_FILE_SIZE_MB=42、LANGUAGES=en,de、SameSite=None/Secure=true、ENABLE_ACCOUNT_CREATION=false、transcription_enabled=true、CONFIG_DIR 文件、DISABLE_PASSWORD_LOGIN=true)在取证结束后已全部还原并重建验证,实测回显:
max_file_size=15
languages=["en", "fr", "es", "it", "de"]
cookie=[same_site_cookie: "Lax", secure_cookie: false]
account_creation=true
register: 200
八、参考链接
- 官方配置文档:https://docs.claper.co/self-hosting/configuration.html (查询日期 2026-11-01)
runtime.exs(参数读取与校验的权威来源):https://github.com/ClaperCo/Claper/blob/v3.2.0/runtime.exsconfig.exs(storage_dir 编译期读取点,第 82 行):https://github.com/ClaperCo/Claper/blob/v3.2.0/config/config.exs.env.sample:https://github.com/ClaperCo/Claper/blob/v3.2.0/.env.sample- 配置辅助模块
lib/claper/helpers/config.ex:https://github.com/ClaperCo/Claper/blob/v3.2.0/lib/claper/helpers/config.ex - 站内:《Claper 介绍与选型指南》、《Claper Docker Compose 部署指南》