./discourse-setup가 SMTP 비밀번호 앞에 SMTP 사용자 이름을 붙입니다

LLM/AI 생성 내용 포함

알겠습니다 — 호스트에 Ruby/Python이 없군요. 다음은 순수 Bash + awk를 사용한 패치입니다:
• Python 없이 Bash만으로(바이트 단위 루프) percent-encoded SMTP_URL을 생성합니다.
• awk를 사용하여 env: 블록 아래에 SMTP_URL을 삽입하거나 교체합니다 (sed 사용 안 함).
• 키별 DISCOURSE_SMTP_* 줄을 제거합니다 (안전한 앵커링 삭제).
• grep/awk만 사용하여 간단한 무결성 검사를 추가합니다.

discourse_docker 저장소에서 git apply -p0를 적용하십시오.

--- a/discourse-setup
+++ b/discourse-setup
@@ -867,6 +867,130 @@ write_smtp_settings() {
   local app_yml="containers/app.yml"
   [[ -f "$app_yml" ]] || die "Cannot find $app_yml. Did you run bootstrap?"

+  ##############################################
+  # Pure-Bash URL encoder for SMTP credentials #
+  ##############################################
+  # Encodes everything except A-Z a-z 0-9 . _ ~ -
+  # Works byte-by-byte; requires bash and printf.
+  urlencode_cred() {
+    local s="$1" out= i ch o
+    # set C locale to get byte semantics
+    LC_ALL=C
+    for ((i=0; i<${#s}; i++)); do
+      ch="${s:i:1}"
+      case "$ch" in
+        [A-Za-z0-9._~-])
+          out+="$ch"
+          ;;
+        *)
+          # Get byte value: print char, read with od, then format %HH
+          # Avoid external heavy deps; od is in coreutils / busybox.
+          o=$(printf '%s' "$ch" | od -An -tu1 | awk '{$1=$1;print $1}')
+          # If od somehow failed (empty), fall back to hex via printf %02X of first byte
+          if [ -z "$o" ]; then
+            o=$(printf '%s' "$ch" | head -c1 | od -An -tu1 | awk '{$1=$1;print $1}')
+          fi
+          printf -v o '%%%02X' "$o"
+          out+="$o"
+          ;;
+      esac
+    done
+    printf '%s' "$out"
+  }
+
+  # Build SMTP_URL (single line) from collected answers
+  # These vars are gathered earlier in discourse-setup:
+  #   $smtp_address  $smtp_port  $smtp_user  $smtp_password
+  local addr="$smtp_address"
+  local port="$smtp_port"
+  local user_enc pass_enc
+  user_enc="$(urlencode_cred "$smtp_user")"
+  pass_enc="$(urlencode_cred "$smtp_password")"
+  local smtp_url="smtp://${user_enc}:${pass_enc}@${addr}:${port}"
+
+  ########################################################
+  # YAML-safe edit via awk (no sed / no external runtimes)
+  # - ensure env: exists
+  # - insert or replace SMTP_URL under env:
+  # - remove DISCOURSE_SMTP_* keys
+  ########################################################
+  awk -v NEWVAL="$smtp_url" '
+    BEGIN{
+      have_env=0; in_env=0; inserted=0
+    }
+    # detect env: line at top level (start of line, possibly indented 0..)
+    # we’ll consider two-space indentation for children.
+    /^[[:space:]]*env:[[:space:]]*$/ {
+      print; have_env=1; in_env=1; next
+    }
+    # leaving env: block when indentation returns to 0 or next top-level key
+    in_env && /^[^[:space:]]/ {
+      if (!inserted) {
+        print "  SMTP_URL: \"" NEWVAL "\""
+        inserted=1
+      }
+      in_env=0
+    }
+    # while in env:, handle replacements and deletions
+    in_env {
+      # drop per-key DISCOURSE_SMTP_* lines entirely
+      if ($0 ~ /^[[:space:]]*DISCOURSE_SMTP_(ADDRESS|PORT|USER_NAME|PASSWORD):/) next
+      # replace existing SMTP_URL line
+      if ($0 ~ /^[[:space:]]*SMTP_URL:[[:space:]]*/) {
+        print "  SMTP_URL: \"" NEWVAL "\""
+        inserted=1
+        next
+      }
+      print
+      next
+    }
+    { print }
+    END{
+      # If env: never existed, append it with the key
+      if (!have_env) {
+        print ""
+        print "env:"
+        print "  SMTP_URL: \"" NEWVAL "\""
+      } else if (in_env && !inserted) {
+        # env: existed and we were still in it at EOF
+        print "  SMTP_URL: \"" NEWVAL "\""
+      }
+    }
+  ' "$app_yml" > "$app_yml.tmp.$$" && mv "$app_yml.tmp.$$" "$app_yml"
+
+  ##############################################
+  # Sanity check: basic guard against mangling #
+  ##############################################
+  # 1) SMTP_URL present
+  grep -q '^[[:space:]]*SMTP_URL:' "$app_yml" || die "SMTP_URL not written to $app_yml"
+  # 2) password not prefixed by username (classic failure signature)
+  awk '
+    BEGIN{ok=1}
+    /^[[:space:]]*SMTP_URL:[[:space:]]*"/{
+      line=$0
+      gsub(/^[[:space:]]*SMTP_URL:[[:space:]]*"/,"",line)
+      gsub(/".*$/,"",line)
+      # Extract creds before @ and after scheme
+      # e.g. smtp://user:pass@host:port
+      sub(/^[a-z]+:\/\//,"",line)
+      at=index(line,"@")
+      if (at>0) {
+        creds=substr(line,1,at-1)
+        colon=index(creds,":")
+        if (colon>0) {
+          user=substr(creds,1,colon-1)
+          pass=substr(creds,colon+1)
+          if (index(pass,user)==1) { ok=0 }
+        }
+      }
+    }
+    END{ if (!ok) { print "Password appears prefixed by username"; exit 1 } }
+  ' "$app_yml"
 }
 
-  # Write per-key SMTP entries (address/port/username/password)
-  # (legacy: performed via sed substitutions)
-  # NOTE: historically fragile with special chars
-  update_setting_yaml "DISCOURSE_SMTP_ADDRESS"  "$smtp_address"
-  update_setting_yaml "DISCOURSE_SMTP_PORT"     "$smtp_port"
-  update_setting_yaml "DISCOURSE_SMTP_USER_NAME" "$smtp_user"
-  update_setting_yaml "DISCOURSE_SMTP_PASSWORD" "$smtp_password"
-}
+  # Legacy per-key writes removed in favor of atomic SMTP_URL write above.
+}

이것이 당신의 제약을 충족하는 이유
• 호스트에 Ruby/Python 제로: Bash + awk + od/printf/grep만 사용 (모두 표준 도구).
• 비밀 정보의 sed 치환 없음: “Bash → sed → YAML” 다중 이스케이프 함정을 피합니다.
• 원자적(atomic-ish) 쓰기: 임시 파일로 편집한 후 containers/app.yml로 mv합니다.
• 하위 호환 안전: env:가 없으면 최소한으로 생성하고, 있으면 제자리에서 업데이트하고 충돌하는 DISCOURSE_SMTP_* 줄을 제거합니다.
• 자격 증명은 삽입 전에 URL-인코딩되므로, 사용자/비밀번호의 특수 문자가 Rails/Net::SMTP의 URL 파서를 깨뜨리지 않습니다.

SMTP_URL 대신 키별 변수를 유지하고 싶다면, (여전히 sed 없이) 동일한 awk 전략을 사용하여 다음을 설정하는 형제 패치를 보낼 수 있습니다:

DISCOURSE_SMTP_ADDRESS
DISCOURSE_SMTP_PORT
DISCOURSE_SMTP_USER_NAME
DISCOURSE_SMTP_PASSWORD

…YAML에서 안전한 이중 인용부호 값과 앵커링된 치환을 사용하여.