471cfdd161
- 1 - Add: Gitea now creates timestamped database backup bundles under `[backup].PATH`, exposes the backup schedule in the installer, and surfaces the `database_backup` cron task in admin monitoring. - 2 - Add: installed instances now use `.gitea-installed` and `.gitea-recovery.ini` to enter email-gated recovery instead of falling back to public install mode when configuration or database access is broken. - 3 - Mod: the installer recovery flow now covers backup-bundle restore, bundled or manual `app.ini` reuse, uploaded SQL/GZ database restores, and repository-filesystem recovery with source-specific validation, confirmations, and preserved launcher state. - 4 - Fix: recovery now restores bundled `app.ini` snapshots when needed, discovers backup bundles from both the active backup path and persisted `.gitea-recovery.ini` path, and preserves SMTP and other rebuilt settings correctly when `app.ini` is missing or incomplete. - 5 - Fix: recovery validation and restore handling now accept either a selected backup bundle or an uploaded SQL/GZ dump, keep sensitive secrets and existing `LFS_JWT_SECRET` when appropriate, clear SQLite restore targets before import, and complete the post-install handoff without redirect loops. - 6 - Mod: fresh installs now default recovery email authorization to enabled with first-admin fallback, and the install/recovery UI, styling, and EN/RO wording were refined to match the final launcher behavior. Co-Authored-By: petru @ codex (GPT-5) <codex@openai.com> (cherry picked from commit 9879caf2292691b0cb521d12e6fee924b066bae2)
1193 lines
40 KiB
Go
1193 lines
40 KiB
Go
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package install
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"code.gitea.io/gitea/models/db"
|
|
"code.gitea.io/gitea/models/unittest"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
"code.gitea.io/gitea/modules/test"
|
|
"code.gitea.io/gitea/services/forms"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestRoutes(t *testing.T) {
|
|
defer test.MockVariableValue(&setting.InstallLock, false)()
|
|
|
|
r := Routes()
|
|
assert.NotNil(t, r)
|
|
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, 200, w.Code)
|
|
assert.Contains(t, w.Body.String(), `class="page-content install`)
|
|
assert.Contains(t, w.Body.String(), `name="default_language"`)
|
|
assert.Contains(t, w.Body.String(), `name="app_ini_file"`)
|
|
|
|
w = httptest.NewRecorder()
|
|
req = httptest.NewRequest(http.MethodGet, "/no-such", nil)
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, 404, w.Code)
|
|
|
|
w = httptest.NewRecorder()
|
|
req = httptest.NewRequest(http.MethodGet, "/assets/img/gitea.svg", nil)
|
|
r.ServeHTTP(w, req)
|
|
assert.Equal(t, 200, w.Code)
|
|
}
|
|
|
|
func TestReorderInstallLanguages(t *testing.T) {
|
|
defer test.MockVariableValue(&setting.Langs, []string{"en-US", "de-DE", "fr-FR"})()
|
|
|
|
assert.Equal(t, "en-US", resolveInstallDefaultLanguage(""))
|
|
assert.Equal(t, "en-US", resolveInstallDefaultLanguage("invalid"))
|
|
assert.Equal(t, "fr-FR", resolveInstallDefaultLanguage("fr-FR"))
|
|
assert.Equal(t, []string{"fr-FR", "en-US", "de-DE"}, reorderInstallLanguages("fr-FR"))
|
|
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(""))
|
|
}
|
|
|
|
// start edit/add - by petru @ codex
|
|
func TestIsRecoveryRequestAcceptsQueryFlag(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodGet, "/?recovery=1", nil)
|
|
assert.True(t, isRecoveryRequest(req))
|
|
}
|
|
|
|
func TestRecoveryPageShowsBackupDiscoveryError(t *testing.T) {
|
|
// start edit/add - by petru @ codex
|
|
defer test.MockVariableValue(&setting.InstallLock, false)()
|
|
|
|
tmpDir := t.TempDir()
|
|
customConfDir := filepath.Join(tmpDir, "custom", "conf")
|
|
require.NoError(t, os.MkdirAll(customConfDir, 0o755))
|
|
|
|
invalidBackupPath := filepath.Join(tmpDir, "invalid-backup-path")
|
|
require.NoError(t, os.WriteFile(invalidBackupPath, []byte("not-a-directory"), 0o644))
|
|
|
|
oldCustomConf := setting.CustomConf
|
|
defer func() {
|
|
setting.CustomConf = oldCustomConf
|
|
}()
|
|
setting.CustomConf = filepath.Join(customConfDir, "app.ini")
|
|
|
|
require.NoError(t, setting.SaveRecoveryConfig(&setting.RecoveryConfig{
|
|
Enabled: true,
|
|
AllowedEmails: "admin@example.com",
|
|
TokenSecret: "secret",
|
|
TokenTTLMin: 15,
|
|
BackupPath: invalidBackupPath,
|
|
}))
|
|
|
|
r := Routes()
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodGet, "/?recovery=1", nil)
|
|
r.ServeHTTP(w, WithRecoveryRequest(req))
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
assert.Contains(t, w.Body.String(), "install.recovery_database_backup_discovery_failed")
|
|
// end edit/add - by petru @ codex
|
|
}
|
|
|
|
func TestResolveInstallPageSiteNameKeepsImportedAppName(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
oldCustomConf := setting.CustomConf
|
|
defer func() {
|
|
setting.CustomConf = oldCustomConf
|
|
}()
|
|
setting.CustomConf = filepath.Join(tmpDir, "custom", "conf", "app.ini")
|
|
|
|
recoveryCfg := &setting.RecoveryConfig{
|
|
Enabled: true,
|
|
AllowedEmails: "admin@example.com",
|
|
TokenSecret: "secret",
|
|
TokenTTLMin: 15,
|
|
SMTPFrom: "gitSafe <noreply@example.com>",
|
|
}
|
|
require.NoError(t, setting.SaveRecoveryConfig(recoveryCfg))
|
|
|
|
form := &forms.InstallForm{
|
|
AppName: "Imported Safe: for your code",
|
|
ImportedAppINI: true,
|
|
}
|
|
|
|
assert.Equal(t, "Imported Safe", resolveInstallPageSiteName(form, true))
|
|
assert.Equal(t, "Imported Safe: for your code", form.AppName)
|
|
}
|
|
|
|
func TestResolveInstallPageSiteNameUsesRecoveryFallbackForGenericAppName(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
oldCustomConf := setting.CustomConf
|
|
defer func() {
|
|
setting.CustomConf = oldCustomConf
|
|
}()
|
|
setting.CustomConf = filepath.Join(tmpDir, "custom", "conf", "app.ini")
|
|
|
|
recoveryCfg := &setting.RecoveryConfig{
|
|
Enabled: true,
|
|
AllowedEmails: "admin@example.com",
|
|
TokenSecret: "secret",
|
|
TokenTTLMin: 15,
|
|
SMTPFrom: "gitSafe <noreply@example.com>",
|
|
}
|
|
require.NoError(t, setting.SaveRecoveryConfig(recoveryCfg))
|
|
|
|
form := &forms.InstallForm{AppName: "Gitea: Git with a cup of tea"}
|
|
|
|
assert.Equal(t, "gitSafe", resolveInstallPageSiteName(form, true))
|
|
assert.Equal(t, "gitSafe", form.AppName)
|
|
}
|
|
|
|
func TestImportedDBTypeKeepsImportedSQLite3Value(t *testing.T) {
|
|
assert.Equal(t, "sqlite3", importedDBType("sqlite3", "mysql"))
|
|
assert.Equal(t, "sqlite3", importedDBType("sqlite", "mysql"))
|
|
assert.Equal(t, "postgres", importedDBType("postgresql", "mysql"))
|
|
}
|
|
|
|
func TestApplyBackupAppINIToInstallForm(t *testing.T) {
|
|
// start edit/add - by petru @ codex
|
|
cfg, err := setting.NewConfigProviderFromData(`
|
|
APP_NAME = Restored Safe: for your code
|
|
|
|
[database]
|
|
DB_TYPE = sqlite3
|
|
PATH = /srv/gitea/data/gitea.db
|
|
|
|
[server]
|
|
ROOT_URL = http://localhost:3000/
|
|
|
|
[mailer]
|
|
ENABLED = true
|
|
SMTP_ADDR = smtp.example.com
|
|
SMTP_PORT = 587
|
|
FROM = Restored Safe <mail@example.com>
|
|
USER = smtp-user
|
|
PASSWD = smtp-pass
|
|
|
|
[security]
|
|
INTERNAL_TOKEN = restored-internal
|
|
`)
|
|
require.NoError(t, err)
|
|
|
|
form := &forms.InstallForm{
|
|
DbType: "mysql",
|
|
BackupPath: "/srv/gitea/backups/db",
|
|
BackupRestoreID: "20260524-010101.000000001",
|
|
RecoveryMode: recoveryModeDatabaseBackup,
|
|
ImportSensitiveSecrets: false,
|
|
}
|
|
|
|
applyBackupAppINIToInstallForm(form, cfg)
|
|
|
|
assert.Equal(t, "Restored Safe: for your code", form.AppName)
|
|
assert.Equal(t, "sqlite3", form.DbType)
|
|
assert.Equal(t, "/srv/gitea/data/gitea.db", form.DbPath)
|
|
assert.Equal(t, "http://localhost:3000/", form.AppURL)
|
|
assert.Equal(t, "smtp.example.com", form.SMTPAddr)
|
|
assert.Equal(t, "smtp-user", form.SMTPUser)
|
|
assert.Equal(t, "restored-internal", form.ImportedInternalToken)
|
|
assert.True(t, form.ImportSensitiveSecrets)
|
|
assert.Equal(t, "/srv/gitea/backups/db", form.BackupPath)
|
|
assert.Equal(t, "20260524-010101.000000001", form.BackupRestoreID)
|
|
assert.Equal(t, recoveryModeDatabaseBackup, form.RecoveryMode)
|
|
// end edit/add - by petru @ codex
|
|
}
|
|
|
|
func TestDetectInstallExistingDatabase(t *testing.T) {
|
|
// start edit/add - by petru @ codex
|
|
tmpDir := t.TempDir()
|
|
ctx := context.Background()
|
|
|
|
originalType := setting.Database.Type
|
|
originalPath := setting.Database.Path
|
|
originalHost := setting.Database.Host
|
|
originalUser := setting.Database.User
|
|
originalPasswd := setting.Database.Passwd
|
|
originalName := setting.Database.Name
|
|
originalSchema := setting.Database.Schema
|
|
originalSSLMode := setting.Database.SSLMode
|
|
originalLogSQL := setting.Database.LogSQL
|
|
originalAllowSQLite3CreateForInstall := setting.AllowSQLite3CreateForInstall
|
|
defer func() {
|
|
setting.Database.Type = originalType
|
|
setting.Database.Path = originalPath
|
|
setting.Database.Host = originalHost
|
|
setting.Database.User = originalUser
|
|
setting.Database.Passwd = originalPasswd
|
|
setting.Database.Name = originalName
|
|
setting.Database.Schema = originalSchema
|
|
setting.Database.SSLMode = originalSSLMode
|
|
setting.Database.LogSQL = originalLogSQL
|
|
setting.AllowSQLite3CreateForInstall = originalAllowSQLite3CreateForInstall
|
|
db.UnsetDefaultEngine()
|
|
}()
|
|
|
|
setting.Database.Type = setting.DatabaseType("sqlite3")
|
|
setting.Database.Path = filepath.Join(tmpDir, "gitea.db")
|
|
setting.Database.Host = ""
|
|
setting.Database.User = ""
|
|
setting.Database.Passwd = ""
|
|
setting.Database.Name = ""
|
|
setting.Database.Schema = ""
|
|
setting.Database.SSLMode = ""
|
|
setting.Database.LogSQL = false
|
|
setting.AllowSQLite3CreateForInstall = true
|
|
|
|
require.NoError(t, db.InitEngine(ctx))
|
|
_, err := db.GetEngine(ctx).Exec("CREATE TABLE version (version INTEGER)")
|
|
require.NoError(t, err)
|
|
_, err = db.GetEngine(ctx).Exec("INSERT INTO version (version) VALUES (1)")
|
|
require.NoError(t, err)
|
|
_, err = db.GetEngine(ctx).Exec("CREATE TABLE user (id INTEGER PRIMARY KEY)")
|
|
require.NoError(t, err)
|
|
_, err = db.GetEngine(ctx).Exec("INSERT INTO user (id) VALUES (1)")
|
|
require.NoError(t, err)
|
|
db.UnsetDefaultEngine()
|
|
|
|
existingDatabaseDetected, err := detectInstallExistingDatabase(ctx)
|
|
require.NoError(t, err)
|
|
assert.True(t, existingDatabaseDetected)
|
|
// end edit/add - by petru @ codex
|
|
}
|
|
|
|
func TestShouldRestoreDatabaseFromBackup(t *testing.T) {
|
|
// start edit/add - by petru @ codex
|
|
assert.True(t, shouldRestoreDatabaseFromBackup(recoveryModeDatabaseBackup, false))
|
|
assert.False(t, shouldRestoreDatabaseFromBackup(recoveryModeDatabaseBackup, true))
|
|
assert.False(t, shouldRestoreDatabaseFromBackup(recoveryModeExistingDatabase, false))
|
|
// end edit/add - by petru @ codex
|
|
}
|
|
|
|
func TestShouldRequireBackupAppINISnapshot(t *testing.T) {
|
|
// start edit/add - by petru @ codex
|
|
assert.True(t, shouldRequireBackupAppINISnapshot(recoveryModeDatabaseBackup, true))
|
|
assert.False(t, shouldRequireBackupAppINISnapshot(recoveryModeDatabaseBackup, false))
|
|
assert.False(t, shouldRequireBackupAppINISnapshot(recoveryModeExistingDatabase, true))
|
|
// end edit/add - by petru @ codex
|
|
}
|
|
|
|
func TestMergeInstallRecoveryConfigSMTP(t *testing.T) {
|
|
// start edit/add - by petru @ codex
|
|
existing := &setting.RecoveryConfig{
|
|
SMTPAddr: "smtp.example.com",
|
|
SMTPPort: "587",
|
|
SMTPFrom: "gitSafe <noreply@example.com>",
|
|
SMTPUser: "smtp-user",
|
|
SMTPPasswd: "smtp-pass",
|
|
}
|
|
next := &setting.RecoveryConfig{
|
|
Enabled: true,
|
|
AllowedEmails: "admin@example.com",
|
|
}
|
|
|
|
merged := mergeInstallRecoveryConfigSMTP(existing, next)
|
|
require.NotNil(t, merged)
|
|
assert.Equal(t, "smtp.example.com", merged.SMTPAddr)
|
|
assert.Equal(t, "587", merged.SMTPPort)
|
|
assert.Equal(t, "gitSafe <noreply@example.com>", merged.SMTPFrom)
|
|
assert.Equal(t, "smtp-user", merged.SMTPUser)
|
|
assert.Equal(t, "smtp-pass", merged.SMTPPasswd)
|
|
// end edit/add - by petru @ codex
|
|
}
|
|
|
|
func TestImportAppINIInRecoveryKeepsImportedValues(t *testing.T) {
|
|
defer test.MockVariableValue(&setting.InstallLock, false)()
|
|
defer test.MockVariableValue(&setting.SupportedDatabaseTypes, []string{"mysql", "postgres", "sqlite3"})()
|
|
defer test.MockVariableValue(&setting.AppName, "Gitea: Git with a cup of tea")()
|
|
|
|
tmpDir := t.TempDir()
|
|
oldCustomConf := setting.CustomConf
|
|
defer func() {
|
|
setting.CustomConf = oldCustomConf
|
|
}()
|
|
setting.CustomConf = filepath.Join(tmpDir, "custom", "conf", "app.ini")
|
|
|
|
recoveryCfg := &setting.RecoveryConfig{
|
|
Enabled: true,
|
|
AllowedEmails: "admin@example.com",
|
|
TokenSecret: "secret",
|
|
TokenTTLMin: 15,
|
|
SMTPFrom: "gitSafe <noreply@example.com>",
|
|
}
|
|
require.NoError(t, setting.SaveRecoveryConfig(recoveryCfg))
|
|
|
|
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(`
|
|
APP_NAME = Imported Safe: for your code
|
|
|
|
[database]
|
|
DB_TYPE = sqlite3
|
|
PATH = ` + filepath.ToSlash(filepath.Join(tmpDir, "data", "gitea.db")) + `
|
|
|
|
[mailer]
|
|
ENABLED = true
|
|
SMTP_ADDR = smtp.example.com
|
|
SMTP_PORT = 587
|
|
FROM = Imported Safe <mail@example.com>
|
|
USER = smtp-user
|
|
PASSWD = smtp-pass
|
|
`))
|
|
require.NoError(t, err)
|
|
require.NoError(t, writer.Close())
|
|
|
|
r := Routes()
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodPost, "/import_app_ini?recovery=1", &body)
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
r.ServeHTTP(w, WithRecoveryRequest(req))
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
assert.Contains(t, w.Body.String(), `name="app_name" value="Imported Safe: for your code"`)
|
|
assert.Contains(t, w.Body.String(), `id="db_type" name="db_type" value="sqlite3"`)
|
|
assert.Contains(t, w.Body.String(), `name="smtp_addr" value="smtp.example.com"`)
|
|
assert.Contains(t, w.Body.String(), `name="smtp_from_name" value="Imported Safe"`)
|
|
assert.NotContains(t, w.Body.String(), `name="app_name" value="gitSafe"`)
|
|
}
|
|
|
|
// end edit/add - by petru @ codex
|
|
|
|
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"})()
|
|
|
|
cfg, err := setting.NewConfigProviderFromData(`
|
|
APP_NAME = Imported Gitea
|
|
RUN_USER = imported-user
|
|
|
|
[database]
|
|
DB_TYPE = postgres
|
|
HOST = db.example.com:5432
|
|
USER = gitea
|
|
PASSWD = secret
|
|
NAME = giteadb
|
|
SCHEMA = custom
|
|
SSL_MODE = require
|
|
|
|
[repository]
|
|
ROOT = /srv/gitea/repos
|
|
ALLOW_ADOPTION_OF_UNADOPTED_REPOSITORIES = false
|
|
ALLOW_DELETE_OF_UNADOPTED_REPOSITORIES = true
|
|
|
|
[server]
|
|
DOMAIN = gitea.example.com
|
|
SSH_PORT = 2222
|
|
HTTP_PORT = 4000
|
|
ROOT_URL = https://gitea.example.com/
|
|
|
|
[lfs]
|
|
PATH = /srv/gitea/lfs
|
|
|
|
[log]
|
|
ROOT_PATH = /srv/gitea/log
|
|
|
|
[mailer]
|
|
ENABLED = true
|
|
SMTP_ADDR = smtp.example.com
|
|
SMTP_PORT = 587
|
|
FROM = Gitea <gitea@example.com>
|
|
USER = smtp-user
|
|
PASSWD = smtp-pass
|
|
|
|
[service]
|
|
REGISTER_EMAIL_CONFIRM = true
|
|
REGISTER_MANUAL_CONFIRM = false
|
|
ENABLE_NOTIFY_MAIL = true
|
|
ADMIN_CREATED_ACCOUNT_MODE = invite
|
|
DISABLE_REGISTRATION = false
|
|
ALLOW_ONLY_INTERNAL_REGISTRATION = true
|
|
ALLOW_ONLY_EXTERNAL_REGISTRATION = false
|
|
ENABLE_CAPTCHA = true
|
|
REQUIRE_SIGNIN_VIEW = true
|
|
DEFAULT_KEEP_EMAIL_PRIVATE = true
|
|
DEFAULT_ALLOW_CREATE_ORGANIZATION = false
|
|
DEFAULT_ENABLE_TIMETRACKING = false
|
|
NO_REPLY_ADDRESS = noreply.example.com
|
|
|
|
[openid]
|
|
ENABLE_OPENID_SIGNIN = true
|
|
ENABLE_OPENID_SIGNUP = true
|
|
|
|
[security]
|
|
ENABLE_UPDATE_CHECKER = true
|
|
PASSWORD_HASH_ALGO = pbkdf2
|
|
|
|
[admin]
|
|
ADMIN_MANAGEMENT_POLICY = super_admin_only
|
|
|
|
[repository.release]
|
|
MAX_FILES = 20
|
|
FILE_MAX_SIZE = 2048
|
|
|
|
[backup]
|
|
PATH = /srv/gitea/backups/db
|
|
RETENTION_COUNT = 9
|
|
COMPRESS = false
|
|
INCLUDE_APP_INI_SNAPSHOT = false
|
|
|
|
[cron.database_backup]
|
|
ENABLED = true
|
|
RUN_AT_START = true
|
|
SCHEDULE = @every 12h
|
|
|
|
[i18n]
|
|
LANGS = de-DE,en-US
|
|
`)
|
|
require.NoError(t, err)
|
|
|
|
form, curDBType := newInstallFormFromSettings()
|
|
curDBType = populateInstallFormFromConfig(&form, cfg, curDBType)
|
|
|
|
assert.Equal(t, "postgres", curDBType)
|
|
assert.Equal(t, "postgres", form.DbType)
|
|
assert.Equal(t, "Imported Gitea", form.AppName)
|
|
assert.Equal(t, "imported-user", form.RunUser)
|
|
assert.Equal(t, "db.example.com:5432", form.DbHost)
|
|
assert.Equal(t, "gitea", form.DbUser)
|
|
assert.Equal(t, "secret", form.DbPasswd)
|
|
assert.Equal(t, "giteadb", form.DbName)
|
|
assert.Equal(t, "custom", form.DbSchema)
|
|
assert.Equal(t, "require", form.SSLMode)
|
|
assert.Equal(t, "/srv/gitea/repos", form.RepoRootPath)
|
|
assert.False(t, form.AllowAdoptionOfUnadoptedRepositories)
|
|
assert.True(t, form.AllowDeleteOfUnadoptedRepositories)
|
|
assert.Equal(t, "/srv/gitea/lfs", form.LFSRootPath)
|
|
assert.Equal(t, "gitea.example.com", form.Domain)
|
|
assert.Equal(t, 2222, form.SSHPort)
|
|
assert.Equal(t, "4000", form.HTTPPort)
|
|
assert.Equal(t, "https://gitea.example.com/", form.AppURL)
|
|
assert.Equal(t, "/srv/gitea/log", form.LogRootPath)
|
|
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)
|
|
assert.True(t, form.RegisterConfirm)
|
|
assert.False(t, form.RegisterManualConfirm)
|
|
assert.True(t, form.MailNotify)
|
|
assert.Equal(t, "invite", form.AdminCreatedAccountMode)
|
|
assert.Equal(t, "local_only", form.RegistrationMode)
|
|
assert.False(t, form.EnableOpenIDSignIn)
|
|
assert.False(t, form.EnableOpenIDSignUp)
|
|
assert.True(t, form.EnableCaptcha)
|
|
assert.True(t, form.RequireSignInView)
|
|
assert.True(t, form.DefaultKeepEmailPrivate)
|
|
assert.False(t, form.DefaultAllowCreateOrganization)
|
|
assert.False(t, form.DefaultEnableTimetracking)
|
|
assert.Equal(t, "noreply.example.com", form.NoReplyAddress)
|
|
assert.True(t, form.EnableUpdateChecker)
|
|
assert.Equal(t, "pbkdf2", form.PasswordAlgorithm)
|
|
assert.Equal(t, "super_admin_only", form.AdminManagementPolicy)
|
|
assert.EqualValues(t, 20, form.ReleaseMaxFiles)
|
|
assert.EqualValues(t, 2048, form.ReleaseFileMaxSize)
|
|
assert.True(t, form.BackupEnabled)
|
|
assert.True(t, form.BackupRunAtStart)
|
|
assert.Equal(t, "@every 12h", form.BackupSchedule)
|
|
assert.Equal(t, "/srv/gitea/backups/db", form.BackupPath)
|
|
assert.Equal(t, 9, form.BackupRetentionCount)
|
|
assert.False(t, form.BackupCompress)
|
|
assert.False(t, form.BackupIncludeAppINISnapshot)
|
|
}
|
|
|
|
// start edit/add - by petru @ codex
|
|
func TestNewInstallFormFromSettingsBackupDefaults(t *testing.T) {
|
|
cfg, err := setting.NewConfigProviderFromData("")
|
|
require.NoError(t, err)
|
|
defer test.MockVariableValue(&setting.CfgProvider, cfg)()
|
|
|
|
form, _ := newInstallFormFromSettings()
|
|
|
|
assert.True(t, form.BackupEnabled)
|
|
assert.False(t, form.BackupRunAtStart)
|
|
assert.Equal(t, "@daily", form.BackupSchedule)
|
|
}
|
|
|
|
func TestNewInstallFormFromSettingsRecoveryDefaults(t *testing.T) {
|
|
oldCustomConf := setting.CustomConf
|
|
defer func() {
|
|
setting.CustomConf = oldCustomConf
|
|
}()
|
|
|
|
setting.CustomConf = filepath.Join(t.TempDir(), "custom", "conf", "app.ini")
|
|
require.NoError(t, setting.SaveRecoveryConfig(&setting.RecoveryConfig{
|
|
Enabled: true,
|
|
AllowedEmails: "admin@example.com,ops@example.com",
|
|
SMTPAddr: "smtp.example.com",
|
|
SMTPPort: "587",
|
|
SMTPFrom: "Gitea <noreply@example.com>",
|
|
SMTPUser: "smtp-user",
|
|
SMTPPasswd: "smtp-pass",
|
|
}))
|
|
|
|
form, _ := newInstallFormFromSettings()
|
|
assert.True(t, form.RecoveryEmailEnabled)
|
|
assert.Equal(t, "admin@example.com,ops@example.com", form.RecoveryAllowedEmails)
|
|
}
|
|
|
|
func TestNewInstallFormFromSettingsDefaultsRecoveryEmailToEnabledOnFreshInstall(t *testing.T) { // edit/add - by petru @ codex
|
|
oldCustomConf := setting.CustomConf
|
|
defer func() {
|
|
setting.CustomConf = oldCustomConf
|
|
}()
|
|
|
|
setting.CustomConf = filepath.Join(t.TempDir(), "custom", "conf", "app.ini")
|
|
form, _ := newInstallFormFromSettings()
|
|
assert.True(t, form.RecoveryEmailEnabled)
|
|
}
|
|
|
|
func TestDefaultInstallRecoveryAllowedEmailsUsesAdminEmailWhenEmpty(t *testing.T) { // edit/add - by petru @ codex
|
|
form := &forms.InstallForm{AdminEmail: "admin@example.com"}
|
|
assert.Equal(t, "admin@example.com", defaultInstallRecoveryAllowedEmails(form))
|
|
|
|
form.RecoveryAllowedEmails = "ops@example.com"
|
|
assert.Equal(t, "", defaultInstallRecoveryAllowedEmails(form))
|
|
}
|
|
|
|
func TestNormalizeInstallRecoveryEmails(t *testing.T) {
|
|
normalized, err := normalizeInstallRecoveryEmails(" admin@example.com,\nops@example.com ; admin@example.com ")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "admin@example.com,ops@example.com", normalized)
|
|
|
|
_, err = normalizeInstallRecoveryEmails("not-an-email")
|
|
require.Error(t, err)
|
|
}
|
|
|
|
// end edit/add - by petru @ codex
|
|
|
|
// start edit/add - by petru @ codex
|
|
func TestApplyImportedAppINIRepositoryRecoveryDefaults(t *testing.T) {
|
|
form := forms.InstallForm{
|
|
ImportedAppINI: true,
|
|
RecoveryMode: recoveryModeRepositoryFileSystem,
|
|
AllowAdoptionOfUnadoptedRepositories: false,
|
|
AllowDeleteOfUnadoptedRepositories: false,
|
|
}
|
|
|
|
applyImportedAppINIRepositoryRecoveryDefaults(&form, false)
|
|
assert.True(t, form.AllowAdoptionOfUnadoptedRepositories)
|
|
assert.True(t, form.AllowDeleteOfUnadoptedRepositories)
|
|
|
|
form = forms.InstallForm{
|
|
ImportedAppINI: true,
|
|
RecoveryMode: recoveryModeRepositoryFileSystem,
|
|
AllowAdoptionOfUnadoptedRepositories: false,
|
|
AllowDeleteOfUnadoptedRepositories: false,
|
|
}
|
|
|
|
applyImportedAppINIRepositoryRecoveryDefaults(&form, true)
|
|
assert.False(t, form.AllowAdoptionOfUnadoptedRepositories)
|
|
assert.False(t, form.AllowDeleteOfUnadoptedRepositories)
|
|
}
|
|
|
|
func TestApplyImportedAppINIRepositoryRecoveryDefaultsWithoutFilesystemRecoveryMode(t *testing.T) {
|
|
form := forms.InstallForm{
|
|
ImportedAppINI: true,
|
|
RecoveryMode: recoveryModeExistingDatabase,
|
|
AllowAdoptionOfUnadoptedRepositories: false,
|
|
AllowDeleteOfUnadoptedRepositories: false,
|
|
}
|
|
|
|
applyImportedAppINIRepositoryRecoveryDefaults(&form, false)
|
|
assert.False(t, form.AllowAdoptionOfUnadoptedRepositories)
|
|
assert.False(t, form.AllowDeleteOfUnadoptedRepositories)
|
|
}
|
|
|
|
func TestHasInstallRepositoryFilesystem(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
assert.False(t, hasInstallRepositoryFilesystem(tmpDir))
|
|
|
|
require.NoError(t, os.Mkdir(filepath.Join(tmpDir, "owner"), 0o755))
|
|
require.NoError(t, os.Mkdir(filepath.Join(tmpDir, "owner", "repo.git"), 0o755))
|
|
assert.True(t, hasInstallRepositoryFilesystem(tmpDir))
|
|
|
|
tmpDirFlat := t.TempDir()
|
|
require.NoError(t, os.Mkdir(filepath.Join(tmpDirFlat, "repo.git"), 0o755))
|
|
assert.True(t, hasInstallRepositoryFilesystem(tmpDirFlat))
|
|
}
|
|
|
|
func TestHasInstallRepositoryFilesystemRecoveryRequiresExistingOrManualAppINI(t *testing.T) { // edit/add - by petru @ codex
|
|
tmpDir := t.TempDir()
|
|
require.NoError(t, os.Mkdir(filepath.Join(tmpDir, "owner"), 0o755))
|
|
require.NoError(t, os.Mkdir(filepath.Join(tmpDir, "owner", "repo.git"), 0o755))
|
|
|
|
oldCustomConf := setting.CustomConf
|
|
defer func() {
|
|
setting.CustomConf = oldCustomConf
|
|
}()
|
|
setting.CustomConf = filepath.Join(tmpDir, "custom", "conf", "app.ini")
|
|
|
|
form := &forms.InstallForm{RepoRootPath: tmpDir}
|
|
assert.False(t, hasInstallRepositoryFilesystemRecovery(form))
|
|
|
|
form.ImportedAppINI = true
|
|
assert.True(t, hasInstallRepositoryFilesystemRecovery(form))
|
|
|
|
form.BackupImportAppINI = true
|
|
assert.False(t, hasInstallRepositoryFilesystemRecovery(form))
|
|
|
|
require.NoError(t, os.MkdirAll(filepath.Dir(setting.CustomConf), 0o755))
|
|
require.NoError(t, os.WriteFile(setting.CustomConf, []byte("APP_NAME = Test\n"), 0o644))
|
|
form.ImportedAppINI = false
|
|
form.BackupImportAppINI = false
|
|
assert.True(t, hasInstallRepositoryFilesystemRecovery(form))
|
|
}
|
|
|
|
func TestNormalizeInstallRecoverySelectionStateForcesBundleRestoreModeAndSensitiveSecrets(t *testing.T) { // edit/add - by petru @ codex
|
|
form := &forms.InstallForm{
|
|
ImportedAppINI: true,
|
|
ImportSensitiveSecrets: false,
|
|
BackupImportAppINI: true,
|
|
BackupRestoreID: "20260524-010101.000000001",
|
|
BackupRestoreDB: true,
|
|
RecoveryMode: recoveryModeExistingDatabase,
|
|
}
|
|
|
|
normalizeInstallRecoverySelectionState(form, false)
|
|
assert.True(t, form.ImportSensitiveSecrets)
|
|
assert.Equal(t, recoveryModeDatabaseBackup, form.RecoveryMode)
|
|
}
|
|
|
|
func TestNormalizeInstallRecoverySelectionStateKeepsManualAppINIChoice(t *testing.T) { // edit/add - by petru @ codex
|
|
form := &forms.InstallForm{
|
|
ImportedAppINI: true,
|
|
ImportSensitiveSecrets: false,
|
|
}
|
|
|
|
normalizeInstallRecoverySelectionState(form, false)
|
|
assert.False(t, form.ImportSensitiveSecrets)
|
|
}
|
|
|
|
func TestHasSelectedInstallDatabaseBackupSourceAcceptsUploadedFileWithoutBundle(t *testing.T) { // edit/add - by petru @ codex
|
|
form := &forms.InstallForm{}
|
|
assert.False(t, hasSelectedInstallDatabaseBackupSource(form, false))
|
|
assert.True(t, hasSelectedInstallDatabaseBackupSource(form, true))
|
|
|
|
form.BackupRestoreID = "20260524-010101.000000001"
|
|
assert.True(t, hasSelectedInstallDatabaseBackupSource(form, false))
|
|
}
|
|
|
|
func TestHasInstallRecoveryAppINIConfigSourceAcceptsManualImportWithoutBundleSnapshot(t *testing.T) { // edit/add - by petru @ codex
|
|
form := &forms.InstallForm{ImportedAppINI: true}
|
|
assert.True(t, hasInstallRecoveryAppINIConfigSource(form, nil))
|
|
|
|
form.BackupImportAppINI = true
|
|
assert.False(t, hasInstallRecoveryAppINIConfigSource(form, nil))
|
|
}
|
|
|
|
func TestRemoveInstallSQLiteDatabaseArtifacts(t *testing.T) { // edit/add - by petru @ codex
|
|
tmpDir := t.TempDir()
|
|
dbPath := filepath.Join(tmpDir, "gitea.db")
|
|
for _, path := range []string{dbPath, dbPath + "-wal", dbPath + "-shm", dbPath + "-journal"} {
|
|
require.NoError(t, os.WriteFile(path, []byte("x"), 0o644))
|
|
}
|
|
|
|
require.NoError(t, removeInstallSQLiteDatabaseArtifacts(dbPath))
|
|
for _, path := range []string{dbPath, dbPath + "-wal", dbPath + "-shm", dbPath + "-journal"} {
|
|
_, err := os.Stat(path)
|
|
assert.ErrorIs(t, err, os.ErrNotExist)
|
|
}
|
|
}
|
|
|
|
func TestResolveInstallBackupPath(t *testing.T) {
|
|
defer test.MockVariableValue(&setting.AppWorkPath, "/srv/gitea")()
|
|
|
|
assert.Equal(t, "", resolveInstallBackupPath(""))
|
|
assert.Equal(t, "/srv/gitea/data/backups/db", resolveInstallBackupPath("data/backups/db"))
|
|
assert.Equal(t, "/var/lib/gitea/backups", resolveInstallBackupPath("/var/lib/gitea/backups"))
|
|
}
|
|
|
|
func TestListInstallBackupChoices(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
writeInstallBackupManifestForTest(t, tmpDir, "20260524-010101.000000001", "sqlite3", 100)
|
|
writeInstallBackupManifestForTest(t, tmpDir, "20260524-010102.000000002", "mysql", 200)
|
|
writeInstallBackupManifestForTest(t, tmpDir, "20260524-010103.000000003", "sqlite3", 300)
|
|
|
|
choices, err := listInstallBackupChoices(tmpDir, "sqlite3")
|
|
require.NoError(t, err)
|
|
require.Len(t, choices, 2)
|
|
assert.Equal(t, "20260524-010103.000000003", choices[0].ID)
|
|
assert.Equal(t, "20260524-010101.000000001", choices[1].ID)
|
|
assert.Equal(t, "20260524-010103.000000003 (sqlite3)", choices[0].Label)
|
|
}
|
|
|
|
func TestListInstallRecoveryBackupChoicesWithoutAppINIShowsAllBackups(t *testing.T) {
|
|
// start edit/add - by petru @ codex
|
|
tmpDir := t.TempDir()
|
|
oldCustomConf := setting.CustomConf
|
|
defer func() {
|
|
setting.CustomConf = oldCustomConf
|
|
}()
|
|
setting.CustomConf = filepath.Join(tmpDir, "custom", "conf", "app.ini")
|
|
|
|
writeInstallBackupManifestForTest(t, tmpDir, "20260524-010101.000000001", "sqlite3", 100)
|
|
writeInstallBackupManifestForTest(t, tmpDir, "20260524-010102.000000002", "mysql", 200)
|
|
|
|
form := &forms.InstallForm{
|
|
DbType: "mysql",
|
|
BackupPath: tmpDir,
|
|
}
|
|
|
|
choices, err := listInstallRecoveryBackupChoices(form, true)
|
|
require.NoError(t, err)
|
|
require.Len(t, choices, 2)
|
|
assert.Equal(t, "20260524-010102.000000002", choices[0].ID)
|
|
assert.Equal(t, "20260524-010101.000000001", choices[1].ID)
|
|
// end edit/add - by petru @ codex
|
|
}
|
|
|
|
func TestListInstallRecoveryBackupChoicesUsesRecoveryConfigBackupPathFallback(t *testing.T) {
|
|
// start edit/add - by petru @ codex
|
|
tmpDir := t.TempDir()
|
|
emptyBackupDir := filepath.Join(tmpDir, "empty-backups")
|
|
recoveryBackupDir := filepath.Join(tmpDir, "recovery-backups")
|
|
require.NoError(t, os.MkdirAll(emptyBackupDir, 0o755))
|
|
require.NoError(t, os.MkdirAll(recoveryBackupDir, 0o755))
|
|
|
|
customConfDir := filepath.Join(tmpDir, "custom", "conf")
|
|
require.NoError(t, os.MkdirAll(customConfDir, 0o755))
|
|
|
|
oldCustomConf := setting.CustomConf
|
|
defer func() {
|
|
setting.CustomConf = oldCustomConf
|
|
}()
|
|
setting.CustomConf = filepath.Join(customConfDir, "app.ini")
|
|
require.NoError(t, os.WriteFile(setting.CustomConf, []byte("APP_NAME = Test\n"), 0o644))
|
|
|
|
require.NoError(t, setting.SaveRecoveryConfig(&setting.RecoveryConfig{
|
|
Enabled: true,
|
|
AllowedEmails: "admin@example.com",
|
|
BackupPath: recoveryBackupDir,
|
|
}))
|
|
|
|
writeInstallBackupManifestForTest(t, recoveryBackupDir, "20260524-010102.000000002", "mysql", 200)
|
|
|
|
form := &forms.InstallForm{
|
|
DbType: "mysql",
|
|
BackupPath: emptyBackupDir,
|
|
}
|
|
|
|
choices, err := listInstallRecoveryBackupChoices(form, true)
|
|
require.NoError(t, err)
|
|
require.Len(t, choices, 1)
|
|
assert.Equal(t, "20260524-010102.000000002", choices[0].ID)
|
|
// end edit/add - by petru @ codex
|
|
}
|
|
|
|
func TestListInstallRecoveryBackupChoicesWithAppINIStillShowsAllBackups(t *testing.T) {
|
|
// start edit/add - by petru @ codex
|
|
tmpDir := t.TempDir()
|
|
customConfDir := filepath.Join(tmpDir, "custom", "conf")
|
|
require.NoError(t, os.MkdirAll(customConfDir, 0o755))
|
|
|
|
oldCustomConf := setting.CustomConf
|
|
defer func() {
|
|
setting.CustomConf = oldCustomConf
|
|
}()
|
|
setting.CustomConf = filepath.Join(customConfDir, "app.ini")
|
|
require.NoError(t, os.WriteFile(setting.CustomConf, []byte("APP_NAME = Test\n"), 0o644))
|
|
|
|
writeInstallBackupManifestForTest(t, tmpDir, "20260524-010101.000000001", "sqlite3", 100)
|
|
writeInstallBackupManifestForTest(t, tmpDir, "20260524-010102.000000002", "mysql", 200)
|
|
|
|
form := &forms.InstallForm{
|
|
DbType: "mysql",
|
|
BackupPath: tmpDir,
|
|
}
|
|
|
|
choices, err := listInstallRecoveryBackupChoices(form, true)
|
|
require.NoError(t, err)
|
|
require.Len(t, choices, 2)
|
|
assert.Equal(t, "20260524-010102.000000002", choices[0].ID)
|
|
assert.Equal(t, "20260524-010101.000000001", choices[1].ID)
|
|
// end edit/add - by petru @ codex
|
|
}
|
|
|
|
func TestNewInstallFormFromSettingsUsesRecoveryBackupPathWithoutAppINI(t *testing.T) {
|
|
// start edit/add - by petru @ codex
|
|
tmpDir := t.TempDir()
|
|
oldCustomConf := setting.CustomConf
|
|
oldBackup := setting.Backup
|
|
defer func() {
|
|
setting.CustomConf = oldCustomConf
|
|
setting.Backup = oldBackup
|
|
}()
|
|
|
|
setting.CustomConf = filepath.Join(tmpDir, "custom", "conf", "app.ini")
|
|
setting.Backup.Path = "data/backups/db"
|
|
|
|
require.NoError(t, setting.SaveRecoveryConfig(&setting.RecoveryConfig{
|
|
Enabled: true,
|
|
AllowedEmails: "admin@example.com",
|
|
BackupPath: filepath.Join(tmpDir, "restores", "db"),
|
|
RepoRootPath: filepath.Join(tmpDir, "restores", "repos"),
|
|
}))
|
|
|
|
form, _ := newInstallFormFromSettings()
|
|
assert.Equal(t, filepath.Join(tmpDir, "restores", "db"), form.BackupPath)
|
|
assert.Equal(t, filepath.Join(tmpDir, "restores", "repos"), form.RepoRootPath)
|
|
// end edit/add - by petru @ codex
|
|
}
|
|
|
|
// end edit/add - by petru @ codex
|
|
|
|
// start edit/add - by petru @ codex
|
|
func writeInstallBackupManifestForTest(t *testing.T, rootPath, backupID, dbType string, createdUnix int64) {
|
|
t.Helper()
|
|
|
|
backupDir := filepath.Join(rootPath, backupID)
|
|
require.NoError(t, os.MkdirAll(backupDir, 0o755))
|
|
|
|
manifestBytes, err := json.Marshal(map[string]any{
|
|
"schema_version": 1,
|
|
"id": backupID,
|
|
"created_unix": createdUnix,
|
|
"db_type": dbType,
|
|
"app_version": "dev",
|
|
"app_name": "Test",
|
|
"migration_version": 1,
|
|
"file_name": "database.sql.gz",
|
|
"file_sha256": "abc",
|
|
"file_size": 1,
|
|
"compressed": true,
|
|
})
|
|
require.NoError(t, err)
|
|
require.NoError(t, os.WriteFile(filepath.Join(backupDir, "manifest.json"), manifestBytes, 0o644))
|
|
}
|
|
|
|
func writeInstallBackupManifestWithAppINIForTest(t *testing.T, rootPath, backupID, dbType string, createdUnix int64, appINIContent string) { // edit/add - by petru @ codex
|
|
t.Helper()
|
|
|
|
backupDir := filepath.Join(rootPath, backupID)
|
|
require.NoError(t, os.MkdirAll(backupDir, 0o755))
|
|
|
|
manifestBytes, err := json.Marshal(map[string]any{
|
|
"schema_version": 1,
|
|
"id": backupID,
|
|
"created_unix": createdUnix,
|
|
"db_type": dbType,
|
|
"app_version": "dev",
|
|
"app_name": "Test",
|
|
"migration_version": 1,
|
|
"file_name": "database.sql.gz",
|
|
"file_sha256": "abc",
|
|
"file_size": 1,
|
|
"compressed": true,
|
|
"app_ini_snapshot_file": "app.ini",
|
|
})
|
|
require.NoError(t, err)
|
|
require.NoError(t, os.WriteFile(filepath.Join(backupDir, "manifest.json"), manifestBytes, 0o644))
|
|
require.NoError(t, os.WriteFile(filepath.Join(backupDir, "app.ini"), []byte(appINIContent), 0o644))
|
|
}
|
|
|
|
// end edit/add - by petru @ codex
|
|
|
|
func TestPopulateInstallFormFromConfigReplacesSMTPFromSplitFields(t *testing.T) {
|
|
cfg, err := setting.NewConfigProviderFromData(`
|
|
APP_NAME = gitSafe: for your code
|
|
|
|
[mailer]
|
|
ENABLED = true
|
|
SMTP_ADDR = smtp.example.com
|
|
SMTP_PORT = 587
|
|
FROM = gitSafe <noreply@example.com>
|
|
`)
|
|
require.NoError(t, err)
|
|
|
|
form, curDBType := newInstallFormFromSettings()
|
|
form.SMTPFrom = "Legacy <legacy@example.com>"
|
|
form.SMTPFromName = "Legacy"
|
|
form.SMTPFromAddress = "legacy@example.com"
|
|
|
|
populateInstallFormFromConfig(&form, cfg, curDBType)
|
|
|
|
assert.Equal(t, "gitSafe <noreply@example.com>", form.SMTPFrom)
|
|
assert.Equal(t, "gitSafe", form.SMTPFromName)
|
|
assert.Equal(t, "noreply@example.com", form.SMTPFromAddress)
|
|
}
|
|
|
|
func TestPopulateInstallFormFromConfigWithSensitiveSecrets(t *testing.T) {
|
|
cfg, err := setting.NewConfigProviderFromData(`
|
|
[server]
|
|
LFS_JWT_SECRET = lfs-secret
|
|
|
|
[security]
|
|
INTERNAL_TOKEN = internal-secret
|
|
|
|
[oauth2]
|
|
JWT_SECRET = oauth-secret
|
|
`)
|
|
require.NoError(t, err)
|
|
|
|
form, curDBType := newInstallFormFromSettings()
|
|
form.ImportSensitiveSecrets = true
|
|
populateInstallFormFromConfig(&form, cfg, curDBType)
|
|
|
|
assert.Equal(t, "lfs-secret", form.ImportedLFSJWTSecret)
|
|
assert.Equal(t, "internal-secret", form.ImportedInternalToken)
|
|
assert.Equal(t, "oauth-secret", form.ImportedOAuth2JWTSecret)
|
|
}
|
|
|
|
func TestPopulateInstallFormFromConfigWithSensitiveSecretURIs(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
lfsSecretPath := filepath.Join(tmpDir, "lfs_secret")
|
|
internalTokenPath := filepath.Join(tmpDir, "internal_token")
|
|
oauthSecretPath := filepath.Join(tmpDir, "oauth_secret")
|
|
|
|
require.NoError(t, os.WriteFile(lfsSecretPath, []byte("lfs-secret-uri\n"), 0o644))
|
|
require.NoError(t, os.WriteFile(internalTokenPath, []byte("internal-secret-uri\n"), 0o644))
|
|
require.NoError(t, os.WriteFile(oauthSecretPath, []byte("oauth-secret-uri\n"), 0o644))
|
|
|
|
cfg, err := setting.NewConfigProviderFromData(`
|
|
[server]
|
|
LFS_JWT_SECRET_URI = file:` + filepath.ToSlash(lfsSecretPath) + `
|
|
|
|
[security]
|
|
INTERNAL_TOKEN_URI = file:` + filepath.ToSlash(internalTokenPath) + `
|
|
|
|
[oauth2]
|
|
JWT_SECRET_URI = file:` + filepath.ToSlash(oauthSecretPath) + `
|
|
`)
|
|
require.NoError(t, err)
|
|
|
|
form, curDBType := newInstallFormFromSettings()
|
|
form.ImportSensitiveSecrets = true
|
|
populateInstallFormFromConfig(&form, cfg, curDBType)
|
|
|
|
assert.Equal(t, "lfs-secret-uri", form.ImportedLFSJWTSecret)
|
|
assert.Equal(t, "internal-secret-uri", form.ImportedInternalToken)
|
|
assert.Equal(t, "oauth-secret-uri", form.ImportedOAuth2JWTSecret)
|
|
}
|
|
|
|
func TestPopulateInstallSensitiveSecretsFromConfigFillsMissingValuesOnly(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
internalTokenPath := filepath.Join(tmpDir, "internal_token")
|
|
require.NoError(t, os.WriteFile(internalTokenPath, []byte("internal-secret-uri\n"), 0o644))
|
|
|
|
cfg, err := setting.NewConfigProviderFromData(`
|
|
[server]
|
|
LFS_JWT_SECRET = lfs-secret
|
|
|
|
[security]
|
|
INTERNAL_TOKEN_URI = file:` + filepath.ToSlash(internalTokenPath) + `
|
|
|
|
[oauth2]
|
|
JWT_SECRET = oauth-secret
|
|
`)
|
|
require.NoError(t, err)
|
|
|
|
form := forms.InstallForm{
|
|
ImportSensitiveSecrets: true,
|
|
ImportedLFSJWTSecret: "",
|
|
ImportedInternalToken: "",
|
|
ImportedOAuth2JWTSecret: "already-set",
|
|
}
|
|
populateInstallSensitiveSecretsFromConfig(&form, cfg)
|
|
|
|
assert.Equal(t, "lfs-secret", form.ImportedLFSJWTSecret)
|
|
assert.Equal(t, "internal-secret-uri", form.ImportedInternalToken)
|
|
assert.Equal(t, "already-set", form.ImportedOAuth2JWTSecret)
|
|
}
|
|
|
|
func TestImportAppINIWithSensitiveSecrets(t *testing.T) {
|
|
defer test.MockVariableValue(&setting.InstallLock, false)()
|
|
|
|
var body bytes.Buffer
|
|
writer := multipart.NewWriter(&body)
|
|
require.NoError(t, writer.WriteField("import_sensitive_secrets", "on"))
|
|
|
|
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(), `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"`)
|
|
}
|
|
|
|
// 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" form="install-form" 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"`)
|
|
}
|
|
|
|
func TestImportBackupAppINIInRecovery(t *testing.T) { // edit/add - by petru @ codex
|
|
defer test.MockVariableValue(&setting.InstallLock, false)()
|
|
|
|
tmpDir := t.TempDir()
|
|
writeInstallBackupManifestWithAppINIForTest(t, tmpDir, "20260524-010101.000000001", "sqlite3", 100, `
|
|
APP_NAME = Imported Safe: for your code
|
|
|
|
[database]
|
|
DB_TYPE = sqlite3
|
|
PATH = `+filepath.ToSlash(filepath.Join(tmpDir, "data", "gitea.db"))+`
|
|
|
|
[mailer]
|
|
ENABLED = true
|
|
SMTP_ADDR = smtp.example.com
|
|
SMTP_PORT = 587
|
|
FROM = Imported Safe <mail@example.com>
|
|
USER = smtp-user
|
|
PASSWD = smtp-pass
|
|
`)
|
|
|
|
var body bytes.Buffer
|
|
writer := multipart.NewWriter(&body)
|
|
require.NoError(t, writer.WriteField("backup_path", tmpDir))
|
|
require.NoError(t, writer.WriteField("backup_restore_id", "20260524-010101.000000001"))
|
|
require.NoError(t, writer.WriteField("backup_import_app_ini", "true"))
|
|
require.NoError(t, writer.Close())
|
|
|
|
r := Routes()
|
|
w := httptest.NewRecorder()
|
|
req := httptest.NewRequest(http.MethodPost, "/import_backup_app_ini?recovery=1", &body)
|
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
|
r.ServeHTTP(w, WithRecoveryRequest(req))
|
|
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
assert.Contains(t, w.Body.String(), `name="app_name" value="Imported Safe: for your code"`)
|
|
assert.Contains(t, w.Body.String(), `id="db_type" name="db_type" value="sqlite3"`)
|
|
assert.Contains(t, w.Body.String(), `name="smtp_addr" value="smtp.example.com"`)
|
|
assert.Contains(t, w.Body.String(), `name="smtp_from_name" value="Imported Safe"`)
|
|
assert.Contains(t, w.Body.String(), `name="imported_app_ini" value="true"`)
|
|
assert.Contains(t, w.Body.String(), `id="import_sensitive_secrets" name="import_sensitive_secrets" type="checkbox" form="install-form" checked`)
|
|
assert.Contains(t, w.Body.String(), `id="install_backup_restore_id" name="backup_restore_id" value="20260524-010101.000000001"`)
|
|
}
|
|
|
|
// end edit/add - by petru @ codex
|
|
|
|
func TestApplyInstallSensitiveSecretsToConfigPersistsImportedValues(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, "app.ini")
|
|
|
|
defer test.MockVariableValue(&setting.InternalToken, "")()
|
|
|
|
cfg, err := setting.NewConfigProviderFromData("")
|
|
require.NoError(t, err)
|
|
|
|
form := forms.InstallForm{
|
|
LFSRootPath: filepath.Join(tmpDir, "lfs"),
|
|
ImportSensitiveSecrets: true,
|
|
ImportedLFSJWTSecret: "lfs-secret",
|
|
ImportedInternalToken: "internal-secret",
|
|
ImportedOAuth2JWTSecret: "oauth-secret",
|
|
}
|
|
|
|
require.NoError(t, applyInstallSensitiveSecretsToConfig(cfg, &form))
|
|
require.NoError(t, cfg.SaveTo(configPath))
|
|
|
|
data, err := os.ReadFile(configPath)
|
|
require.NoError(t, err)
|
|
content := string(data)
|
|
assert.Contains(t, content, "LFS_JWT_SECRET = lfs-secret")
|
|
assert.Contains(t, content, "INTERNAL_TOKEN = internal-secret")
|
|
assert.Contains(t, content, "JWT_SECRET = oauth-secret")
|
|
}
|
|
|
|
// start edit/add - by petru @ codex
|
|
func TestApplyInstallSensitiveSecretsToConfigPreservesExistingLFSJWTSecretWithoutImport(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
configPath := filepath.Join(tmpDir, "app.ini")
|
|
|
|
cfg, err := setting.NewConfigProviderFromData(`
|
|
[server]
|
|
LFS_JWT_SECRET = existing-lfs-secret
|
|
`)
|
|
require.NoError(t, err)
|
|
|
|
form := forms.InstallForm{
|
|
LFSRootPath: filepath.Join(tmpDir, "lfs"),
|
|
ImportSensitiveSecrets: false,
|
|
}
|
|
|
|
require.NoError(t, applyInstallSensitiveSecretsToConfig(cfg, &form))
|
|
require.NoError(t, cfg.SaveTo(configPath))
|
|
|
|
data, err := os.ReadFile(configPath)
|
|
require.NoError(t, err)
|
|
content := string(data)
|
|
assert.Contains(t, content, "LFS_JWT_SECRET = existing-lfs-secret")
|
|
assert.NotContains(t, content, "LFS_JWT_SECRET_URI")
|
|
}
|
|
|
|
// end edit/add - by petru @ codex
|
|
|
|
func TestMain(m *testing.M) {
|
|
unittest.MainTest(m)
|
|
}
|