auch LLM/KI-generiert
Verstanden – kein Ruby/Python auf dem Host. Hier ist ein reiner Bash + awk Patch, der:
• eine prozent-kodierte SMTP_URL nur mit Bash erstellt (Byte-für-Byte-Schleife; kein Python),
• SMTP_URL im env:-Block mit awk einfügt oder ersetzt (kein sed),
• die pro Schlüssel DISCOURSE_SMTP_* Zeilen entfernt (sichere verankerte Löschungen),
• eine kleine Plausibilitätsprüfung nur mit grep/awk hinzufügt.
Anwendung mit git apply -p0 im discourse_docker Repository.
--- 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"
+ }
+
##############################################
# Pure-Bash URL encoder for SMTP credentials #
##############################################
@@ -989,17 +1113,10 @@
# 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"
+ # update_setting_yaml "DISCOURSE_SMTP_USER_NAME" "$smtp_user"
+ # update_setting_yaml "DISCOURSE_SMTP_PASSWORD" "$smtp_password"
}
-
-# 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.
+}
Warum dies Ihre Einschränkung erfüllt
• Kein Ruby/Python auf dem Host: nur Bash + awk + od/printf/grep (alles Standard).
• Keine sed-Ersetzung von Geheimnissen: Wir vermeiden die „Bash → sed → YAML“-Mehrfach-Escape-Falle.
• Atomare Schreibweise: Bearbeitung in eine temporäre Datei, dann mv über containers/app.yml.
• Rückwärtskompatibel: Wenn env: nicht existiert, erstellen wir es minimal; wenn es existiert, aktualisieren wir es vor Ort und entfernen widersprüchliche DISCOURSE_SMTP_* Zeilen.
• Anmeldeinformationen werden vor dem Einfügen URL-kodiert, sodass Sonderzeichen in Benutzer/Passwort den URL-Parser in Rails/Net::SMTP nicht brechen.
Wenn Sie die pro Schlüssel Variablen anstelle von SMTP_URL beibehalten möchten, kann ich einen Geschwister-Patch senden, der (immer noch ohne sed) dieselbe awk-Strategie verwendet, um Folgendes festzulegen:
DISCOURSE_SMTP_ADDRESS
DISCOURSE_SMTP_PORT
DISCOURSE_SMTP_USER_NAME
DISCOURSE_SMTP_PASSWORD
…mit YAML-sicheren, doppelt zitierten Werten und verankerten Ersetzungen.