Accent v1.32.1 配置参数详解

Accent v1.32.1 全量环境变量与 config/runtime.exs 配置项详解:数据库、端口与反代、密钥、SMTP、OAuth/OIDC、多项目角色权限、机器翻译与可观测性、日志级别;含 8 项参数改值实测回显与 DEBUG_ERRORS 编译期陷阱等排障实录。

AccentAccent翻译管理Elixir2026/10/27

Accent v1.32.1 配置参数详解

目标:把 Accent v1.32.1 的全部运行期配置讲清楚——每个变量的读取位置、类型、默认值、真实效果,以及哪些「官方 README 写了但代码里根本没有」。全文基于 v1.32.1 的 config/runtime.exs、lib/accent/config.ex 源码,并在 Ubuntu 22.04 + OTP Release 实例上逐项改值验证,回显照抄。

一、先厘清三件事

1.1 变量名没有 ACCENT_ 前缀

很多部署笔记写成 ACCENT_DATABASE_URL、ACCENT_SECRET_KEY_BASE,这在 v1.32.1 上不生效。实测在源码全量检索 ACCENT_,唯一命中是 S3 集成执行时拼进请求头的 ACCENT_VERSION=... 字符串,与配置无关:

grep -rn "ACCENT_" lib config rel
# 回显(仅一条):
# lib/accent/integrations/execute/aws_s3.ex:44: "ACCENT_VERSION=..."}

配置变量就是裸名:DATABASE_URL、PORT、SECRET_KEY_BASE、SMTP_ADDRESS 等。加前缀不会报错,但会被静默忽略——这是最常见的「配了却没生效」原因。

1.2 所有变量都在 config/runtime.exs 读取

Accent 遵循 Twelve-Factor,runtime.exs 在每个节点启动时执行,因此同一份 Release 产物可以靠环境变量切换环境。编译期配置(config/prod.exs)只保留少量固定项,两者的边界见第十一节。

1.3 类型解析规则

Accent.Config.get_env/2 是唯一入口,支持 6 种类型:

类型解析规则典型变量
:string(默认)原样返回,未设置为 nilCANONICAL_URL、MAILER_FROM、OAuth 各 *_CLIENT_ID
:integerString.to_integer/1,非法值直接抛异常,启动失败PORT、DATABASE_POOL_SIZE、DATABASE_TIMEOUT
:boolean"true"/"1"(忽略大小写)为真,其余为假DATABASE_SSL、DISABLE_CANONICAL_HOST_REDIRECT、ECTO_IPV6
:uriURI.parse/1,空串视为 nilCANONICAL_URL、STATIC_URL
:comma_separated_list按逗号切分,未设置返回 []LANGUAGE_TOOL_LANGUAGES
:cors单值或逗号分隔列表预留

⚠️ 关键陷阱:布尔型变量走 :boolean 解析("0"/"false" 为假),但功能开关型变量(DUMMY_LOGIN_ENABLED、SENTRY_DSN、SMTP_ADDRESS、TZDATA_AUTOUPDATE_DISABLED 等)在 runtime.exs 里用的是 if get_env("XXX") 直接判空。Elixir 中空字符串 "" 是真值,因此 DUMMY_LOGIN_ENABLED=0、=false、甚至 = 空值都会开启功能,只有「完全不存在这个变量」才会关闭。详见第七节实测。

二、配置生效的三种验证手段

不要靠「重启了应该就好了」判断配置是否生效,用下面三种手段拿回显。

手段一:systemd EnvironmentFile(生产推荐)

# /etc/accent/accent.env
PORT=4000
DATABASE_URL=postgresql://accent:q1w2e3r4@127.0.0.1:5432/accent_prod
SECRET_KEY_BASE=<64 字节随机串>
# /etc/systemd/system/accent.service
[Service]
EnvironmentFile=/etc/accent/accent.env
ExecStart=/opt/accent-release/bin/accent start

手段二:bin/accent eval 读运行期配置(本文主要手段)

Release 自带 eval 子命令,不启动业务进程,但会执行 runtime.exs,所以能把「环境变量 → 应用配置」的真实映射打印出来:

/opt/accent-release/bin/accent eval \
  'IO.inspect(Application.get_env(:accent, Accent.Endpoint)[:http], label: "endpoint_http")'

手段三:启动日志与接口。端口、LanguageTool、迁移结果都会在启动时打印;认证提供商可通过 GraphQL 查询。

