Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,16 @@ WORKDIR ${GOPATH}/src/code.gitea.io/gitea
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
&& make clean-all build

# Begin env-to-ini build
RUN go build contrib/environment-to-ini/environment-to-ini.go

# Copy local files
COPY docker/root /tmp/local

# Set permissions
RUN chmod 755 /tmp/local/usr/bin/entrypoint \
/tmp/local/usr/local/bin/gitea \
/tmp/local/usr/local/bin/* \
/tmp/local/etc/s6/gitea/* \
/tmp/local/etc/s6/openssh/* \
/tmp/local/etc/s6/.s6-svscan/* \
/go/src/code.gitea.io/gitea/gitea \
/go/src/code.gitea.io/gitea/environment-to-ini
/go/src/code.gitea.io/gitea/gitea

FROM docker.io/library/alpine:3.22
LABEL maintainer="[email protected]"
Expand Down Expand Up @@ -82,4 +78,3 @@ CMD ["/usr/bin/s6-svscan", "/etc/s6"]

COPY --from=build-env /tmp/local /
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini
11 changes: 2 additions & 9 deletions Dockerfile.rootless
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,12 @@ WORKDIR ${GOPATH}/src/code.gitea.io/gitea
RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \
&& make clean-all build

# Begin env-to-ini build
RUN go build contrib/environment-to-ini/environment-to-ini.go

# Copy local files
COPY docker/rootless /tmp/local

# Set permissions
RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
/tmp/local/usr/local/bin/docker-setup.sh \
/tmp/local/usr/local/bin/gitea \
/go/src/code.gitea.io/gitea/gitea \
/go/src/code.gitea.io/gitea/environment-to-ini
RUN chmod 755 /tmp/local/usr/local/bin/* \
/go/src/code.gitea.io/gitea/gitea

FROM docker.io/library/alpine:3.22
LABEL maintainer="[email protected]"
Expand Down Expand Up @@ -71,7 +65,6 @@ RUN chown git:git /var/lib/gitea /etc/gitea

COPY --from=build-env /tmp/local /
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea
COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini

# git:git
USER 1000:1000
Expand Down
156 changes: 156 additions & 0 deletions cmd/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package cmd

import (
"context"
"errors"
"fmt"
"os"

"code.gitea.io/gitea/modules/setting"

"github.com/urfave/cli/v3"
)

func cmdConfig() *cli.Command {
subcmdConfigEditIni := &cli.Command{
Name: "edit-ini",
Usage: "Load an existing INI file, apply environment variables, keep specified keys, and output to a new INI file.",
Description: `
Help users to edit the Gitea configuration INI file.

# Keep Specified Keys

If you need to re-create the configuration file with only a subset of keys,
you can provide an INI template file for the kept keys and use the "--config-keep-keys" flag.
For example, if a helm chart needs to reset the settings and only keep SECRET_KEY,
it can use a template file (only keys take effect, values are ignored):

[security]
SECRET_KEY=

$ ./gitea config edit-ini --config app-old.ini --config-keep-keys app-keys.ini --out app-new.ini

# Map Environment Variables to INI Configuration

Environment variables of the form "GITEA__section_name__KEY_NAME"
will be mapped to the ini section "[section_name]" and the key
"KEY_NAME" with the value as provided.

Environment variables of the form "GITEA__section_name__KEY_NAME__FILE"
will be mapped to the ini section "[section_name]" and the key
"KEY_NAME" with the value loaded from the specified file.

Environment variable keys can only contain characters "0-9A-Z_",
if a section or key name contains dot ".", it needs to be escaped as _0x2E_.
For example, to apply this config:

[git.config]
foo.bar=val

$ export GITEA__git_0x2E_config__foo_0x2E_bar=val

# Put All Together

$ ./gitea config edit-ini --config app.ini --config-keep-keys app-keys.ini --apply-env {--in-place|--out app-new.ini}
`,
Flags: []cli.Flag{
// "--config" flag is provided by global flags, and this flag is also used by "environment-to-ini" script wrapper
// "--in-place" is also used by "environment-to-ini" script wrapper for its old behavior: always overwrite the existing config file
&cli.BoolFlag{
Name: "in-place",
Usage: "Output to the same config file as input. This flag will be ignored if --out is set.",
},
&cli.StringFlag{
Name: "config-keep-keys",
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.",
},
&cli.BoolFlag{
Name: "apply-env",
Usage: "Apply all GITEA__* variables from the environment to the config.",
},
&cli.StringFlag{
Name: "out",
Usage: "Destination config file to write to.",
},
},
Action: runConfigEditIni,
}

return &cli.Command{
Name: "config",
Usage: "Manage Gitea configuration",
Commands: []*cli.Command{
subcmdConfigEditIni,
},
}
}

func runConfigEditIni(_ context.Context, c *cli.Command) error {
// the config system may change the environment variables, so get a copy first, to be used later
env := append([]string{}, os.Environ()...)

// don't use the guessed setting.CustomConf, instead, require the user to provide --config explicitly
if !c.IsSet("config") {
return errors.New("flag is required but not set: --config")
}
configFileIn := c.String("config")

cfgIn, err := setting.NewConfigProviderFromFile(configFileIn)
if err != nil {
return fmt.Errorf("failed to load config file %q: %v", configFileIn, err)
}

// determine output config file: use "--out" flag or use "--in-place" flag to overwrite input file
inPlace := c.Bool("in-place")
configFileOut := c.String("out")
if configFileOut == "" {
if !inPlace {
return errors.New("either --in-place or --out must be specified")
}
configFileOut = configFileIn // in-place edit
}

needWriteOut := configFileOut != configFileIn

cfgOut := cfgIn
configKeepKeys := c.String("config-keep-keys")
if configKeepKeys != "" {
needWriteOut = true
cfgOut, err = setting.NewConfigProviderFromFile(configKeepKeys)
if err != nil {
return fmt.Errorf("failed to load config-keep-keys template file %q: %v", configKeepKeys, err)
}

for _, secOut := range cfgOut.Sections() {
for _, keyOut := range secOut.Keys() {
secIn := cfgIn.Section(secOut.Name())
keyIn := setting.ConfigSectionKey(secIn, keyOut.Name())
if keyIn != nil {
keyOut.SetValue(keyIn.String())
} else {
secOut.DeleteKey(keyOut.Name())
}
}
if len(secOut.Keys()) == 0 {
cfgOut.DeleteSection(secOut.Name())
}
}
}

if c.Bool("apply-env") {
if setting.EnvironmentToConfig(cfgOut, env) {
needWriteOut = true
}
}

if needWriteOut {
err = cfgOut.SaveTo(configFileOut)
if err != nil {
return err
}
}
return nil
}
85 changes: 85 additions & 0 deletions cmd/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package cmd

import (
"os"
"testing"

"github.com/stretchr/testify/require"
)

func TestConfigEdit(t *testing.T) {
tmpDir := t.TempDir()
configOld := tmpDir + "/app-old.ini"
configTemplate := tmpDir + "/app-template.ini"
_ = os.WriteFile(configOld, []byte(`
[sec]
k1=v1
k2=v2
`), os.ModePerm)

_ = os.WriteFile(configTemplate, []byte(`
[sec]
k1=in-template

[sec2]
k3=v3
`), os.ModePerm)

t.Setenv("GITEA__EnV__KeY", "val")

t.Run("OutputToNewWithEnv", func(t *testing.T) {
configNew := tmpDir + "/app-new.ini"
err := NewMainApp(AppVersion{}).Run(t.Context(), []string{
"./gitea", "--config", configOld,
"config", "edit-ini",
"--apply-env",
"--config-keep-keys", configTemplate,
"--out", configNew,
})
require.NoError(t, err)

// "k1" old value is kept because its key is in the template
// "k2" is removed because it isn't in the template
// "k3" isn't in new config because it isn't in the old config
// [env] is applied from environment variable
data, _ := os.ReadFile(configNew)
require.Equal(t, `[sec]
k1 = v1

[env]
KeY = val
`, string(data))
})

t.Run("OutputToExisting(environment-to-ini)", func(t *testing.T) {
// the legacy "environment-to-ini" (now a wrapper script) behavior:
// if no "--out", then "--in-place" must be used to overwrite the existing "--config" file
err := NewMainApp(AppVersion{}).Run(t.Context(), []string{
"./gitea", "config", "edit-ini",
"--apply-env",
"--config", configOld,
})
require.ErrorContains(t, err, "either --in-place or --out must be specified")

// simulate the "environment-to-ini" behavior with "--in-place"
err = NewMainApp(AppVersion{}).Run(t.Context(), []string{
"./gitea", "config", "edit-ini",
"--in-place",
"--apply-env",
"--config", configOld,
})
require.NoError(t, err)

data, _ := os.ReadFile(configOld)
require.Equal(t, `[sec]
k1 = v1
k2 = v2

[env]
KeY = val
`, string(data))
})
}
1 change: 1 addition & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ func NewMainApp(appVer AppVersion) *cli.Command {

// these sub-commands do not need the config file, and they do not depend on any path or environment variable.
subCmdStandalone := []*cli.Command{
cmdConfig(),
cmdCert(),
CmdGenerate,
CmdDocs,
Expand Down
47 changes: 0 additions & 47 deletions contrib/environment-to-ini/README

This file was deleted.

Loading