Added - Added optional sensitive-secret import for installer app.ini uploads.
release-nightly / nightly-binary (push) Has been cancelled
release-nightly / nightly-container (push) Has been cancelled

- 1 - I added an explicit installer checkbox for importing sensitive secrets from `app.ini` in `templates/install.tmpl`.
- 2 - I extended the installer form, submit pipeline, and final config writer so the optional import reuses `LFS_JWT_SECRET`, `INTERNAL_TOKEN`, and `oauth2.JWT_SECRET` from the uploaded `app.ini` instead of generating new values, including a submit-time fallback that re-reads the uploaded file if the checkbox was enabled after the first auto-import.
- 3 - I finalized secret resolution for both direct values and `LFS_JWT_SECRET_URI` / `INTERNAL_TOKEN_URI` / `JWT_SECRET_URI` file-based references, and added regression coverage for direct imports, URI-based imports, the real `POST /import_app_ini` flow, and the persisted `app.ini` output.
This commit is contained in:
2026-05-12 20:35:52 +00:00
parent e69839ed88
commit 512e577c3f
13 changed files with 457 additions and 62 deletions
+5
View File
@@ -735,3 +735,8 @@ Project Change ID[date-time] - application-version - Type - Summary:
146 - [2026-05-12 01:30:10] - v1.27.0-dev-125-g1525c9c8ee - Type: Modified - Enabled shared branding assets by default in the installer.
- 1 - I updated `routers/install/install.go` so `BrandingUseSharedAssets` starts as enabled on the install form, making `Use the same logo files for favicon assets` checked by default.
147 - [2026-05-12 01:43:10] - v1.27.0-dev-125-g1525c9c8ee - Type: Added - Added optional sensitive-secret import for installer `app.ini` uploads.
- 1 - I added an explicit installer checkbox for importing sensitive secrets from `app.ini` in `templates/install.tmpl`.
- 2 - I extended the installer form, submit pipeline, and final config writer so the optional import reuses `LFS_JWT_SECRET`, `INTERNAL_TOKEN`, and `oauth2.JWT_SECRET` from the uploaded `app.ini` instead of generating new values, including a submit-time fallback that re-reads the uploaded file if the checkbox was enabled after the first auto-import.
- 3 - I finalized secret resolution for both direct values and `LFS_JWT_SECRET_URI` / `INTERNAL_TOKEN_URI` / `JWT_SECRET_URI` file-based references, and added regression coverage for direct imports, URI-based imports, the real `POST /import_app_ini` flow, and the persisted `app.ini` output.
View File
+6
View File
@@ -113,7 +113,13 @@ prime/
/.claude/
/.cursorrules
/.cursor/
# /.configure.sh
# /configure.sh
/.frontend.hash
# /.smart-build.sh
# /smart-build.sh
# /.update-gitea.sh
# /update-gitea.sh
/.goosehints
/.windsurfrules
/.github/copilot-instructions.md
View File
+151
View File
@@ -0,0 +1,151 @@
#!/usr/bin/env bash
set -euo pipefail
# This script safely updates the current local branch on top of the official
# Gitea upstream without losing local work.
# It:
# 1. checks that no git operation is already in progress;
# 2. ensures the upstream remote exists and points to the official repository;
# 3. creates a local backup branch at the current HEAD;
# 4. stashes tracked and untracked local changes;
# 5. fetches the latest upstream changes and rebases the current branch on top
# of upstream/main;
# 6. reapplies the local stash only after a successful rebase.
# If a conflict happens, the backup branch and the stash are both kept so the
# local work can be recovered manually.
BRANCH="${BRANCH:-main}"
REMOTE_NAME="${REMOTE_NAME:-upstream}"
REMOTE_URL="${REMOTE_URL:-https://github.com/go-gitea/gitea.git}"
say() {
printf '%s\n' "$*"
}
die() {
say "ERROR: $*" >&2
exit 1
}
ensure_git_repo() {
git rev-parse --show-toplevel >/dev/null 2>&1 || die "This script must be run inside a git repository."
}
ensure_no_git_operation_in_progress() {
local git_path
for git_path in rebase-merge rebase-apply MERGE_HEAD CHERRY_PICK_HEAD REVERT_HEAD BISECT_LOG; do
if [ -e "$(git rev-parse --git-path "$git_path")" ]; then
die "A git operation is already in progress ($git_path). Finish it before running this script."
fi
done
if [ -n "$(git diff --name-only --diff-filter=U)" ]; then
die "There are unmerged files in the working tree. Resolve them first."
fi
}
ensure_current_branch() {
CURRENT_BRANCH="$(git symbolic-ref --quiet --short HEAD || true)"
[ -n "$CURRENT_BRANCH" ] || die "Detached HEAD is not supported. Check out a branch first."
}
ensure_upstream_remote() {
local current_remote_url
if current_remote_url="$(git remote get-url "$REMOTE_NAME" 2>/dev/null)"; then
if [ "$current_remote_url" != "$REMOTE_URL" ]; then
die "Remote '$REMOTE_NAME' points to '$current_remote_url', expected '$REMOTE_URL'."
fi
return
fi
say "Adding remote '$REMOTE_NAME'..."
git remote add "$REMOTE_NAME" "$REMOTE_URL"
}
create_backup_branch() {
local safe_branch_name timestamp
safe_branch_name="${CURRENT_BRANCH//\//-}"
timestamp="$(date +%Y%m%d-%H%M%S)"
BACKUP_BRANCH="backup/${safe_branch_name}-before-upstream-sync-${timestamp}"
git branch "$BACKUP_BRANCH" HEAD >/dev/null
say "Backup branch created: $BACKUP_BRANCH"
}
stash_local_changes() {
local timestamp
STASH_REF=""
if git diff --quiet && git diff --cached --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]; then
say "No local tracked/untracked changes to stash."
return
fi
timestamp="$(date +%Y%m%d-%H%M%S)"
STASH_NAME="pre-upstream-sync-${CURRENT_BRANCH//\//-}-${timestamp}"
say "Stashing local tracked and untracked changes..."
git stash push --include-untracked --message "$STASH_NAME" >/dev/null
STASH_REF="$(git stash list -1 --format='%gd')"
[ -n "$STASH_REF" ] || die "Failed to locate the created stash entry."
say "Local changes saved in $STASH_REF"
}
fetch_upstream() {
say "Fetching latest changes from $REMOTE_NAME/$BRANCH..."
git fetch --prune "$REMOTE_NAME"
git show-ref --verify --quiet "refs/remotes/$REMOTE_NAME/$BRANCH" || die "Remote branch '$REMOTE_NAME/$BRANCH' was not found."
}
rebase_onto_upstream() {
say "Rebasing '$CURRENT_BRANCH' onto '$REMOTE_NAME/$BRANCH'..."
if git rebase "$REMOTE_NAME/$BRANCH"; then
say "Rebase completed successfully."
return
fi
say "Rebase stopped because of conflicts."
say "Backup branch kept at: $BACKUP_BRANCH"
if [ -n "$STASH_REF" ]; then
say "Local stash kept at: $STASH_REF"
fi
say "Resolve conflicts and run 'git rebase --continue', or abort with 'git rebase --abort'."
exit 1
}
restore_stash() {
if [ -z "$STASH_REF" ]; then
return
fi
say "Restoring stashed local changes from $STASH_REF..."
if git stash apply --index "$STASH_REF"; then
git stash drop "$STASH_REF" >/dev/null
say "Local changes restored successfully."
return
fi
say "Conflicts occurred while restoring local changes."
say "Backup branch kept at: $BACKUP_BRANCH"
say "Stash kept at: $STASH_REF"
say "Resolve the conflicts manually. After that, drop the stash yourself if it is no longer needed."
exit 1
}
main() {
ensure_git_repo
ensure_no_git_operation_in_progress
ensure_current_branch
ensure_upstream_remote
create_backup_branch
stash_local_changes
fetch_upstream
rebase_onto_upstream
restore_stash
say "Project is now synchronized with $REMOTE_NAME/$BRANCH."
say "Safety backup branch available at: $BACKUP_BRANCH"
}
main "$@"
+2
View File
@@ -242,6 +242,8 @@
"install.import_app_ini_desc": "Upload an existing app.ini file to prefill this installer form with the configuration values it already contains.",
"install.import_app_ini_file": "app.ini File",
"install.import_app_ini_helper": "Optional. INI file only, up to %d KB. The imported values are copied into the installer form so you can review and adjust them before saving.",
"install.import_app_ini_sensitive_secrets": "Import sensitive secrets from app.ini",
"install.import_app_ini_sensitive_secrets_helper": "Optional. Also import LFS_JWT_SECRET, INTERNAL_TOKEN, and JWT_SECRET from the uploaded configuration. Use this for restores or migrations where existing signed tokens must continue working.",
"install.import_app_ini_button": "Load app.ini",
"install.import_app_ini_missing": "Please choose an app.ini file to import.",
"install.import_app_ini_read_failed": "Could not read the uploaded app.ini file: %v",
+2
View File
@@ -242,6 +242,8 @@
"install.import_app_ini_desc": "Încarcă un fișier app.ini existent pentru a precompleta acest formular de instalare cu valorile de configurare deja definite în el.",
"install.import_app_ini_file": "Fișier app.ini",
"install.import_app_ini_helper": "Opțional. Doar fișier INI, până la %d KB. Valorile importate sunt copiate în formularul de instalare pentru a le putea verifica și ajusta înainte de salvare.",
"install.import_app_ini_sensitive_secrets": "Importă secretele sensibile din app.ini",
"install.import_app_ini_sensitive_secrets_helper": "Opțional. Importă și LFS_JWT_SECRET, INTERNAL_TOKEN și JWT_SECRET din configurația încărcată. Folosește asta la restaurări sau migrări unde tokenurile semnate existente trebuie să continue să funcționeze.",
"install.import_app_ini_button": "Încarcă app.ini",
"install.import_app_ini_missing": "Alege un fișier app.ini pentru import.",
"install.import_app_ini_read_failed": "Fișierul app.ini încărcat nu a putut fi citit: %v",
+3 -3
View File
@@ -157,8 +157,8 @@
},
"scripts": {
"0-codex-gpt-login": "./.codex_gpt_login.sh",
"1-configure-buid": "./configure.sh",
"2-update-gitea": "./update-gitea.sh",
"3-smart-build": "./smart-build.sh"
"1-configure-buid": "./.configure.sh",
"2-update-gitea": "./.update-gitea.sh",
"3-smart-build": "./.smart-build.sh"
}
}
+113 -20
View File
@@ -12,9 +12,11 @@ import (
"io"
"net/http"
"net/mail"
"net/url"
"os"
"os/exec"
"path/filepath"
"runtime"
"slices"
"strconv"
"strings"
@@ -466,11 +468,59 @@ func populateInstallFormFromConfig(form *forms.InstallForm, cfg setting.ConfigPr
form.EnableUpdateChecker = setting.ConfigSectionKeyBool(securitySec, "ENABLE_UPDATE_CHECKER", form.EnableUpdateChecker)
form.PasswordAlgorithm = setting.ConfigSectionKeyString(securitySec, "PASSWORD_HASH_ALGO", form.PasswordAlgorithm)
form.AdminManagementPolicy = setting.ConfigSectionKeyString(adminSec, "ADMIN_MANAGEMENT_POLICY", form.AdminManagementPolicy)
if form.ImportSensitiveSecrets {
form.ImportedLFSJWTSecret = readImportedSecretValue(serverSec, "LFS_JWT_SECRET_URI", "LFS_JWT_SECRET")
form.ImportedInternalToken = readImportedSecretValue(securitySec, "INTERNAL_TOKEN_URI", "INTERNAL_TOKEN")
form.ImportedOAuth2JWTSecret = readImportedSecretValue(cfg.Section("oauth2"), "JWT_SECRET_URI", "JWT_SECRET")
}
normalizeInstallRegistrationOptions(form)
return form.DbType
}
func readImportedSecretValue(sec setting.ConfigSection, uriKey, verbatimKey string) string {
verbatim := setting.ConfigSectionKeyString(sec, verbatimKey)
if verbatim != "" {
return verbatim
}
uriValue := setting.ConfigSectionKeyString(sec, uriKey)
if uriValue == "" {
return ""
}
parsed, err := url.Parse(uriValue)
if err != nil || parsed.Scheme != "file" {
return ""
}
path := parsed.Path
if parsed.Opaque != "" {
path = parsed.Opaque
}
if parsed.Host != "" {
switch {
case len(parsed.Host) == 2 && parsed.Host[1] == ':':
path = parsed.Host + path
default:
path = "//" + parsed.Host + path
}
}
path, err = url.PathUnescape(path)
if err != nil || path == "" {
return ""
}
if runtime.GOOS == "windows" && strings.HasPrefix(path, "/") && len(path) > 2 && path[2] == ':' {
path = path[1:]
}
data, err := os.ReadFile(path)
if err != nil {
return ""
}
return strings.TrimSpace(string(data))
}
func readImportedInstallConfig(ctx *context.Context) (setting.ConfigProvider, error) {
file, header, err := ctx.Req.FormFile("app_ini_file")
if errors.Is(err, http.ErrMissingFile) {
@@ -500,6 +550,58 @@ func readImportedInstallConfig(ctx *context.Context) (setting.ConfigProvider, er
return cfg, nil
}
func populateInstallSensitiveSecretsFromConfig(form *forms.InstallForm, cfg setting.ConfigProvider) {
if !form.ImportSensitiveSecrets {
return
}
if form.ImportedLFSJWTSecret == "" {
form.ImportedLFSJWTSecret = readImportedSecretValue(cfg.Section("server"), "LFS_JWT_SECRET_URI", "LFS_JWT_SECRET")
}
if form.ImportedInternalToken == "" {
form.ImportedInternalToken = readImportedSecretValue(cfg.Section("security"), "INTERNAL_TOKEN_URI", "INTERNAL_TOKEN")
}
if form.ImportedOAuth2JWTSecret == "" {
form.ImportedOAuth2JWTSecret = readImportedSecretValue(cfg.Section("oauth2"), "JWT_SECRET_URI", "JWT_SECRET")
}
}
func applyInstallSensitiveSecretsToConfig(cfg setting.ConfigProvider, form *forms.InstallForm) error {
if form.LFSRootPath != "" {
if form.ImportSensitiveSecrets && form.ImportedLFSJWTSecret != "" {
cfg.Section("server").DeleteKey("LFS_JWT_SECRET_URI")
cfg.Section("server").Key("LFS_JWT_SECRET").SetValue(form.ImportedLFSJWTSecret)
} else if !cfg.Section("server").HasKey("LFS_JWT_SECRET_URI") {
_, lfsJwtSecret := generate.NewJwtSecretWithBase64()
cfg.Section("server").Key("LFS_JWT_SECRET").SetValue(lfsJwtSecret)
}
}
// the internal token could be read from INTERNAL_TOKEN or INTERNAL_TOKEN_URI (the file is guaranteed to be non-empty)
// if there is no InternalToken, generate one and save to security.INTERNAL_TOKEN
if form.ImportSensitiveSecrets && form.ImportedInternalToken != "" {
cfg.Section("security").DeleteKey("INTERNAL_TOKEN_URI")
cfg.Section("security").Key("INTERNAL_TOKEN").SetValue(form.ImportedInternalToken)
} else if setting.InternalToken == "" {
internalToken, err := generate.NewInternalToken()
if err != nil {
return err
}
cfg.Section("security").Key("INTERNAL_TOKEN").SetValue(internalToken)
}
// FIXME: at the moment, no matter oauth2 is enabled or not, it must generate a "oauth2 JWT_SECRET"
// see the "loadOAuth2From" in "setting/oauth2.go"
if form.ImportSensitiveSecrets && form.ImportedOAuth2JWTSecret != "" {
cfg.Section("oauth2").DeleteKey("JWT_SECRET_URI")
cfg.Section("oauth2").Key("JWT_SECRET").SetValue(form.ImportedOAuth2JWTSecret)
} else if !cfg.Section("oauth2").HasKey("JWT_SECRET") && !cfg.Section("oauth2").HasKey("JWT_SECRET_URI") {
_, jwtSecretBase64 := generate.NewJwtSecretWithBase64()
cfg.Section("oauth2").Key("JWT_SECRET").SetValue(jwtSecretBase64)
}
return nil
}
// Install render installation page
func Install(ctx *context.Context) {
if setting.InstallLock {
@@ -518,6 +620,7 @@ func ImportAppINI(ctx *context.Context) {
}
form, curDBType := newInstallFormFromSettings()
form.ImportSensitiveSecrets = ctx.FormBool("import_sensitive_secrets")
cfg, err := readImportedInstallConfig(ctx)
if err != nil {
ctx.Data["CurDbType"] = curDBType
@@ -700,6 +803,15 @@ func SubmitInstall(ctx *context.Context) {
var err error
form := *web.GetForm(ctx).(*forms.InstallForm)
form.ImportSensitiveSecrets = ctx.FormBool("import_sensitive_secrets")
form.ImportedLFSJWTSecret = ctx.FormString("imported_lfs_jwt_secret")
form.ImportedInternalToken = ctx.FormString("imported_internal_token")
form.ImportedOAuth2JWTSecret = ctx.FormString("imported_o_auth2_jwt_secret")
if form.ImportSensitiveSecrets && (form.ImportedLFSJWTSecret == "" || form.ImportedInternalToken == "" || form.ImportedOAuth2JWTSecret == "") {
if importedCfg, err := readImportedInstallConfig(ctx); err == nil {
populateInstallSensitiveSecretsFromConfig(&form, importedCfg)
}
}
// fix form values
if form.AppURL != "" && form.AppURL[len(form.AppURL)-1] != '/' {
@@ -882,11 +994,6 @@ func SubmitInstall(ctx *context.Context) {
if form.LFSRootPath != "" {
cfg.Section("server").Key("LFS_START_SERVER").SetValue("true")
cfg.Section("lfs").Key("PATH").SetValue(form.LFSRootPath)
if !cfg.Section("server").HasKey("LFS_JWT_SECRET_URI") {
_, lfsJwtSecret := generate.NewJwtSecretWithBase64()
cfg.Section("server").Key("LFS_JWT_SECRET").SetValue(lfsJwtSecret)
}
} else {
cfg.Section("server").Key("LFS_START_SERVER").SetValue("false")
}
@@ -941,24 +1048,10 @@ func SubmitInstall(ctx *context.Context) {
cfg.Section("repository.signing").Key("DEFAULT_TRUST_MODEL").SetValue("committer")
cfg.Section("security").Key("INSTALL_LOCK").SetValue("true")
// the internal token could be read from INTERNAL_TOKEN or INTERNAL_TOKEN_URI (the file is guaranteed to be non-empty)
// if there is no InternalToken, generate one and save to security.INTERNAL_TOKEN
if setting.InternalToken == "" {
var internalToken string
if internalToken, err = generate.NewInternalToken(); err != nil {
if err = applyInstallSensitiveSecretsToConfig(cfg, &form); err != nil {
ctx.RenderWithErrDeprecated(ctx.Tr("install.internal_token_failed", err), tplInstall, &form)
return
}
cfg.Section("security").Key("INTERNAL_TOKEN").SetValue(internalToken)
}
// FIXME: at the moment, no matter oauth2 is enabled or not, it must generate a "oauth2 JWT_SECRET"
// see the "loadOAuth2From" in "setting/oauth2.go"
if !cfg.Section("oauth2").HasKey("JWT_SECRET") && !cfg.Section("oauth2").HasKey("JWT_SECRET_URI") {
_, jwtSecretBase64 := generate.NewJwtSecretWithBase64()
cfg.Section("oauth2").Key("JWT_SECRET").SetValue(jwtSecretBase64)
}
// if there is already a SECRET_KEY, we should not overwrite it, otherwise the encrypted data will not be able to be decrypted
if setting.SecretKey == "" {
+151 -1
View File
@@ -4,13 +4,18 @@
package install
import (
"bytes"
"mime/multipart"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"code.gitea.io/gitea/modules/setting"
"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"
@@ -166,6 +171,151 @@ LANGS = de-DE,en-US
assert.Equal(t, "super_admin_only", form.AdminManagementPolicy)
}
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"`)
}
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")
}
func TestMain(m *testing.M) {
unittest.MainTest(m)
}
+4
View File
@@ -43,6 +43,10 @@ type InstallForm struct {
SMTPFrom string
SMTPUser string `binding:"OmitEmpty;MaxSize(254)" locale:"install.mailer_user"`
SMTPPasswd string
ImportSensitiveSecrets bool `form:"import_sensitive_secrets"`
ImportedLFSJWTSecret string `form:"imported_lfs_jwt_secret"`
ImportedInternalToken string `form:"imported_internal_token"`
ImportedOAuth2JWTSecret string `form:"imported_o_auth2_jwt_secret"`
RegisterConfirm bool
RegisterManualConfirm bool
MailNotify bool
+18 -1
View File
@@ -18,6 +18,16 @@
<input id="app_ini_file" name="app_ini_file" type="file" accept=".ini,text/plain" data-import-action="{{AppSubUrl}}/import_app_ini">
<span class="help">{{ctx.Locale.Tr "install.import_app_ini_helper" .AppINIImportMaxSizeKB}}</span>
</div>
<div class="inline field">
<div class="ui checkbox">
<label for="import_sensitive_secrets">{{ctx.Locale.Tr "install.import_app_ini_sensitive_secrets"}}</label>
<input id="import_sensitive_secrets" name="import_sensitive_secrets" type="checkbox" {{if .import_sensitive_secrets}}checked{{end}}>
</div>
<span class="help">{{ctx.Locale.Tr "install.import_app_ini_sensitive_secrets_helper"}}</span>
</div>
<input type="hidden" name="imported_lfs_jwt_secret" value="{{.imported_lfs_jwt_secret}}">
<input type="hidden" name="imported_internal_token" value="{{.imported_internal_token}}">
<input type="hidden" name="imported_o_auth2_jwt_secret" value="{{.imported_o_auth2_jwt_secret}}">
<!-- Database Settings -->
<h4 class="ui dividing header">{{ctx.Locale.Tr "install.db_title"}}</h4>
@@ -530,7 +540,14 @@
}
if (importedInput.type === 'checkbox') {
const currentCheckbox = form.querySelector(`input[type="checkbox"][name="${CSS.escape(importedInput.name)}"]`);
if (currentCheckbox instanceof HTMLInputElement) currentCheckbox.checked = importedInput.checked;
if (currentCheckbox instanceof HTMLInputElement) {
currentCheckbox.checked = importedInput.checked;
currentCheckbox.dispatchEvent(new Event('change', {bubbles: true}));
const checkboxContainer = currentCheckbox.closest('.ui.checkbox');
if (checkboxContainer) {
checkboxContainer.classList.toggle('checked', importedInput.checked);
}
}
continue;
}
-35
View File
@@ -1,35 +0,0 @@
#!/bin/bash
# Configurare nume ramură (main sau master)
BRANCH="main"
REMOTE_NAME="upstream"
REMOTE_URL="https://github.com/go-gitea/gitea.git"
echo "🔍 Verificare sursă upstream..."
# Verificăm dacă remote-ul 'upstream' există, dacă nu, îl adăugăm
if ! git remote | grep -q "$REMOTE_NAME"; then
echo " Adăugare remote $REMOTE_NAME..."
git remote add $REMOTE_NAME $REMOTE_URL
fi
echo "📦 Salvare modificări locale (Stash)..."
git stash
echo "🔄 Descărcare noutăți de la Gitea oficial..."
git fetch $REMOTE_NAME
echo "🚀 Aplicare noutăți peste modificările tale (Rebase)..."
if git rebase $REMOTE_NAME/$BRANCH; then
echo "✅ Actualizare reușită!"
else
echo "⚠️ CONFLICTE DETECTATE!"
echo "Te rog rezolvă conflictele manual în panoul Source Control din code-server,"
echo "apoi rulează 'git rebase --continue'."
exit 1
fi
echo "📥 Recuperare modificări locale din stash..."
git stash pop
echo "✨ Proiectul este acum la zi cu $REMOTE_NAME/$BRANCH"