三、数据库配置

变量默认值作用实测
DATABASE_URLpostgres://localhost/accent_developmentEcto 连接串,postgresql://用户:口令@主机:端口/库名✔
DATABASE_SSLfalse是否启用 SSL 连接源码依据
DATABASE_POOL_SIZE未设置时用 Ecto 默认 10(README 标注 10)连接池大小✔ 改 25 生效
DATABASE_TIMEOUT29000单次查询超时(毫秒)✔ 改 15000 生效
DATABASE_QUEUE_TARGET500排队等待获取连接的毫秒阈值源码依据
DATABASE_QUEUE_INTERVAL2000队列检查间隔(毫秒)源码依据
ECTO_IPV6false为真时 socket 加 :inet6,用于纯 IPv6 内网源码依据

验证(把池调到 25、超时调到 15000 后执行):

/opt/accent-release/bin/accent eval \
 'IO.inspect(Application.get_env(:accent, Accent.Repo)[:pool_size], label: "db_pool_size");
  IO.inspect(Application.get_env(:accent, Accent.Repo)[:timeout], label: "db_timeout")'
# 回显:
# db_pool_size: 25
# db_timeout: 15000

⚠️ 注意:runtime.exs 里 ssl_opts: [verify: :verify_none],即开启 DATABASE_SSL 也不校验证书。跨公网连库时应改走内网/专线或自行在源码中收紧该选项,不要仅依赖 SSL 打开。

⚠️ DATABASE_POOL_SIZE 与 DATABASE_QUEUE_* 是 :integer 解析,DATABASE_POOL_SIZE=25 (末尾空格)会抛 ArgumentError 直接启动失败;同理 DATABASE_URL 必须带协议头,写成 user:pass@host/db 会被 Ecto 拒绝。

四、站点、端口与反向代理

变量默认值作用实测
PORT4000HTTP 监听端口✔ 改 8080 生效
CANONICAL_URLhttp://localhost:<PORT>站点对外 URL,用于邮件链接、OAuth 回调、静态资源域名✔
STATIC_URL等于 CANONICAL_URL静态资源对外 URL,前后端分离/CDN 时单独设置源码依据
DISABLE_CANONICAL_HOST_REDIRECTfalse关闭「非规范域名 301 到 CANONICAL_URL」✔
DISABLE_FRAME_ANCESTORSfalse为真时输出 frame-ancestors 'self',禁止被第三方页面内嵌(同时会使 JIPT 无法嵌入你的站点)源码依据
WEBAPP_SKIP_SUBRESOURCE_INTEGRITYfalse去掉 link/script 的 SRI 属性,适用于会改写静态资源的代理源码依据
DEBUG_ERRORSfalse(编译期固定)错误页是否显示详情❌ 运行期改为 true 直接启动失败,见 11.2

实测端口变更:

# 测试实例监听 8080,宿主机 8134 转发到 8080
grep -E 'Running Accent|Access Accent' /var/log/accent.log
# 回显:
# [info] Running Accent.Endpoint with Bandit 1.13.0 at 0.0.0.0:8080 (http)
# [info] Access Accent.Endpoint at http://localhost:8134
curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://127.0.0.1:8134/
# 回显:HTTP 200

4.1 FORCE_SSL 是个「文档变量」

官方 README 的 Production setup 表里有 FORCE_SSL,但 v1.32.1 源码中检索不到该变量:

grep -rn "FORCE_SSL" config lib rel
# 回显:空

真实逻辑在 runtime.exs:

force_ssl: get_uri_part(canonical_uri, :scheme) === "https"

即 HTTPS 由 CANONICAL_URL 的 scheme 推导:写 https:// 就自动开 SSL 重定向,写 http:// 就关。设置 FORCE_SSL=true 不会有任何效果。

4.2 反向代理的标准写法

TLS 在 Nginx 终结时:

location / {
    proxy_pass http://127.0.0.1:4000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";   # GraphQL Subscription/WebSocket
}

同时设置 CANONICAL_URL=https://your.domain、DISABLE_CANONICAL_HOST_REDIRECT=1。若不加后者,健康检查常用 IP 直连会被 301 到规范域名,表现为「服务 active 但探活一直 301」。实测把 CANONICAL_URL 设为 https:// 且不加该开关时,直连 IP 的请求会被 Plug.SSL 重定向。

