./discourse-setup mette il nome utente SMTP all'inizio della password SMTP

Ambiente

  • Discourse: latest tests-passed
  • Sistema operativo host: [Ubuntu 24.04 LTS / 24.04.3 LTS]
  • Metodo di installazione: installazione Docker ufficiale, esecuzione di ./discourse-setup
  • Piattaforma: VPS (Digital Ocean)

Passaggi per riprodurre

  1. Eseguire ./discourse-setup da /var/discourse
  2. Inserire i dettagli SMTP quando richiesto:
  1. Terminare la configurazione e ispezionare containers/app.yml

Risultato atteso

app.yml dovrebbe contenere due campi distinti, ad esempio:

DISCOURSE_SMTP_USER_NAME: "user@example.com"
DISCOURSE_SMTP_PASSWORD: "p@ssw0rd!"

Risultato effettivo

Il campo password viene scritto con il nome utente anteposto:

DISCOURSE_SMTP_USER_NAME: "user@example.com"
DISCOURSE_SMTP_PASSWORD: "user@brevo.comp@ssw0rd!"

Ciò causa il mancato recapito della posta e discourse-doctor conferma la password alterata.

Note
• La citazione esplicita della password al prompt non cambia l’esito.
• La password contiene caratteri speciali (@, !), ma il problema non è solo il quoting YAML: la stringa del nome utente viene letteralmente concatenata all’inizio della password.
• Riproducibile su più esecuzioni di ./discourse-setup.

1 Mi Piace

puoi verificare se questo è correlato a lettere particolari nella password, magari @ / ! fa emergere il bug?

Tutti gli script sed/awk e bash avanzati in discourse-setup lo rendono piuttosto difficile da mantenere. Forse @pfaffman ha qualche idea al riguardo?

1 Mi Piace

Va bene. È una buona scommessa.

@Ethsim2 se cambi la password in qualcosa con solo numeri e lettere e non uguale a parte del nome utente, hai questo problema?

Puoi condividere i valori che stai utilizzando?

Ci darò un’occhiata più tardi oggi.

Se hai rimosso la parte 83f…com dalla password e hai lasciato solo la password (5AH…), funziona?

2 Mi Piace

Sì, quindi eseguo .\launcher rebuild app, poiché .\discourse-setup aveva già configurato SWAP.

Sì. Quello è il problema. Ci sono più livelli di escaping che devono avvenire, come quando bash legge il valore, quando bash passa il valore a sed, quando sed lo sostituisce, e poi, forse, quando il file yml lo riceve. È un problema noto:

Ho ricategorizzato questo come Support.

Gli username SMTP contengono sempre @, non è vero?

Non credo che @ causi problemi.

1 Mi Piace

non c’è ! nel mio screenshot dove avviene la concatenazione

Qual è la password che stai cercando di inserire?

la password nello screenshot è 5AHQXrf4LDUmRB1J :slightly_smiling_face:

Modifica: questa password è una password predefinita di Brevo creata su un account che è stato successivamente eliminato

Non riesco a replicarlo. Che sistema operativo stai usando?

root@bro:/var/discourse# ./discourse-setup --skip-connection-test --skip-rebuild
skipping connection test
'samples/standalone.yml' -> 'containers/app.yml'
Found 29GB of memory and 32 physical CPU cores
setting db_shared_buffers = 4096MB
setting UNICORN_WORKERS = 8
containers/app.yml memory parameters updated.

Hostname for your Discourse? [discourse.example.com]: forum.phsics.site

Setting EC to 2
Skipping port check.
Email address for admin account(s)? [me@example.com,you@example.com]: jay@literatecomputing.com
SMTP server address? [smtp.example.com]: smtp-relay.brevo.com
SMTP port? [587]: 2525
SMTP user name? [user@example.com]: 83fca0012@smtp-brevo.com
SMTP password? []: 5AHQXrf4LDUmRB1J
notification email address? [noreply@forum.phsics.site]:
Optional email address for Let's Encrypt warnings? (ENTER to skip) [me@example.com]:
Optional MaxMind Account ID (ENTER to continue without MAXMIND GeoLite2 geolocation database) [123456]:

Does this look right?

Hostname          : forum.phsics.site
Email             : jay@literatecomputing.com
SMTP address      : smtp-relay.brevo.com
SMTP port         : 2525
SMTP username     : 83fca0012@smtp-brevo.com
SMTP password     : 5AHQXrf4LDUmRB1J
Notification email: noreply@forum.phsics.site
MaxMind account ID: (unset)
MaxMind license key: (unset)

