|  | 
|  | 1 | +// Copyright 2025 The Gitea Authors. All rights reserved. | 
|  | 2 | +// SPDX-License-Identifier: MIT | 
|  | 3 | + | 
|  | 4 | +package cmd | 
|  | 5 | + | 
|  | 6 | +import ( | 
|  | 7 | +	"context" | 
|  | 8 | +	"errors" | 
|  | 9 | +	"fmt" | 
|  | 10 | +	"os" | 
|  | 11 | + | 
|  | 12 | +	"code.gitea.io/gitea/modules/setting" | 
|  | 13 | + | 
|  | 14 | +	"github.com/urfave/cli/v3" | 
|  | 15 | +) | 
|  | 16 | + | 
|  | 17 | +func cmdConfig() *cli.Command { | 
|  | 18 | +	subcmdConfigEditIni := &cli.Command{ | 
|  | 19 | +		Name:  "edit-ini", | 
|  | 20 | +		Usage: "Load an existing INI file, apply environment variables, keep specified keys, and output to a new INI file.", | 
|  | 21 | +		Description: ` | 
|  | 22 | +Help users to edit the Gitea configuration INI file. | 
|  | 23 | +
 | 
|  | 24 | +# Keep Specified Keys | 
|  | 25 | +
 | 
|  | 26 | +If you need to re-create the configuration file with only a subset of keys, | 
|  | 27 | +you can provide an INI template file for the kept keys and use the "--config-keep-keys" flag. | 
|  | 28 | +For example, if a helm chart needs to reset the settings and only keep SECRET_KEY, | 
|  | 29 | +it can use a template file (only keys take effect, values are ignored): | 
|  | 30 | +
 | 
|  | 31 | +  [security] | 
|  | 32 | +  SECRET_KEY= | 
|  | 33 | +
 | 
|  | 34 | +$ ./gitea config edit-ini --config app-old.ini --config-keep-keys app-keys.ini --out app-new.ini | 
|  | 35 | +
 | 
|  | 36 | +# Map Environment Variables to INI Configuration | 
|  | 37 | +
 | 
|  | 38 | +Environment variables of the form "GITEA__section_name__KEY_NAME" | 
|  | 39 | +will be mapped to the ini section "[section_name]" and the key | 
|  | 40 | +"KEY_NAME" with the value as provided. | 
|  | 41 | +
 | 
|  | 42 | +Environment variables of the form "GITEA__section_name__KEY_NAME__FILE" | 
|  | 43 | +will be mapped to the ini section "[section_name]" and the key | 
|  | 44 | +"KEY_NAME" with the value loaded from the specified file. | 
|  | 45 | +
 | 
|  | 46 | +Environment variable keys can only contain characters "0-9A-Z_", | 
|  | 47 | +if a section or key name contains dot ".", it needs to be escaped as _0x2E_. | 
|  | 48 | +For example, to apply this config: | 
|  | 49 | +
 | 
|  | 50 | +	[git.config] | 
|  | 51 | +	foo.bar=val | 
|  | 52 | +
 | 
|  | 53 | +$ export GITEA__git_0x2E_config__foo_0x2E_bar=val | 
|  | 54 | +
 | 
|  | 55 | +# Put All Together | 
|  | 56 | +
 | 
|  | 57 | +$ ./gitea config edit-ini --config app.ini --config-keep-keys app-keys.ini --apply-env {--in-place|--out app-new.ini} | 
|  | 58 | +`, | 
|  | 59 | +		Flags: []cli.Flag{ | 
|  | 60 | +			// "--config" flag is provided by global flags, and this flag is also used by "environment-to-ini" script wrapper | 
|  | 61 | +			// "--in-place" is also used by "environment-to-ini" script wrapper for its old behavior: always overwrite the existing config file | 
|  | 62 | +			&cli.BoolFlag{ | 
|  | 63 | +				Name:  "in-place", | 
|  | 64 | +				Usage: "Output to the same config file as input. This flag will be ignored if --out is set.", | 
|  | 65 | +			}, | 
|  | 66 | +			&cli.StringFlag{ | 
|  | 67 | +				Name:  "config-keep-keys", | 
|  | 68 | +				Usage: "An INI template file containing keys for keeping. Only the keys defined in the INI template will be kept from old config. If not set, all keys will be kept.", | 
|  | 69 | +			}, | 
|  | 70 | +			&cli.BoolFlag{ | 
|  | 71 | +				Name:  "apply-env", | 
|  | 72 | +				Usage: "Apply all GITEA__* variables from the environment to the config.", | 
|  | 73 | +			}, | 
|  | 74 | +			&cli.StringFlag{ | 
|  | 75 | +				Name:  "out", | 
|  | 76 | +				Usage: "Destination config file to write to.", | 
|  | 77 | +			}, | 
|  | 78 | +		}, | 
|  | 79 | +		Action: runConfigEditIni, | 
|  | 80 | +	} | 
|  | 81 | + | 
|  | 82 | +	return &cli.Command{ | 
|  | 83 | +		Name:  "config", | 
|  | 84 | +		Usage: "Manage Gitea configuration", | 
|  | 85 | +		Commands: []*cli.Command{ | 
|  | 86 | +			subcmdConfigEditIni, | 
|  | 87 | +		}, | 
|  | 88 | +	} | 
|  | 89 | +} | 
|  | 90 | + | 
|  | 91 | +func runConfigEditIni(_ context.Context, c *cli.Command) error { | 
|  | 92 | +	// the config system may change the environment variables, so get a copy first, to be used later | 
|  | 93 | +	env := append([]string{}, os.Environ()...) | 
|  | 94 | + | 
|  | 95 | +	// don't use the guessed setting.CustomConf, instead, require the user to provide --config explicitly | 
|  | 96 | +	if !c.IsSet("config") { | 
|  | 97 | +		return errors.New("flag is required but not set: --config") | 
|  | 98 | +	} | 
|  | 99 | +	configFileIn := c.String("config") | 
|  | 100 | + | 
|  | 101 | +	cfgIn, err := setting.NewConfigProviderFromFile(configFileIn) | 
|  | 102 | +	if err != nil { | 
|  | 103 | +		return fmt.Errorf("failed to load config file %q: %v", configFileIn, err) | 
|  | 104 | +	} | 
|  | 105 | + | 
|  | 106 | +	// determine output config file: use "--out" flag or use "--in-place" flag to overwrite input file | 
|  | 107 | +	inPlace := c.Bool("in-place") | 
|  | 108 | +	configFileOut := c.String("out") | 
|  | 109 | +	if configFileOut == "" { | 
|  | 110 | +		if !inPlace { | 
|  | 111 | +			return errors.New("either --in-place or --out must be specified") | 
|  | 112 | +		} | 
|  | 113 | +		configFileOut = configFileIn // in-place edit | 
|  | 114 | +	} | 
|  | 115 | + | 
|  | 116 | +	needWriteOut := configFileOut != configFileIn | 
|  | 117 | + | 
|  | 118 | +	cfgOut := cfgIn | 
|  | 119 | +	configKeepKeys := c.String("config-keep-keys") | 
|  | 120 | +	if configKeepKeys != "" { | 
|  | 121 | +		needWriteOut = true | 
|  | 122 | +		cfgOut, err = setting.NewConfigProviderFromFile(configKeepKeys) | 
|  | 123 | +		if err != nil { | 
|  | 124 | +			return fmt.Errorf("failed to load config-keep-keys template file %q: %v", configKeepKeys, err) | 
|  | 125 | +		} | 
|  | 126 | + | 
|  | 127 | +		for _, secOut := range cfgOut.Sections() { | 
|  | 128 | +			for _, keyOut := range secOut.Keys() { | 
|  | 129 | +				secIn := cfgIn.Section(secOut.Name()) | 
|  | 130 | +				keyIn := setting.ConfigSectionKey(secIn, keyOut.Name()) | 
|  | 131 | +				if keyIn != nil { | 
|  | 132 | +					keyOut.SetValue(keyIn.String()) | 
|  | 133 | +				} else { | 
|  | 134 | +					secOut.DeleteKey(keyOut.Name()) | 
|  | 135 | +				} | 
|  | 136 | +			} | 
|  | 137 | +			if len(secOut.Keys()) == 0 { | 
|  | 138 | +				cfgOut.DeleteSection(secOut.Name()) | 
|  | 139 | +			} | 
|  | 140 | +		} | 
|  | 141 | +	} | 
|  | 142 | + | 
|  | 143 | +	if c.Bool("apply-env") { | 
|  | 144 | +		if setting.EnvironmentToConfig(cfgOut, env) { | 
|  | 145 | +			needWriteOut = true | 
|  | 146 | +		} | 
|  | 147 | +	} | 
|  | 148 | + | 
|  | 149 | +	if needWriteOut { | 
|  | 150 | +		err = cfgOut.SaveTo(configFileOut) | 
|  | 151 | +		if err != nil { | 
|  | 152 | +			return err | 
|  | 153 | +		} | 
|  | 154 | +	} | 
|  | 155 | +	return nil | 
|  | 156 | +} | 
0 commit comments