五、密钥与加密

变量默认值作用
SECRET_KEY_BASEUNSAFE_pJhJ9q2q...会话 Cookie 加密与签名主密钥
SIGNING_SALTaccent-signing-salt-used-for-callback-authCookie 与 OAuth 回调签名盐
MACHINE_TRANSLATIONS_VAULT_KEYDEFAULT_UNSAFE_VAULT_KEY机器翻译/AI 密钥的加密主密钥(Cloak)
  • SECRET_KEY_BASE 生成:openssl rand -hex 64(64 字节)。必须固定,重启换值会导致所有用户会话失效、OAuth 回调校验失败。
  • 默认值是公开的 UNSAFE_* 字符串,生产不替换等于没有加密。
  • MACHINE_TRANSLATIONS_VAULT_KEY 用于加密项目里保存的 Google/DeepL/OpenAI 密钥。换这个值后,数据库里已存的密文无法解密,表现为「机器翻译配置页能打开但翻译报密钥错误」,必须在项目设置里重新录入密钥。轮换前先评估。

六、邮件配置

runtime.exs 用 cond 决定邮件适配器,优先级从高到低:

顺序触发变量适配器
1SENDGRID_API_KEYBamboo.SendGridAdapter
2MANDRILL_API_KEYBamboo.MandrillAdapter
3MAILGUN_API_KEY(+MAILGUN_DOMAIN、MAILGUN_BASE_URI)Bamboo.MailgunAdapter
4SMTP_ADDRESS(+SMTP_PORT、SMTP_USERNAME、SMTP_PASSWORD)BambooSMTPAdapterWithTlsOptions
5都不配Bamboo.LocalAdapter(只写日志,不投递)

通用项:

变量作用
MAILER_FROM发件人地址,未设置时邮件头为空,多数 SMTP 会拒收
SMTP_API_HEADER附加到邮件的自定义头(如 SendGrid 分类)

实测把 SMTP 指向内网投递端后读取配置:

/opt/accent-release/bin/accent eval \
 'IO.inspect(Application.get_env(:accent, Accent.Mailer), label: "mailer")'
# 回显:
# mailer: [
#   mailer_from: "accent-noreply@example.com",
#   x_smtpapi_header: nil,
#   adapter: BambooSMTPAdapterWithTlsOptions,
#   server: "10.89.61.1",
#   port: "2525",
#   username: "accent",
#   password: "q1w2e3r4"
# ]

同版本实例向 SMTP 接收端发送邀请邮件,接收端日志回执:

2026-10-27T18:55:58 ... >> '250 OK queued'
...
<a ... href="http://localhost:8133/">http://localhost:8133/</a>

两点结论:投递通路可用;邮件正文里的链接来自 CANONICAL_URL,域名配错会直接导致邀请链接打不开。

⚠️ 注意:SMTP_PORT 在配置结构里是字符串(回显 port: "2525"),不是整数。v1.32.1 的适配器接受字符串,但如果你自行写脚本读取该配置并参与数值比较,需要先 String.to_integer/1。

⚠️ 优先级是「谁先命中谁生效」:同时配了 SENDGRID_API_KEY 和 SMTP_ADDRESS 时,SMTP 被静默忽略。

七、认证与单点登录

runtime.exs 按「命中即追加」的方式构造 Ueberauth 提供商列表,最终顺序与代码书写顺序相反(后写的在前):

变量组提供商备注
GOOGLE_API_CLIENT_ID/_SECRETGooglescope email openid
SLACK_CLIENT_ID/_SECRET(+SLACK_TEAM_ID)Slack
GITHUB_CLIENT_ID/_SECRETGitHubscope user
GITLAB_CLIENT_ID/_SECRET(+GITLAB_SITE_URL)GitLab自建 GitLab 必须改 GITLAB_SITE_URL,否则回调去 gitlab.com
DISCORD_CLIENT_ID/_SECRETDiscord
MICROSOFT_CLIENT_ID/_SECRET(+MICROSOFT_TENANT_ID)Microsoft租户 ID 决定单租户/多租户
AUTH0_CLIENT_ID/_SECRET(+AUTH0_DOMAIN)Auth0
OIDC_CLIENT_ID/_SECRET(+OIDC_ISSUER、OIDC_SCOPE、OIDC_UID_FIELD)通用 OIDC回调路径 /auth/oidc/callback
DUMMY_LOGIN_ENABLEDDummy只填邮箱、无密码登录

