# ./discourse-setup 将 SMTP 用户名放在 SMTP 密码的开头

**URL:** <https://meta.discourse.org/t/discourse-setup-puts-smtp-username-at-start-of-smtp-password/381710>\
**Category:** Support\
**Tags:** install\
**Created:** [2025年九月5日 12:37 UTC](https://meta.discourse.org/t/discourse-setup-puts-smtp-username-at-start-of-smtp-password/381710 "2025-09-05T12:37:46Z")\
**Posts on this page:** 1\
**Showing post:** 15

<div class="post-metadata">

**Author:** ![Ethsim2](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/ethsim2/32/522255_2.png) [@Ethsim2](https://meta.discourse.org/u/Ethsim2)\
**Post date:** [2025年九月15日 19:42 UTC](https://meta.discourse.org/t/discourse-setup-puts-smtp-username-at-start-of-smtp-password/381710/15 "2025-09-15T19:42:00Z")

</div>

> **LLM/AI 生成的建议**
>
> \n以下是一个精简的补丁，它：\n\n- 停止使用 sed\n- 构建一个经过百分比编码的 SMTP\_URL\n- 通过 Ruby 的 YAML (Psych) 编辑 containers/app.yml，因此 YAML 的引用/转义由真正的解析器处理\n- 删除每个键的 SMTP 变量以避免冲突\n\n在 discourse\_docker 仓库中使用 `git apply -p0` 应用。\n\n- - -\n\n补丁 1 -\n\n`discourse-setup`\n\n（使用 Ruby YAML 编写 SMTP，而不是 sed）\n\n`diff\n--- a/discourse-setup\n+++ b/discourse-setup\n@@ -867,6 +867,77 @@ write_smtp_settings() {\n local app_yml=\"containers/app.yml\"\n [[-f \"$app_yml\"]] || die \"Cannot find $app_yml. Did you run bootstrap?\"\n\n+ # 使用 Python 标准库构建 URL 编码的 SMTP_URL（无 shell 转义技巧）\n+ urlencode() {\n+ python3 - \u003c\u003c'PY'\n+import sys, urllib.parse\n+print(urllib.parse.quote(sys.stdin.read().strip(), safe='._~-'))\n+PY\n+ }\n+\n+ # 重要提示：读取变量时不要进行反斜杠处理\n+ # （这些来自之前的提示；只需确保在提示时使用 -r）\n+ local addr=\"$smtp_address\"\n+ local port=\"$smtp_port\"\n+ local user_enc pass_enc\n+ user_enc=\"$(printf '%s' \"$smtp_user\" | urlencode)\"\n+ pass_enc=\"$(printf '%s' \"$smtp_password\" | urlencode)\"\n+ local smtp_url=\"smtp://${user_enc}:${pass_enc}@${addr}:${port}\"\n+\n+ # 使用 Ruby 安全地加载/修改/转储 YAML（消除 3 层转义）\n+ ruby - \u003c\u003c'RUBY' \"$app_yml\" \"$smtp_url\"\n+require \"yaml\"\n+require \"psych\"\n+path, url = ARGV\n+doc = YAML.safe_load(File.read(path), permitted_classes: [], aliases: true) || {}\n+\n+# 确保顶层结构是 Hash 并且有 env\n+unless doc.is_a?(Hash)\n+ abort \"containers/app.yml 未解析为 Hash\"\n+end\n+doc[\"env\"] ||= {}\n+env = doc[\"env\"]\n+\n+# 写入单行 SMTP_URL；删除每个键的变量以避免冲突\n+env[\"SMTP_URL\"] = url\n+%w[DISCOURSE_SMTP_ADDRESS DISCOURSE_SMTP_PORT DISCOURSE_SMTP_USER_NAME DISCOURSE_SMTP_PASSWORD].each { |k| env.delete(k) }\n+\n+# 转储回。 (Psych 安全地引用所需的字符串。)\n+File.write(path, Psych.dump(doc))\n+RUBY\n+\n+ # 对经典的“密码前缀为用户名”失败进行快速健全性检查\n+ python3 - \u003c\u003c'PY'\n+import re, sys\n+y = open(\"containers/app.yml\",\"r\",encoding=\"utf-8\").read()\n+m = re.search(r'^\\s*SMTP_URL:\\s*(?:\"|\\')?([^\\r\\n\"\\']+)', y, re.M)\n+assert m, \"写入后缺少 SMTP_URL\"\n+creds = m.group(1).split('@',1)[0].split('://',1)[-1]\n+assert \":\" in creds, \"SMTP_URL 凭据缺少 ':'\"\n+u, p = creds.split(':',1)\n+assert not p.startswith(u), \"密码似乎以用户名作为前缀\"\n+print(\"SMTP_URL 看起来正常。\")\n+PY\n+}\n+\n- # 写入每个键的 SMTP 条目（地址/端口/用户名/密码）\n- # （旧版：通过 sed 替换执行）\n- # 注意：历史上对于特殊字符来说很脆弱\n- update_setting_yaml \"DISCOURSE_SMTP_ADDRESS\" \"$smtp_address\"\n- update_setting_yaml \"DISCOURSE_SMTP_PORT\" \"$smtp_port\"\n- update_setting_yaml \"DISCOURSE_SMTP_USER_NAME\" \"$smtp_user\"\n- update_setting_yaml \"DISCOURSE_SMTP_PASSWORD\" \"$smtp_password\"\n-}\n+ # （旧版的每个键写入已移除，转而使用通过 YAML 设置的 SMTP_URL）\n+}\n`\n\n然后是补丁 2 -\n\n`templates/web.template.yml`\n\n（用于记录更安全的路径）\n\n`diff\n--- a/templates/web.template.yml\n+++ b/templates/web.template.yml\n@@ -68,6 +68,14 @@ params:\n DISCOURSE_SMTP_ENABLE_START_TLS: true\n #DISCOURSE_NOTIFICATION_EMAIL: noreply@example.com\n\n+ ## 首选的单行 SMTP 配置（由 discourse-setup 设置）：\n+ ## 对用户名和密码进行 URL 编码；示例：\n+ ## SMTP_URL: \"smtp://user%40example.com:p%40ss%3Aword@smtp.example.com:587\"\n+ ##\n+ #SMTP_URL:\n+\n ## 如果您无法使用 SMTP_URL，则可以改用每个键的变量。\n ## 请注意，使用 shell 工具编辑这些行可能会很脆弱，如果值包含\n ## 字符如 @、:、/、\"、\\ 或换行符。\n`\n\n为什么这样做有效（以及它避免了什么）\n\t•\tbash 层：我们只插入简单的变量；通过 stdin/argv 将秘密信息传递给 Python/Ruby，而不是通过 sed 正则表达式或 shell 求值。\n\t•\tsed 层：完全移除。\n\t•\tYAML 层：Ruby/Psych 正确处理引用和转义；无需手动处理引用。\n\t•\tSMTP 凭据：SMTP\_URL 中的百分比编码是在正确的位置对特殊字符进行编码以进行身份验证。\n\n如果您更喜欢保留每个键的变量，我可以提供一个姊妹补丁，它使用相同的 Ruby-YAML 方法直接设置 DISCOURSE\_SMTP\_\*（仍然没有 sed），但 SMTP\_URL 路线是最简洁的，因为它是一个键、一次写入、一个编码步骤。\n

---

_[View the full topic](https://meta.discourse.org/t/discourse-setup-puts-smtp-username-at-start-of-smtp-password/381710)._
