Modified - [install] [mailer] Split the installer mail sender field into display name and address, auto-filled the sender name from Site Title, and reused that site name in test mails.

- 1 - I updated `services/forms/user_form.go`, `routers/install/install.go`, `templates/install.tmpl`, `options/locale/locale_en-US.json`, and `options/locale/locale_ro-RO.json` so the installer now edits `Send Email As` through separate sender-name and sender-address inputs on the same line, auto-fills the sender name from the part of `Site Title` before `:`, or from the first word when no `:` is present, and composes `[mailer] FROM = site_name <mail@address>` server-side during test mail and install submission.
- 2 - I updated `services/mailer/mail.go` so the installer test mail subject/body reuse that derived site name instead of the hardcoded `Gitea` label.
- 3 - I added targeted coverage in `routers/install/routes_test.go` and `services/mailer/mail_test.go` for the derived site-name logic and the composed installer `FROM` address.
- 4 - I updated `routers/install/install.go`, `templates/install.tmpl`, and the installer locale strings so the primary install button now renders `Install <site_name>` from the same derived site-name rule and updates live as `Site Title` changes.
This commit is contained in:
2026-05-21 02:32:36 +03:00
parent 953a7c08be
commit b1db0705d8
9 changed files with 200 additions and 12 deletions
+6
View File
@@ -828,3 +828,9 @@ History search guidance:
167 - [2026-05-20 01:12:06] - v1.27.0-dev-178-gf09229e739 - Type: Modified - [footer] [sticky-ui] Fine-tuned the persistent footer lip geometry for better visual balance.
- 1 - I updated `web_src/css/home.css` so the persistent-footer toggle lip now rises a bit higher above the footer edge and is slightly taller, giving the handle a more relaxed fit against the footer border.
168 - [2026-05-21 01:08:54] - v1.27.0-dev-179-g953a7c08be - Type: Modified - [install] [mailer] Split the installer mail sender field into display name and address, auto-filled the sender name from Site Title, and reused that site name in test mails.
- 1 - I updated `services/forms/user_form.go`, `routers/install/install.go`, `templates/install.tmpl`, `options/locale/locale_en-US.json`, and `options/locale/locale_ro-RO.json` so the installer now edits `Send Email As` through separate sender-name and sender-address inputs on the same line, auto-fills the sender name from the part of `Site Title` before `:`, or from the first word when no `:` is present, and composes `[mailer] FROM = site_name <mail@address>` server-side during test mail and install submission.
- 2 - I updated `services/mailer/mail.go` so the installer test mail subject/body reuse that derived site name instead of the hardcoded `Gitea` label.
- 3 - I added targeted coverage in `routers/install/routes_test.go` and `services/mailer/mail_test.go` for the derived site-name logic and the composed installer `FROM` address.
- 4 - I updated `routers/install/install.go`, `templates/install.tmpl`, and the installer locale strings so the primary install button now renders `Install <site_name>` from the same derived site-name rule and updates live as `Site Title` changes.
+4 -2
View File
@@ -327,7 +327,9 @@
"install.smtp_port": "SMTP Port",
"install.smtp_from": "Send Email As",
"install.smtp_from_invalid": "The \"Send Email As\" address is invalid",
"install.smtp_from_helper": "Email address Gitea will use. Enter a plain email address or use the \"Name\" <email@example.com> format.",
"install.smtp_from_helper": "Enter the sender name and email address Gitea will use. The name field is auto-filled from the part of Site Title before \":\", or from the first word when no \":\" is present.",
"install.smtp_from_name_placeholder": "Sender name",
"install.smtp_from_address_placeholder": "email@example.com",
"install.mailer_user": "SMTP Username",
"install.mailer_password": "SMTP Password",
"install.test_mail_to": "Send Testing Email",
@@ -383,7 +385,7 @@
"install.admin_management_policy.grantor_only_helper": "Regular admins can promote users to administrator and can later modify or delete only the administrators they directly promoted.",
"install.admin_management_policy.grantor_inheritance": "Grantor chain with inheritance",
"install.admin_management_policy.grantor_inheritance_helper": "Like grantor-only, but if the direct grantor becomes inactive or has sign-in disabled, management passes up the admin grant chain until an eligible admin is found. If none is found, super admins keep control.",
"install.install_btn_confirm": "Install Gitea",
"install.install_btn_confirm": "Install %s",
"install.test_git_failed": "Could not test 'git' command: %v",
"install.sqlite3_not_available": "This Gitea version does not support SQLite3. Please download the official binary version from %s (not the 'gobuild' version).",
"install.invalid_db_setting": "The database settings are invalid: %v",
+4 -2
View File
@@ -327,7 +327,9 @@
"install.smtp_port": "Port SMTP",
"install.smtp_from": "Trimite e-mail ca",
"install.smtp_from_invalid": "Adresa „Trimite e-mail ca” nu este validă",
"install.smtp_from_helper": "Adresa de e-mail pe care o va folosi Gitea. Introdu o adresă de e-mail simplă sau utilizează formatul „Nume” <email@example.com>.",
"install.smtp_from_helper": "Introdu numele expeditorului și adresa de e-mail pe care o va folosi Gitea. Câmpul pentru nume este completat automat din partea din Site Title aflată înainte de „:”, sau din primul cuvânt dacă „:” nu există.",
"install.smtp_from_name_placeholder": "Numele expeditorului",
"install.smtp_from_address_placeholder": "email@example.com",
"install.mailer_user": "Nume de utilizator SMTP",
"install.mailer_password": "Parola SMTP",
"install.test_mail_to": "Trimite un e-mail de testare",
@@ -383,7 +385,7 @@
"install.admin_management_policy.grantor_only_helper": "Administratorii obișnuiți pot promova utilizatori la rang de administrator și ulterior pot modifica sau șterge doar administratorii promovați direct de ei.",
"install.admin_management_policy.grantor_inheritance": "Lanț de acordare cu moștenire",
"install.admin_management_policy.grantor_inheritance_helper": "Ca în modul doar-promotor, dar dacă promotorul direct devine inactiv sau are autentificarea dezactivată, gestionarea urcă pe lanțul de acordare până la un administrator eligibil. Dacă acesta nu există, controlul rămâne la super administratori.",
"install.install_btn_confirm": "Instalați Gitea",
"install.install_btn_confirm": "Instalează %s",
"install.test_git_failed": "Nu s-a putut testa comanda „git”: %v",
"install.sqlite3_not_available": "Această versiune Gitea nu acceptă SQLite3. Te rugăm să descărci versiunea oficială binară de la %s (nu versiunea „gobuild”).",
"install.invalid_db_setting": "Setările bazei de date nu sunt valide: %v",
+85 -3
View File
@@ -16,6 +16,7 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"slices"
"strconv"
@@ -74,6 +75,8 @@ var installBrandingAssetSpecs = []installBrandingAssetSpec{
{FormField: "favicon_png", TargetName: "favicon.png", LabelKey: "install.branding.favicon_png", MimeType: "image/png"},
}
var installSMTPUnquotedDisplayNamePattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9 ._-]*$`)
// getSupportedDbTypeNames returns a slice for supported database types and names. The slice is used to keep the order
func getSupportedDbTypeNames() (dbTypeNames []map[string]string) {
for _, t := range setting.SupportedDatabaseTypes {
@@ -339,6 +342,7 @@ func newInstallFormFromSettings() (forms.InstallForm, string) {
form.SMTPUser = setting.MailService.User
form.SMTPPasswd = setting.MailService.Passwd
}
populateInstallSMTPFromFields(&form)
form.RegisterConfirm = setting.Service.RegisterEmailConfirm
form.RegisterManualConfirm = setting.Service.RegisterManualConfirm
form.MailNotify = setting.Service.EnableNotifyMail
@@ -372,6 +376,7 @@ func renderInstallPage(ctx *context.Context, form *forms.InstallForm, curDBType
}
normalizeInstallRegistrationOptions(form)
ctx.Data["CurDbType"] = curDBType
ctx.Data["InstallerSiteName"] = installMailerDisplayName(form.AppName)
middleware.AssignForm(form, ctx.Data)
ctx.HTML(http.StatusOK, tplInstall)
}
@@ -447,6 +452,7 @@ func populateInstallFormFromConfig(form *forms.InstallForm, cfg setting.ConfigPr
form.SMTPUser = setting.ConfigSectionKeyString(mailerSec, "USER", form.SMTPUser)
form.SMTPPasswd = setting.ConfigSectionKeyString(mailerSec, "PASSWD", form.SMTPPasswd)
}
populateInstallSMTPFromFields(form)
form.RegisterConfirm = setting.ConfigSectionKeyBool(serviceSec, "REGISTER_EMAIL_CONFIRM", form.RegisterConfirm)
form.RegisterManualConfirm = setting.ConfigSectionKeyBool(serviceSec, "REGISTER_MANUAL_CONFIRM", form.RegisterManualConfirm)
@@ -633,6 +639,73 @@ func ImportAppINI(ctx *context.Context) {
renderInstallPage(ctx, &form, curDBType)
}
func installMailerDisplayName(appName string) string {
appName = strings.TrimSpace(appName)
if appName == "" {
return "Gitea"
}
if siteName, _, found := strings.Cut(appName, ":"); found {
siteName = strings.TrimSpace(siteName)
if siteName != "" {
return siteName
}
}
if parts := strings.Fields(appName); len(parts) > 0 {
return parts[0]
}
return appName
}
func composeInstallSMTPFrom(appName, fromName, fromAddress string) (string, error) {
fromAddress = strings.TrimSpace(fromAddress)
if fromAddress == "" {
return "", errors.New("missing from address")
}
if _, err := mail.ParseAddress(fromAddress); err != nil {
return "", err
}
fromName = strings.TrimSpace(fromName)
if fromName == "" {
fromName = installMailerDisplayName(appName)
}
if installSMTPUnquotedDisplayNamePattern.MatchString(fromName) {
return fromName + " <" + fromAddress + ">", nil
}
return (&mail.Address{
Name: fromName,
Address: fromAddress,
}).String(), nil
}
func populateInstallSMTPFromFields(form *forms.InstallForm) {
form.SMTPFromName = strings.TrimSpace(form.SMTPFromName)
form.SMTPFromAddress = strings.TrimSpace(form.SMTPFromAddress)
if form.SMTPFrom != "" {
if parsedFrom, err := mail.ParseAddress(form.SMTPFrom); err == nil {
if form.SMTPFromName == "" {
form.SMTPFromName = parsedFrom.Name
}
if form.SMTPFromAddress == "" {
form.SMTPFromAddress = parsedFrom.Address
}
} else if form.SMTPFromAddress == "" {
form.SMTPFromAddress = strings.TrimSpace(form.SMTPFrom)
}
}
if form.SMTPFromName == "" {
form.SMTPFromName = installMailerDisplayName(form.AppName)
}
}
// TestMail checks the mail settings entered on the install page.
func TestMail(ctx *context.Context) {
email := strings.TrimSpace(ctx.FormString("test_mail_email"))
@@ -666,7 +739,9 @@ func TestMail(ctx *context.Context) {
func buildInstallTestMailService(ctx *context.Context) (*setting.Mailer, error) {
smtpAddr := strings.TrimSpace(ctx.FormString("smtp_addr"))
smtpPort := strings.TrimSpace(ctx.FormString("smtp_port"))
smtpFrom := strings.TrimSpace(ctx.FormString("smtp_from"))
appName := strings.TrimSpace(ctx.FormString("app_name"))
smtpFromName := strings.TrimSpace(ctx.FormString("smtp_from_name"))
smtpFromAddress := strings.TrimSpace(ctx.FormString("smtp_from_address"))
if smtpAddr == "" {
return nil, errors.New(string(ctx.Tr("install.test_mail_missing_host")))
@@ -683,6 +758,11 @@ func buildInstallTestMailService(ctx *context.Context) (*setting.Mailer, error)
return nil, errors.New(string(ctx.Tr("form.email_invalid")))
}
smtpFrom, err := composeInstallSMTPFrom(appName, smtpFromName, smtpFromAddress)
if err != nil {
return nil, errors.New(string(ctx.Tr("install.smtp_from_invalid")))
}
parsedFrom, err := mail.ParseAddress(smtpFrom)
if err != nil {
return nil, errors.New(string(ctx.Tr("install.smtp_from_invalid")))
@@ -701,7 +781,7 @@ func buildInstallTestMailService(ctx *context.Context) (*setting.Mailer, error)
}
return &setting.Mailer{
Name: setting.AppName,
Name: installMailerDisplayName(appName),
From: smtpFrom,
FromName: parsedFrom.Name,
FromEmail: parsedFrom.Address,
@@ -822,6 +902,7 @@ func SubmitInstall(ctx *context.Context) {
}
form.DefaultLanguage = strings.TrimSpace(form.DefaultLanguage)
normalizeInstallRegistrationOptions(&form)
populateInstallSMTPFromFields(&form)
if form.DefaultLanguage == "" {
form.DefaultLanguage = resolveInstallDefaultLanguage("")
@@ -999,7 +1080,8 @@ func SubmitInstall(ctx *context.Context) {
}
if len(strings.TrimSpace(form.SMTPAddr)) > 0 {
if _, err := mail.ParseAddress(form.SMTPFrom); err != nil {
form.SMTPFrom, err = composeInstallSMTPFrom(form.AppName, form.SMTPFromName, form.SMTPFromAddress)
if err != nil {
ctx.RenderWithErrDeprecated(ctx.Tr("install.smtp_from_invalid"), tplInstall, &form)
return
}
+14
View File
@@ -56,6 +56,18 @@ func TestReorderInstallLanguages(t *testing.T) {
assert.Equal(t, []string{"en-US", "de-DE", "fr-FR"}, reorderInstallLanguages("invalid"))
}
func TestInstallMailerDisplayName(t *testing.T) {
assert.Equal(t, "gitSafe", installMailerDisplayName("gitSafe: for your code"))
assert.Equal(t, "gitSafe", installMailerDisplayName("gitSafe for your code"))
assert.Equal(t, "Gitea", installMailerDisplayName(""))
}
func TestComposeInstallSMTPFrom(t *testing.T) {
from, err := composeInstallSMTPFrom("gitSafe for your code", "", "noreply@example.com")
require.NoError(t, err)
assert.Equal(t, "gitSafe <noreply@example.com>", from)
}
func TestPopulateInstallFormFromConfig(t *testing.T) {
defer test.MockVariableValue(&setting.SupportedDatabaseTypes, []string{"mysql", "postgres", "sqlite3"})()
defer test.MockVariableValue(&setting.Langs, []string{"en-US", "de-DE", "fr-FR"})()
@@ -150,6 +162,8 @@ LANGS = de-DE,en-US
assert.Equal(t, "smtp.example.com", form.SMTPAddr)
assert.Equal(t, "587", form.SMTPPort)
assert.Equal(t, "Gitea <gitea@example.com>", form.SMTPFrom)
assert.Equal(t, "Gitea", form.SMTPFromName)
assert.Equal(t, "gitea@example.com", form.SMTPFromAddress)
assert.Equal(t, "smtp-user", form.SMTPUser)
assert.Equal(t, "smtp-pass", form.SMTPPasswd)
assert.Equal(t, "de-DE", form.DefaultLanguage)
+2
View File
@@ -41,6 +41,8 @@ type InstallForm struct {
SMTPAddr string
SMTPPort string
SMTPFrom string
SMTPFromName string
SMTPFromAddress string
SMTPUser string `binding:"OmitEmpty;MaxSize(254)" locale:"install.mailer_user"`
SMTPPasswd string
ImportSensitiveSecrets bool `form:"import_sensitive_secrets"`
+21 -1
View File
@@ -58,7 +58,27 @@ func SendTestMailWith(email string, mailService *setting.Mailer) error {
setting.MailService = previousMailService
}()
return sender_service.Send(newTestMailSender(mailService.Protocol), sender_service.NewMessage(email, "Gitea Test Email!", "Gitea Test Email!"))
subject := fmt.Sprintf("%s Test Email!", testMailAppName(mailService.Name))
return sender_service.Send(newTestMailSender(mailService.Protocol), sender_service.NewMessage(email, subject, subject))
}
func testMailAppName(name string) string {
name = strings.TrimSpace(name)
if name == "" {
name = strings.TrimSpace(setting.AppName)
}
if siteName, _, found := strings.Cut(name, ":"); found {
siteName = strings.TrimSpace(siteName)
if siteName != "" {
return siteName
}
} else if parts := strings.Fields(name); len(parts) > 0 {
return parts[0]
}
if name == "" {
return "Gitea"
}
return name
}
func newTestMailSender(protocol string) sender_service.Sender {
+12
View File
@@ -508,6 +508,18 @@ func TestFromDisplayName(t *testing.T) {
})
}
func TestTestMailAppName(t *testing.T) {
oldAppName := setting.AppName
setting.AppName = "Gitea: Git with a cup of tea"
defer func() {
setting.AppName = oldAppName
}()
assert.Equal(t, "gitSafe", testMailAppName("gitSafe: for your code"))
assert.Equal(t, "gitSafe", testMailAppName("gitSafe for your code"))
assert.Equal(t, "Gitea", testMailAppName(""))
}
func TestEmbedBase64Images(t *testing.T) {
user, repo, issue, att1, att2 := prepareMailerBase64Test(t)
// comment := &mailComment{Issue: issue, Doer: user}
+52 -4
View File
@@ -246,8 +246,15 @@
<input id="smtp_port" name="smtp_port" value="{{.smtp_port}}">
</div>
<div class="inline field {{if .Err_SMTPFrom}}error{{end}}">
<label for="smtp_from">{{ctx.Locale.Tr "install.smtp_from"}}</label>
<input id="smtp_from" name="smtp_from" value="{{.smtp_from}}">
<label for="smtp_from_name">{{ctx.Locale.Tr "install.smtp_from"}}</label>
<div class="tw-inline-flex tw-items-center tw-gap-2 tw-flex-wrap" style="width: 60%;">
<div class="ui input" style="flex: 1 1 200px;">
<input id="smtp_from_name" name="smtp_from_name" value="{{.smtp_from_name}}" placeholder="{{ctx.Locale.Tr "install.smtp_from_name_placeholder"}}">
</div>
<div class="ui input" style="flex: 1 1 240px;">
<input id="smtp_from_address" name="smtp_from_address" type="email" value="{{.smtp_from_address}}" placeholder="{{ctx.Locale.Tr "install.smtp_from_address_placeholder"}}">
</div>
</div>
<span class="help">{{ctx.Locale.TrString "install.smtp_from_helper"}}{{/* it contains lt/gt chars*/}}</span>
</div>
<div class="inline field {{if .Err_SMTPUser}}error{{end}}">
@@ -264,7 +271,7 @@
<label for="test_mail_email">{{ctx.Locale.Tr "install.test_mail_to"}}</label>
<div class="tw-inline-flex tw-items-center tw-gap-2 tw-flex-wrap" style="width: 60%;">
<div class="ui small input">
<input id="test_mail_email" name="test_mail_email" type="email" placeholder="{{ctx.Locale.Tr "admin.config.test_email_placeholder"}}" size="29">
<input id="test_mail_email" name="test_mail_email" type="email" placeholder="{{ctx.Locale.Tr "admin.config.test_email_placeholder"}}" size="40">
</div>
<button class="ui tiny js-install-test-mail-button button" data-mail-action-ui="inline-v2" data-action="{{AppSubUrl}}/test_mail" type="button" style="background: var(--color-primary); color: var(--color-primary-contrast); border-color: var(--color-primary);">{{ctx.Locale.Tr "admin.config.send_test_mail_submit"}}</button>
<span class="js-install-test-mail-message"></span>
@@ -489,7 +496,10 @@
{{ctx.Locale.Tr "install.config_write_file_prompt" $filePath}}
</div>
<div class="tw-mt-4 tw-mb-2 tw-text-center">
<button class="ui primary button">{{ctx.Locale.Tr "install.install_btn_confirm"}}</button>
<button
class="ui primary button js-install-confirm-button"
data-install-label-template="{{ctx.Locale.Tr "install.install_btn_confirm" "__SITE_NAME__"}}"
>{{ctx.Locale.Tr "install.install_btn_confirm" .InstallerSiteName}}</button>
</div>
</div>
</form>
@@ -612,6 +622,44 @@
}
};
const deriveInstallMailerName = (appName) => {
const trimmed = appName.trim();
if (!trimmed) return 'Gitea';
const colonIndex = trimmed.indexOf(':');
if (colonIndex >= 0) {
const siteName = trimmed.slice(0, colonIndex).trim();
return siteName || 'Gitea';
}
return trimmed.split(/\s+/, 1)[0] || 'Gitea';
};
const installForm = document.querySelector('.js-install-form');
const appNameInput = installForm?.querySelector('#app_name');
const smtpFromNameInput = installForm?.querySelector('#smtp_from_name');
const installConfirmButton = installForm?.querySelector('.js-install-confirm-button');
if (appNameInput instanceof HTMLInputElement && smtpFromNameInput instanceof HTMLInputElement) {
let lastAutoMailerName = smtpFromNameInput.value.trim() || deriveInstallMailerName(appNameInput.value);
if (!smtpFromNameInput.value.trim()) {
smtpFromNameInput.value = lastAutoMailerName;
}
if (installConfirmButton instanceof HTMLButtonElement) {
installConfirmButton.textContent = installConfirmButton.dataset.installLabelTemplate.replace('__SITE_NAME__', lastAutoMailerName);
}
appNameInput.addEventListener('input', () => {
const currentValue = smtpFromNameInput.value.trim();
if (currentValue && currentValue !== lastAutoMailerName) return;
lastAutoMailerName = deriveInstallMailerName(appNameInput.value);
smtpFromNameInput.value = lastAutoMailerName;
if (installConfirmButton instanceof HTMLButtonElement) {
installConfirmButton.textContent = installConfirmButton.dataset.installLabelTemplate.replace('__SITE_NAME__', lastAutoMailerName);
}
});
} else if (appNameInput instanceof HTMLInputElement && installConfirmButton instanceof HTMLButtonElement) {
const siteName = deriveInstallMailerName(appNameInput.value);
installConfirmButton.textContent = installConfirmButton.dataset.installLabelTemplate.replace('__SITE_NAME__', siteName);
}
for (const button of document.querySelectorAll('.js-install-test-mail-button')) {
button.addEventListener('click', async () => {
const form = button.closest('.js-install-form');