OIDC 的两个细节:

  • OIDC_ISSUER 推荐使用,值形如 https://idp.example.com/realms/xxx;OIDC_DISCOVERY_URI 已废弃但仍兼容,代码会裁掉 /.well-known/openid-configuration 后缀。
  • OIDC_UID_FIELD 默认 sub;若 IdP 用 preferred_username 做唯一标识可改,但同一用户换字段会产生重复账号。

实测只开 Dummy 登录后:

curl -s -X POST http://127.0.0.1:8134/graphql \
  -H "Authorization: Bearer <API_TOKEN>" -H 'Content-Type: application/json' \
  -d '{"query":"{ authenticationProviders { id } }"}'
# 回显:{"data":{"authenticationProviders":[{"id":"dummy"}]}}

⚠️ 再次强调空值陷阱:把 DUMMY_LOGIN_ENABLED 写成 0 或 false 想关掉它是无效的,"" 在 Elixir 里是真值。生产上线前必须删除该变量,而不是置 0。GitHub/Google 等提供商同理——只要变量存在(哪怕空串),登录按钮就会出现,随后因 client_id 为空而报错。

7.1 限制谁能建项目

变量作用
RESTRICTED_PROJECT_CREATOR_EMAIL_DOMAIN只允许该邮箱域的用户创建新项目,已存在项目不受影响
RESTRICTED_DOMAIN同上,旧别名,二者取其一即可(前者优先)

实测设为 example.com:

/opt/accent-release/bin/accent eval \
 'IO.inspect(Application.get_env(:accent, :restricted_domain), label: "restricted_domain")'
# 回显:restricted_domain: "example.com"

八、多租户与角色权限

Accent 的「多租户」是单实例多项目模型,不是每租户一套库表:

  • 用户(users)全局唯一,可同时属于多个项目;
  • 项目(projects)是数据隔离边界:文档、版本、翻译、评审、集成、API Token 全部挂在项目下;
  • 项目成员关系存在 collaborators,角色是项目级的,同一个人在不同项目可以角色不同;
  • 没有「组织/工作区」层级,跨项目汇总要靠 API 或自行聚合。

角色共 5 + 1 个,源码定义在 lib/accent/schemas/role.ex,GraphQL 枚举回显为大写:

curl -s -X POST http://127.0.0.1:8134/graphql \
  -H "Authorization: Bearer <API_TOKEN>" -H 'Content-Type: application/json' \
  -d '{"query":"{ roles { slug } }"}'
# 回显:
# {"data":{"roles":[{"slug":"OWNER"},{"slug":"ADMIN"},{"slug":"DEVELOPER"},
#                   {"slug":"REVIEWER"},{"slug":"TRANSLATOR"}]}}

权限是累加式(RoleAbilities 里逐层 ++),实测矩阵:

能力translatorreviewerdeveloperadminownerbot
只读(翻译/文档/版本/评论/活动)✔✔✔✔✔✔
编辑翻译、增删评论、处理 lint、订阅通知✔✔✔✔✔✘
标记已评审/取消评审、批量修正✘✔✔✔✔✘
同步/合并、文档增删改、API Token、集成、版本、机器翻译配置、Prompt✘✘✔✔✔仅 sync/merge
项目设置/删除、成员增删改、回滚、锁定文件操作✘✘✘✔✔✘

bot 角色不在 roles 查询结果里,它是 API Token 专用的受限角色:只读 + 同步/合并,无法修改翻译内容——给 CI/CD 用这个角色最安全。

💡 技术拓展:这套模型和 Weblate 的「项目/组件 + 角色」接近,但比 Transifex 的「组织-团队-项目」三层少一层。需要租户级硬隔离(客户之间互不可见、独立备份)时,不要试图在 Accent 内用项目模拟,应按客户拆实例/拆库,再在上层做统一入口。

九、机器翻译、AI 与拼写检查

变量作用默认
GOOGLE_TRANSLATIONS_SERVICE_ACCOUNT_KEYGoogle 翻译服务账号 JSON无
DEEPL_TRANSLATIONS_KEYDeepL API Key无
OPENAI_API_KEYAI 助手/Prompt 能力的 OpenAI Key无
OPENAI_API_MODEL模型名gpt-4o
OPENAI_API_BASE_URL兼容 OpenAI 协议的网关地址https://api.openai.com/v1/
AI_ASSISTANT_USE_DEFAULT_PROVIDER是否默认启用内置 AI 提供商无
LANGUAGE_TOOL_LANGUAGES拼写检查语言,逗号分隔,如 fr,en[]