ENTER to continue, 'n' to try again, Ctrl+C to exit:
letsencrypt.ssl.template.yml enabled

Configuration file at containers/app.yml updated successfully!

Updates successful. --skip-rebuild requested. Exiting.
root@bro:/var/discourse# grep SMTP containers/app.yml
  ## TODO: The SMTP mail server used to validate new accounts and send notifications
  # SMTP ADDRESS is required
  # WARNING: SMTP password should be wrapped in quotes to avoid problems
  DISCOURSE_SMTP_ADDRESS: smtp-relay.brevo.com
  DISCOURSE_SMTP_PORT: 2525
  DISCOURSE_SMTP_USER_NAME: 83fca0012@smtp-brevo.com
  DISCOURSE_SMTP_PASSWORD: "5AHQXrf4LDUmRB1J"
  #DISCOURSE_SMTP_ENABLE_START_TLS: true           # (optional, default: true)
  DISCOURSE_SMTP_DOMAIN: discourse.example.com # (required by some providers)
  #DISCOURSE_SMTP_OPENSSL_VERIFY_MODE: peer        # (optional, default: peer, valid values: none, peer, client_once, fail_if_no_peer_cert)
  #DISCOURSE_SMTP_AUTHENTICATION: plain            # (default: plain, valid values: plain, login, cram_md5)

Digital Ocean Ubuntu 24.04

LLM/AI -generated recommendazioni

\nqui sotto c’è una patch mirata che:\n\n- smette di usare sed\n- costruisce una SMTP_URL codificata in percentuale\n- modifica containers/app.yml tramite YAML di Ruby (Psych), quindi l’escape/quoting YAML è gestito da un vero parser\n- elimina le variabili SMTP per chiave per evitare contraddizioni\n\napplicare con git apply -p0 nella repository discourse_docker.\n\n- - -\n\nPatch 1 -\n\ndiscourse-setup\n\n(scrive SMTP usando Ruby YAML, non sed)\n\ndiff\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+ # Costruisce una SMTP_URL codificata in URL usando la libreria standard Python (nessun gioco di escape della 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+ # IMPORTANTE: legge le variabili senza manipolazione di backslash\n+ # (queste provengono da prompt precedenti; assicurarsi solo che -r sia usato al momento del prompt)\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+ # Usa Ruby per caricare/modificare/salvare in modo sicuro YAML (elimina 3 livelli di escaping)\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+# Assicura che la struttura di primo livello sia un Hash e abbia env\n+unless doc.is_a?(Hash)\n+ abort \"containers/app.yml non viene analizzato come Hash\"\n+end\n+doc[\"env\"] ||= {}\n+env = doc[\"env\"]\n+\n+# Scrive SMTP_URL su una sola riga; rimuove le variabili per chiave per evitare conflitti\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+# Scarica indietro. (Psych conserva le stringhe correttamente quotate secondo necessità.)\n+File.write(path, Psych.dump(doc))\n+RUBY\n+\n+ # rapido controllo di sanità per il classico fallimento "password preceduta da username"\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 mancante dopo la scrittura\"\n+creds = m.group(1).split('@',1)[0].split('://',1)[-1]\n+assert \":\" in creds, \"Credenziali SMTP_URL mancanti ':'\"\n+u, p = creds.split(':',1)\n+assert not p.startswith(u), \"La password sembra preceduta dall'username\"\n+print(\"SMTP_URL sembra sensato.\")\n+PY\n+}\n+\n- # Scrive voci SMTP per chiave (indirizzo/porta/username/password)\n- # (legacy: eseguito tramite sostituzioni sed)\n- # NOTA: storicamente fragile con caratteri speciali\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+ # (scritture legacy per chiave rimosse a favore di SMTP_URL tramite YAML)\n+}\n\n\npoi Patch 2 -\n\ntemplates/web.template.yml\n\n(per documentare il percorso più sicuro)\n\ndiff\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+ ## Configurazione SMTP preferita su una sola riga (impostata da discourse-setup):\n+ ## Codifica in URL username e password; esempio:\n+ ## SMTP_URL: \"smtp://user%40example.com:p%40ss%3Aword@smtp.example.com:587\"\n+ ##\n+ #SMTP_URL:\n+\n ## Se non puoi usare SMTP_URL, puoi impostare variabili per chiave invece.\n ## Attenzione che la modifica di quelle righe con strumenti da riga di comando può essere fragile se i valori includono\n ## caratteri come @, :, /, \", \\, o nuove righe.\n\n\nperché funziona (e cosa evita)\n\t•\tlayer bash: interpoliamo solo variabili semplici; i segreti vengono passati a Python/Ruby tramite stdin/argv, non tramite regex sed o eval della shell.\n\t•\tlayer sed: rimosso completamente.\n\t•\tlayer YAML: Ruby/Psych gestisce correttamente il quoting e l’escape; nessun quoting fatto a mano.\n\t•\tcredenziali SMTP: la codifica % in SMTP_URL è il posto giusto per codificare caratteri speciali per l’autenticazione.\n\nse preferisci mantenere le variabili per chiave, posso fornirti una patch sorella che usa lo stesso approccio Ruby-YAML per impostare direttamente DISCOURSE_SMTP_* (ancora senza sed), ma il percorso SMTP_URL è il più pulito perché è una chiave, una scrittura, un passaggio di codifica.\n

