Modified - [install] [reinstall] [modal] [app-ini-import] [secrets] Reworked the installer recovery flow around existing databases and imported app.ini secrets.

- 1 - Compared to the previous project state, the installer no longer mixes the existing-database reinstall warning into the main page body and instead runs that confirmation flow in a dedicated modal with the warning content grouped at the top, a `Back` exit path, and an `Install %s` action that stays disabled until all required confirmations are checked; in the same recovery-oriented flow, importing `app.ini` now comes back with `Import sensitive secrets from app.ini` enabled by default, preserves the imported secret values in the form, and keeps the derived site-name label for the install action stable during validation rerenders.
This commit is contained in:
2026-05-24 01:15:41 +03:00
parent 66093c1564
commit f293572182
6 changed files with 169 additions and 31 deletions
+3
View File
@@ -873,3 +873,6 @@ History search guidance:
180 - [2026-05-23 05:05:14] - v1.27.0-dev-192-g26e30f5ac2 - Type: Added - [org] [settings] [repository] [adopt] Added an organization-scoped pre-existing repository adoption flow under organization settings.
- 1 - I added `routers/web/org/setting_repos.go`, `templates/org/settings/repos.tmpl`, `templates/org/settings/navbar.tmpl`, and the corresponding route wiring in `routers/web/web.go`, then aligned the user and organization repository settings pages so both now separate normal adopted repositories from `Unadopted Repositories`; the final organization implementation correctly resolves adopted repos by `OwnerID`, preventing newly adopted organization repositories from lingering in the unadopted list.
181 - [2026-05-23 21:45:53] - v1.27.0-dev-193-g66093c1564 - Type: Modified - [install] [reinstall] [modal] [app-ini-import] [secrets] Reworked the installer recovery flow around existing databases and imported `app.ini` secrets.
- 1 - Compared to the previous project state, the installer no longer mixes the existing-database reinstall warning into the main page body and instead runs that confirmation flow in a dedicated modal with the warning content grouped at the top, a `Back` exit path, and an `Install %s` action that stays disabled until all required confirmations are checked; in the same recovery-oriented flow, importing `app.ini` now comes back with `Import sensitive secrets from app.ini` enabled by default, preserves the imported secret values in the form, and keeps the derived site-name label for the install action stable during validation rerenders.
+2 -1
View File
@@ -651,7 +651,7 @@ func ImportAppINI(ctx *context.Context) {
}
form, curDBType := newInstallFormFromSettings()
form.ImportSensitiveSecrets = ctx.FormBool("import_sensitive_secrets")
form.ImportSensitiveSecrets = true // edit/add - by petru @ codex
form.ImportedAppINI = true // edit/add - by petru @ codex
cfg, err := readImportedInstallConfig(ctx)
if err != nil {
@@ -942,6 +942,7 @@ func SubmitInstall(ctx *context.Context) {
form.DefaultLanguage = strings.TrimSpace(form.DefaultLanguage)
normalizeInstallRegistrationOptions(&form)
populateInstallSMTPFromFields(&form)
ctx.Data["InstallerSiteName"] = installMailerDisplayName(form.AppName) // edit/add - by petru @ codex
if form.DefaultLanguage == "" {
form.DefaultLanguage = resolveInstallDefaultLanguage("")
+37
View File
@@ -361,6 +361,43 @@ JWT_SECRET = oauth-secret
assert.Contains(t, w.Body.String(), `name="imported_o_auth2_jwt_secret" value="oauth-secret"`)
}
// start edit/add - by petru @ codex
func TestImportAppINIDefaultsSensitiveSecretsToEnabled(t *testing.T) {
defer test.MockVariableValue(&setting.InstallLock, false)()
var body bytes.Buffer
writer := multipart.NewWriter(&body)
fileWriter, err := writer.CreateFormFile("app_ini_file", "app.ini")
require.NoError(t, err)
_, err = fileWriter.Write([]byte(`
[server]
LFS_JWT_SECRET = lfs-secret
[security]
INTERNAL_TOKEN = internal-secret
[oauth2]
JWT_SECRET = oauth-secret
`))
require.NoError(t, err)
require.NoError(t, writer.Close())
r := Routes()
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodPost, "/import_app_ini", &body)
req.Header.Set("Content-Type", writer.FormDataContentType())
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Body.String(), `id="import_sensitive_secrets" name="import_sensitive_secrets" type="checkbox" checked`)
assert.Contains(t, w.Body.String(), `name="imported_lfs_jwt_secret" value="lfs-secret"`)
assert.Contains(t, w.Body.String(), `name="imported_internal_token" value="internal-secret"`)
assert.Contains(t, w.Body.String(), `name="imported_o_auth2_jwt_secret" value="oauth-secret"`)
}
// end edit/add - by petru @ codex
func TestApplyInstallSensitiveSecretsToConfigPersistsImportedValues(t *testing.T) {
tmpDir := t.TempDir()
configPath := filepath.Join(tmpDir, "app.ini")
+72 -30
View File
@@ -6,11 +6,11 @@
{{ctx.Locale.Tr "install.title"}}
</h3>
<div class="ui attached segment">
{{template "base/alert" .}}
{{if not .Err_DbInstalledBefore}}{{template "base/alert" .}}{{end}} <!-- edit/add - by petru @ codex -->
<p>{{ctx.Locale.Tr "install.docker_helper" "https://docs.gitea.com/installation/install-with-docker"}}</p>
<form class="ui form js-install-form" action="{{AppSubUrl}}/" method="post" enctype="multipart/form-data">
<form id="install-form" class="ui form js-install-form" action="{{AppSubUrl}}/" method="post" enctype="multipart/form-data"> <!-- edit/add - by petru @ codex -->
<h4 class="ui dividing header">{{ctx.Locale.Tr "install.import_app_ini_title"}}</h4>
<p>{{ctx.Locale.Tr "install.import_app_ini_desc"}}</p>
<div class="inline field">
@@ -97,30 +97,6 @@
</div>
</div>
{{if .Err_DbInstalledBefore}}
<div>
<p class="reinstall-message">{{ctx.Locale.Tr "install.reinstall_confirm_message"}}</p>
<div class="reinstall-confirm">
<div class="ui checkbox">
<label>{{ctx.Locale.Tr "install.reinstall_confirm_check_1"}}</label>
<input name="reinstall_confirm_first" type="checkbox">
</div>
</div>
<div class="reinstall-confirm">
<div class="ui checkbox">
<label>{{ctx.Locale.Tr "install.reinstall_confirm_check_2"}}</label>
<input name="reinstall_confirm_second" type="checkbox">
</div>
</div>
<div class="reinstall-confirm">
<div class="ui checkbox">
<label>{{ctx.Locale.Tr "install.reinstall_confirm_check_3"}}</label>
<input name="reinstall_confirm_third" type="checkbox">
</div>
</div>
</div>
{{end}}
<!-- General Settings -->
<h4 class="ui dividing header">{{ctx.Locale.Tr "install.general_title"}}</h4>
<div class="inline required field {{if .Err_AppName}}error{{end}}">
@@ -529,18 +505,58 @@
{{$filePath := HTMLFormat `<span class="ui label">%s</span> <button class="btn interact-fg" data-clipboard-text="%s">%s</button>` .CustomConfFile .CustomConfFile $copyBtn}}
{{ctx.Locale.Tr "install.config_write_file_prompt" $filePath}}
</div>
{{if not .Err_DbInstalledBefore}} <!-- edit/add - by petru @ codex -->
<div class="tw-mt-4 tw-mb-2 tw-text-center">
<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>
{{end}} <!-- edit/add - by petru @ codex -->
</div>
</form>
</div>
</div>
</div>
</div>
{{if .Err_DbInstalledBefore}}
<!-- start edit/add - by petru @ codex -->
<div class="ui small modal install-reinstall-confirm-modal js-install-reinstall-modal">
<div class="ui negative message install-reinstall-alert">{{ctx.Locale.Tr "install.reinstall_error"}}</div>
<div class="content">
<p class="reinstall-message">{{ctx.Locale.Tr "install.reinstall_confirm_message"}}</p>
<div class="reinstall-confirm">
<div class="ui checkbox">
<input id="reinstall_confirm_first" name="reinstall_confirm_first" type="checkbox" form="install-form">
<label for="reinstall_confirm_first">{{ctx.Locale.Tr "install.reinstall_confirm_check_1"}}</label>
</div>
</div>
<div class="reinstall-confirm">
<div class="ui checkbox">
<input id="reinstall_confirm_second" name="reinstall_confirm_second" type="checkbox" form="install-form">
<label for="reinstall_confirm_second">{{ctx.Locale.Tr "install.reinstall_confirm_check_2"}}</label>
</div>
</div>
<div class="reinstall-confirm">
<div class="ui checkbox">
<input id="reinstall_confirm_third" name="reinstall_confirm_third" type="checkbox" form="install-form">
<label for="reinstall_confirm_third">{{ctx.Locale.Tr "install.reinstall_confirm_check_3"}}</label>
</div>
</div>
</div>
<div class="actions">
<a class="ui button cancel" href="{{AppSubUrl}}/">{{ctx.Locale.Tr "go_back"}}</a>
<button
type="submit"
form="install-form"
class="ui primary button js-install-confirm-button"
data-install-label-template="{{ctx.Locale.Tr "install.install_btn_confirm" "__SITE_NAME__"}}"
disabled
>{{ctx.Locale.Tr "install.install_btn_confirm" .InstallerSiteName}}</button>
</div>
</div>
<!-- end edit/add - by petru @ codex -->
{{end}}
<div class="install-language-balloon" id="install-language-balloon" role="status" aria-live="polite">
{{ctx.Locale.Tr "install.language_balloon"}}
</div>
@@ -670,7 +686,7 @@
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');
const installConfirmButtons = installForm ? installForm.querySelectorAll('.js-install-confirm-button') : [];
if (appNameInput instanceof HTMLInputElement && smtpFromNameInput instanceof HTMLInputElement) {
let lastAutoMailerName = smtpFromNameInput.value.trim() || deriveInstallMailerName(appNameInput.value);
const syncInstallBranding = () => {
@@ -680,7 +696,8 @@
smtpFromNameInput.value = siteName;
}
lastAutoMailerName = siteName;
if (installConfirmButton instanceof HTMLButtonElement) {
for (const installConfirmButton of installConfirmButtons) {
if (!(installConfirmButton instanceof HTMLButtonElement)) continue;
installConfirmButton.textContent = installConfirmButton.dataset.installLabelTemplate.replace('__SITE_NAME__', siteName);
}
};
@@ -688,10 +705,13 @@
syncInstallBranding();
appNameInput.addEventListener('input', syncInstallBranding);
appNameInput.addEventListener('change', syncInstallBranding);
} else if (appNameInput instanceof HTMLInputElement && installConfirmButton instanceof HTMLButtonElement) {
} else if (appNameInput instanceof HTMLInputElement && installConfirmButtons.length > 0) {
const syncInstallButton = () => {
const siteName = deriveInstallMailerName(appNameInput.value);
installConfirmButton.textContent = installConfirmButton.dataset.installLabelTemplate.replace('__SITE_NAME__', siteName);
for (const installConfirmButton of installConfirmButtons) {
if (!(installConfirmButton instanceof HTMLButtonElement)) continue;
installConfirmButton.textContent = installConfirmButton.dataset.installLabelTemplate.replace('__SITE_NAME__', siteName);
}
};
syncInstallButton();
appNameInput.addEventListener('input', syncInstallButton);
@@ -742,6 +762,28 @@
document.addEventListener('DOMContentLoaded', () => {
dismissInstallSuccessFlash();
{{if .Err_DbInstalledBefore}}
// start edit/add - by petru @ codex
const reinstallModal = document.querySelector('.js-install-reinstall-modal');
if (reinstallModal) {
const reinstallConfirmInputs = reinstallModal.querySelectorAll('input[name^="reinstall_confirm_"]');
const reinstallConfirmButton = reinstallModal.querySelector('.js-install-confirm-button');
const syncReinstallConfirmButton = () => {
if (!(reinstallConfirmButton instanceof HTMLButtonElement)) return;
reinstallConfirmButton.disabled = [...reinstallConfirmInputs].some((input) => !(input instanceof HTMLInputElement) || !input.checked);
};
for (const input of reinstallConfirmInputs) {
input.addEventListener('change', syncReinstallConfirmButton);
}
syncReinstallConfirmButton();
window.$(reinstallModal).modal({
autofocus: false,
closable: false,
}).modal('show');
}
// end edit/add - by petru @ codex
{{end}}
const installLanguageBalloon = document.querySelector('#install-language-balloon');
const footerLanguageSelector = document.querySelector('#footer-language-selector');
if (!installLanguageBalloon || !footerLanguageSelector) return;
+51
View File
@@ -70,6 +70,57 @@
margin: 10px auto;
}
/* start edit/add - by petru @ codex */
.page-content.install .install-reinstall-confirm-modal .content {
padding-top: 1.25rem;
}
.ui.negative.message.install-reinstall-alert {
font-size: 19px;
}
.page-content.install .install-reinstall-confirm-modal .install-reinstall-alert {
margin-bottom: 1rem;
text-align: center;
font-weight: var(--font-weight-semibold);
}
.page-content.install .install-reinstall-confirm-modal .reinstall-message,
.page-content.install .install-reinstall-confirm-modal .reinstall-confirm {
width: 100%;
margin-left: 0;
margin-right: 0;
}
.page-content.install .install-reinstall-confirm-modal .reinstall-message {
margin-top: 0;
margin-bottom: 1rem;
color: var(--color-error-text);
font-weight: var(--font-weight-semibold);
}
.page-content.install .install-reinstall-confirm-modal .reinstall-confirm {
padding: 0;
}
.page-content.install .install-reinstall-confirm-modal .reinstall-confirm + .reinstall-confirm {
margin-top: 0.6rem;
}
.page-content.install .install-reinstall-confirm-modal .reinstall-confirm .ui.checkbox label {
display: block;
padding-left: 1.5rem;
line-height: 1.5;
font-weight: var(--font-weight-normal);
}
.page-content.install .install-reinstall-confirm-modal .actions {
display: flex;
justify-content: flex-end;
gap: 0.75rem;
}
/* end edit/add - by petru @ codex */
.install-language-balloon {
position: fixed;
right: 10.5rem;
+4
View File
@@ -101,6 +101,10 @@ input[type="checkbox"]:indeterminate::before {
margin-left: 20px;
}
.reinstall-confirm > .ui.checkbox label {
margin-bottom: 8px;
}
.ui.checkbox + label {
vertical-align: middle;
}