这些密钥在项目设置里也可逐项目配置,环境变量提供的是「默认值」,且会被 MACHINE_TRANSLATIONS_VAULT_KEY 加密后落库。

实测开启拼写检查:

/opt/accent-release/bin/accent eval \
 'IO.inspect(Application.get_env(:accent, LanguageTool), label: "languagetool")'
# 回显:languagetool: [languages: ["fr", "en"]]

启动日志会等语言包预热完成才宣告就绪(实测约 3 秒):

[info] LanguageTool is ready to spellcheck languages: fr, en (started in 2972ms)

未配置时是提示而非错误:

[info] LanguageTool was not configured. Use LANGUAGE_TOOL_LANGUAGES environment variable to set a list of comma-separated languages short code.

⚠️ 语言包首次加载依赖网络下载时,内网环境会卡在启动阶段;建议预热后再切流量,或接受「前几秒拼写检查不可用」。

十、可观测性

变量作用
SENTRY_DSN后端运行时错误上报(含 SENTRY_ENVIRONMENT_NAME,默认 production)
WEBAPP_SENTRY_DSN前端错误上报(公钥 DSN)
NEW_RELIC_APP_NAME / NEW_RELIC_LICENSE_KEYNewRelic APM
OTEL_EXPORTER_OTLP_ENDPOINT设置后启用 OTLP 链路导出(Phoenix/Absinthe/Ecto/Oban/Bandit/Tesla)
OTEL_EXPORTER_OTLP_HEADERS / OTEL_RESOURCE_ATTRIBUTES导出头与资源属性
TZDATA_AUTOUPDATE_DISABLED关闭时区库自动更新,只读文件系统必设

runtime.exs 里只有 OTEL_EXPORTER_OTLP_ENDPOINT 参与判断,未设置时显式 traces_exporter: :none。README 列出的 OTEL_EXPORTER_OTLP_PROTOCOL 在源码中检索不到:

grep -rn "OTEL_EXPORTER_OTLP_PROTOCOL" config lib rel
# 回显:空

需要 gRPC 导出时应以你所使用的 exporter 版本默认行为为准,不要指望这个变量。

十一、日志级别:为什么改了没用

11.1 级别是编译期写死的

config/prod.exs:

config :logger, :console,
  format: "$time $metadata[$level] $message\n",
  level: :info,
  metadata: ~w(request_id current_user graphql_operation hook_service hook_url)a

全仓库检索 LOG_LEVEL 无任何命中:

grep -rn "LOG_LEVEL" config lib rel
# 回显:空

运行期确认:

/opt/accent-release/bin/accent eval \
 'IO.inspect(Application.get_env(:logger, :console), label: "logger_console")'
# 回显:
# logger_console: [
#   format: "$time $metadata[$level] $message\n",
#   level: :info,
#   metadata: [:request_id, :current_user, :graphql_operation, :hook_service, :hook_url]
# ]

结论:v1.32.1 没有 LOG_LEVEL 环境变量。想调级别只有两条路:

  1. 改 config/prod.exs 的 level: :debug,重新 mix release(官方发行版产物做不到);
  2. 运行期用 eval / rpc 动态改(重启即失效),仅适合临时排障:
/opt/accent-release/bin/accent rpc 'Logger.configure(level: :debug)'

对于生产,建议保持 :info:metadata 里已经带了 request_id,配合反向代理的 access log 足够定位请求;:debug 会把 Ecto 的 SQL 全量打出来,日志量与磁盘占用会明显上升。

11.2 DEBUG_ERRORS 的编译期陷阱(实测)

想在错误页看到堆栈,于是加 DEBUG_ERRORS=true,服务直接起不来:

ERROR! the application :accent has a different value set for path [:debug_errors]
inside key Accent.Endpoint during runtime compared to compile time. Since this
application environment entry was marked as compile time, this difference can
lead to different behavior than expected:

  * Compile time value was set to: false
  * Runtime value was set to: true

To fix this error, you might:
  * Make the runtime value match the compile time one
  * Recompile your project.
  * Alternatively, you can disable this check...
Crash dump is being written to: erl_crash.dump...done