Purtroppo non possiamo fare affidamento sull’installazione di Ruby sul sistema host nelle installazioni di discourse: sarebbe relativamente semplice far sì che l’intero script sia in ruby, se potessimo, ma non c’è alcuna garanzia.

2 Mi Piace
generato anche da LLM/AI

Ricevuto: niente Ruby/Python sull’host. Ecco una patch pura Bash + awk che:
• costruisce una SMTP_URL codificata in percentuale usando solo Bash (ciclo byte per byte; niente Python),
• inserisce o sostituisce SMTP_URL nel blocco env: usando awk (niente sed),
• rimuove le righe DISCOURSE_SMTP_* per chiave (eliminazioni ancorate sicure),
• aggiunge un piccolo controllo di sanità usando solo grep/awk.

Applica con git apply -p0 nella repository discourse_docker.

--- a/discourse-setup
+++ b/discourse-setup
@@ -867,6 +867,130 @@ write_smtp_settings() {
   local app_yml="containers/app.yml"
   [[ -f "$app_yml" ]] || die "Impossibile trovare $app_yml. Hai eseguito il bootstrap?"

+  ##############################################
+  # Codificatore URL puro Bash per credenziali SMTP #
+  ##############################################
+  # Codifica tutto tranne A-Z a-z 0-9 . _ ~ -
+  # Funziona byte per byte; richiede bash e printf.
+  urlencode_cred() {
+    local s="$1" out= i ch o
+    # imposta la locale C per ottenere semantica dei byte
+    LC_ALL=C
+    for ((i=0; i<${#s}; i++)); do
+      ch="${s:i:1}"
+      case "$ch" in
+        [A-Za-z0-9._~-])
+          out+="$ch"
+          ;;
+        *)
+          # Ottieni valore byte: stampa carattere, leggi con od, poi formatta %HH
+          # Evita dipendenze pesanti esterne; od è in coreutils / busybox.
+          o=$(printf '%s' "$ch" | od -An -tu1 | awk '{$1=$1;print $1}')
+          # Se od ha fallito in qualche modo (vuoto), torna all'esadecimale tramite printf %02X del primo 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"
+  }
+
+  # Costruisci SMTP_URL (singola riga) dalle risposte raccolte
+  # Queste variabili sono raccolte in precedenza 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}"
+
+  ########################################################
+  # Modifica sicura per YAML tramite awk (niente sed / niente runtime esterni)
+  # - assicurati che esista env:
+  # - inserisci o sostituisci SMTP_URL sotto env:
+  # - rimuovi le chiavi DISCOURSE_SMTP_*
+  ########################################################
+  awk -v NEWVAL="$smtp_url" '
+    BEGIN{
+      have_env=0; in_env=0; inserted=0
+    }
+    # rileva la riga env: al livello superiore (inizio riga, possibilmente indentata 0..)
+    # considereremo un'indentazione di due spazi per i figli.
+    /^[[:space:]]*env:[[:space:]]*$/ {
+      print; have_env=1; in_env=1; next
+    }
+    # uscendo dal blocco env: quando l'indentazione torna a 0 o alla prossima chiave di livello superiore
+    in_env && /^[^[:space:]]/ {
+      if (!inserted) {
+        print "  SMTP_URL: \"' NEWVAL '\""
+        inserted=1
+      }
+      in_env=0
+    }
+    # mentre in env:, gestisci sostituzioni ed eliminazioni
+    in_env {
+      # elimina interamente le righe DISCOURSE_SMTP_* per chiave
+      if ($0 ~ /^[[:space:]]*DISCOURSE_SMTP_(ADDRESS|PORT|USER_NAME|PASSWORD):/) next
+      # sostituisci la riga SMTP_URL esistente
+      if ($0 ~ /^[[:space:]]*SMTP_URL:[[:space:]]*/) {
+        print "  SMTP_URL: \"' NEWVAL '\""
+        inserted=1
+        next
+      }
+      print
+      next
+    }
+    { print }
+    END{
+      # Se env: non è mai esistito, aggiungilo con la chiave
+      if (!have_env) {
+        print ""
+        print "env:"
+        print "  SMTP_URL: \"' NEWVAL '\""
+      } else if (in_env && !inserted) {
+        # env: è esistito e ci trovavamo ancora lì alla fine del file
+        print "  SMTP_URL: \"' NEWVAL '\""
+      }
+    }
+  ' "$app_yml" > "$app_yml.tmp.$$" && mv "$app_yml.tmp.$$" "$app_yml"
+
+  ##############################################
+  # Controllo di sanità: guardia di base contro la manomissione #
+  ##############################################
+  # 1) SMTP_URL presente
+  grep -q '^[[:space:]]*SMTP_URL:' "$app_yml" || die "SMTP_URL non scritto su $app_yml"
+  # 2) la password non è preceduta dal nome utente (firma classica di errore)
+  awk '
+    BEGIN{ok=1}
+    /^[[:space:]]*SMTP_URL:[[:space:]]*"/ {
+      line=$0
+      gsub(/^[[:space:]]*SMTP_URL:[[:space:]]*"/,"",line)
+      gsub(/".*$/,"",line)
+      # Estrai le credenziali prima di @ e dopo lo schema
+      # es. 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 "La password sembra preceduta dal nome utente"; exit 1 } }
+  ' "$app_yml"
+}
+
+  # Rimozione delle vecchie scritture per chiave
+  # (legacy: eseguite tramite sostituzioni sed)
+  # NOTA: storicamente fragili con caratteri speciali
+  # 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"
+
+  # Scritture per chiave SMTP per chiave (indirizzo/porta/nome utente/password)
+  # (legacy: eseguite tramite sostituzioni sed)
+  # NOTA: storicamente fragili con caratteri speciali
+  # 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"
+# Rimozione delle vecchie scritture per chiave in favore della scrittura atomica SMTP_URL sopra.
+
+
 -  # 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"
 -}
+  # Scritture per chiave SMTP per chiave (indirizzo/porta/nome utente/password)
+  # (legacy: eseguite tramite sostituzioni sed)
+  # NOTA: storicamente fragili con caratteri speciali
+  # 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"
+
+# Rimozione delle vecchie scritture per chiave in favore della scrittura atomica SMTP_URL sopra.
+}

Perché questo soddisfa il tuo vincolo
• Zero Ruby/Python sull’host: solo Bash + awk + od/printf/grep (tutti standard).
• Nessuna sostituzione sed di segreti: evitiamo la trappola multi-escape “Bash → sed → YAML”.
• Scrittura atomica-ish: modifica in un file temporaneo, poi mv su containers/app.yml.
• Retrocompatibile: se env: non esiste, lo creiamo in modo minimale; se esiste, lo aggiorniamo sul posto e rimuoviamo le righe DISCOURSE_SMTP_* in conflitto.
• Le credenziali vengono codificate in URL prima dell’inserimento, quindi i caratteri speciali in utente/password non interromperanno il parser URL in Rails/Net::SMTP.

Se preferisci mantenere le variabili per chiave invece di SMTP_URL, posso inviare una patch sorella che (ancora senza sed) utilizza la stessa strategia awk per impostare:

DISCOURSE_SMTP_ADDRESS
DISCOURSE_SMTP_PORT
DISCOURSE_SMTP_USER_NAME
DISCOURSE_SMTP_PASSWORD

…con valori racchiusi tra virgolette doppi sicuri per YAML e sostituzioni ancorate.

Allora non riesco a immaginare come sia potuto succedere. Riesci a replicarlo come nel mio esempio?

Hai copiato/incollato tutto il testo in un unico blocco in modo che il buffer non potesse tenere il passo con la velocità con cui stavi incollando?

hmmm mi chiedo se possiamo semplicemente includere la logica in launcher2, quindi possiamo usarla per eseguire l’installazione.
La quantità di ostacoli che discourse-setup sta superando è spettacolare.

È possibile compilare il launcher in un file eseguibile binario per il rilascio? In questo modo, il launcher (bash) potrà scaricare solo il file eseguibile (binario) corrispondente in base al sistema.

1 Mi Piace