根因:config/prod.exs 已经把 debug_errors: false 写进编译期环境,runtime.exs 又用环境变量覆盖,OTP 的 validate_compile_env 直接拒绝启动。移除该变量后恢复正常:

[info] Running Accent.Endpoint with Bandit 1.13.0 at 0.0.0.0:8080 (http)
[info] Access Accent.Endpoint at http://localhost:8134

所以「调试错误页」不能在运行期开关,只能在构建时改 prod.exs 并重新打包。生产正确做法是保留 false,用 Sentry/日志排查。

十二、一份可直接用的生产配置清单

# ---- 必需 ----
PORT=4000
DATABASE_URL=postgresql://accent:q1w2e3r4@127.0.0.1:5432/accent_prod
SECRET_KEY_BASE=<openssl rand -hex 64>
SIGNING_SALT=<固定随机串,勿改>
MACHINE_TRANSLATIONS_VAULT_KEY=<固定随机串,勿改>

# ---- 站点 ----
CANONICAL_URL=https://translate.example.com
DISABLE_CANONICAL_HOST_REDIRECT=1
# 不要设置 FORCE_SSL(v1.32.1 不读取)

# ---- 数据库调优 ----
DATABASE_POOL_SIZE=20
DATABASE_TIMEOUT=29000

# ---- 邮件 ----
MAILER_FROM=accent@example.com
SMTP_ADDRESS=10.0.0.25
SMTP_PORT=587
SMTP_USERNAME=accent
SMTP_PASSWORD=<口令>

# ---- 登录 ----
# 生产不要设置 DUMMY_LOGIN_ENABLED(注意:设为 0/false 也会开启)
OIDC_CLIENT_ID=<client-id>
OIDC_CLIENT_SECRET=<client-secret>
OIDC_ISSUER=https://idp.example.com/realms/main
RESTRICTED_PROJECT_CREATOR_EMAIL_DOMAIN=example.com

# ---- 可选 ----
LANGUAGE_TOOL_LANGUAGES=zh,en
SENTRY_DSN=<dsn>
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
TZDATA_AUTOUPDATE_DISABLED=1

生效检查三步:

systemctl restart accent
systemctl is-active accent                     # active
journalctl -u accent -n 20 --no-pager | grep -E 'Running Accent|LanguageTool'
curl -s -o /dev/null -w '%{http_code}\n' https://translate.example.com/   # 200

十三、常见问题(排障实录)

Q1:变量加了 ACCENT_ 前缀,重启后毫无变化? v1.32.1 不读任何 ACCENT_* 配置变量。去掉前缀,再用 bin/accent eval 打印实际配置值确认。

Q2:DUMMY_LOGIN_ENABLED=0 之后匿名登录还在? 该变量用 if get_env(...) 判空,"0" 是非空字符串,同样为真。必须删除变量本身。

Q3:改了 PORT,服务日志显示新端口,但外部仍访问不了? 服务端已生效(日志 at 0.0.0.0:8080),问题在端口转发/防火墙/反向代理 proxy_pass 未同步。逐层核对:ss -ltnp | grep 8080 → 安全组 → Nginx upstream。

Q4:配了 SMTP 却没收到邮件? 先看是否同时配了 SENDGRID_API_KEY(优先级更高,SMTP 被忽略);再看 MAILER_FROM 是否为空;最后确认没有落到 Bamboo.LocalAdapter(只写日志)。用 eval 打印 Accent.Mailer 一目了然。

Q5:邮件里的链接是 http://localhost:4000? CANONICAL_URL 没配或配错。它同时决定邮件链接、OAuth 回调域名与 force_ssl,改了要重启。

Q6:反代之后一直 301 循环? CANONICAL_URL 用了 https(自动开 SSL 重定向),而反代把 X-Forwarded-Proto 丢了,Accent 认为请求是 http 又重定向。补 proxy_set_header X-Forwarded-Proto $scheme;,并确认健康检查加 DISABLE_CANONICAL_HOST_REDIRECT=1。

Q7:改成 DEBUG_ERRORS=true 后服务起不来? 见 11.2,属编译期配置,运行期改必崩,删掉变量即可恢复。

Q8:机器翻译突然全部报密钥错误? 换过 MACHINE_TRANSLATIONS_VAULT_KEY。旧密文无法解密,需在项目设置里重新录入各服务密钥。

十四、参考链接