Skip to content

Commit

Permalink
Better config (thomiceli#50)
Browse files Browse the repository at this point in the history
  • Loading branch information
thomiceli committed Jun 7, 2023
1 parent c517c2d commit 24e3de8
Show file tree
Hide file tree
Showing 9 changed files with 299 additions and 142 deletions.
74 changes: 54 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ A self-hosted pastebin **powered by Git**. [Try it here](https://opengist.thomic
* [With Docker](#with-docker)
* [From source](#from-source)
* [Configuration](#configuration)
* [Via YAML file](#configuration-via-yaml-file)
* [Via Environment Variables](#configuration-via-environment-variables)
* [Administration](#administration)
* [Use Nginx as a reverse proxy](#use-nginx-as-a-reverse-proxy)
* [Use Fail2ban](#use-fail2ban)
Expand Down Expand Up @@ -53,7 +55,7 @@ A self-hosted pastebin **powered by Git**. [Try it here](https://opengist.thomic

A Docker [image](https://github.com/users/thomiceli/packages/container/package/opengist), available for each release, can be pulled

```
```shell
docker pull ghcr.io/thomiceli/opengist:1
```

Expand All @@ -76,9 +78,6 @@ services:
- "2222:2222" # SSH port, can be removed if you don't use SSH
volumes:
- "$HOME/.opengist:/root/.opengist"
environment:
CONFIG: |
log-level: info
```

### From source
Expand All @@ -96,29 +95,64 @@ Opengist is now running on port 6157, you can browse http:https://localhost:6157

## Configuration

Opengist can be configured using YAML. The full configuration file is [config.yml](config.yml), each default key/value
pair can be overridden.
Opengist provides flexible configuration options through either a YAML file and/or environment variables.
You would only need to specify the configuration options you want to change — for any config option left untouched, Opengist will simply apply the default values.

### With docker

Add a `CONFIG` environment variable in the `docker-compose.yml` file to the `opengist` service :
<details>
<summary>Configuration option list</summary>

| YAML Config Key | Environment Variable | Default value | Description |
|-----------------------|--------------------------|----------------------|-----------------------------------------------------------------------------------------------------------------------------------|
| log-level | OG_LOG_LEVEL | `warn` | Set the log level to one of the following: `trace`, `debug`, `info`, `warn`, `error`, `fatal`, `panic`. |
| external-url | OG_EXTERNAL_URL | none | Public URL for the Git HTTP/SSH connection. If not set, uses the URL from the request. |
| opengist-home | OG_OPENGIST_HOME | home directory | Path to the directory where Opengist stores its data. |
| db-filename | OG_DB_FILENAME | `opengist.db` | Name of the SQLite database file. |
| http.host | OG_HTTP_HOST | `0.0.0.0` | The host on which the HTTP server should bind. |
| http.port | OG_HTTP_PORT | `6157` | The port on which the HTTP server should listen. |
| http.git-enabled | OG_HTTP_GIT_ENABLED | `true` | Enable or disable git operations (clone, pull, push) via HTTP. (`true` or `false`) |
| http.tls-enabled | OG_HTTP_TLS_ENABLED | `false` | Enable or disable TLS for the HTTP server. (`true` or `false`) |
| http.cert-file | OG_HTTP_CERT_FILE | none | Path to the TLS certificate file if TLS is enabled. |
| http.key-file | OG_HTTP_KEY_FILE | none | Path to the TLS key file if TLS is enabled. |
| ssh.git-enabled | OG_SSH_GIT_ENABLED | `true` | Enable or disable git operations (clone, pull, push) via SSH. (`true` or `false`) |
| ssh.host | OG_SSH_HOST | `0.0.0.0` | The host on which the SSH server should bind. |
| ssh.port | OG_SSH_PORT | `2222` | The port on which the SSH server should listen. |
| ssh.external-domain | OG_SSH_EXTERNAL_DOMAIN | none | Public domain for the Git SSH connection, if it has to be different from the HTTP one. If not set, uses the URL from the request. |
| ssh.keygen-executable | OG_SSH_KEYGEN_EXECUTABLE | `ssh-keygen` | Path to the SSH key generation executable. |
| github.client-key | OG_GITHUB_CLIENT_KEY | none | The client key for the GitHub OAuth application. |
| github.secret | OG_GITHUB_SECRET | none | The secret for the GitHub OAuth application. |
| gitea.client-key | OG_GITEA_CLIENT_KEY | none | The client key for the Gitea OAuth application. |
| gitea.secret | OG_GITEA_SECRET | none | The secret for the Gitea OAuth application. |
| gitea.url | OG_GITEA_URL | `https://gitea.com/` | The URL of the Gitea instance. |

```diff
environment:
CONFIG: |
log-level: info
ssh.git-enabled: false
# ...
```
</details>

### With binary
### Configuration via YAML file

Create a `config.yml` file (you can reuse this [one](config.yml)) and run Opengist binary with the `--config` flag :
The configuration file must be specified when launching the application, using the `--config` flag followed by the path to your YAML file.

```shell
./opengist --config /path/to/config.yml
```

You can start by copying and/or modifying the provided [config.yml](config.yml) file.

### Configuration via Environment Variables

Usage with Docker Compose :

```yml
services:
opengist:
# ...
environment:
OG_LOG_LEVEL: "info"
# etc.
```
Usage via command line :

```shell
OG_LOG_LEVEL=info ./opengist
```

## Administration

Expand All @@ -142,7 +176,7 @@ server {

Then run :
```shell
service nginx restart
service nginx restart
```

### Use Fail2ban
Expand Down Expand Up @@ -172,7 +206,7 @@ port = anyport

Then run
```shell
service fail2ban restart
service fail2ban restart
```

## Configure OAuth
Expand Down
158 changes: 105 additions & 53 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"gopkg.in/yaml.v3"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
)
Expand All @@ -18,30 +19,30 @@ var C *config
// Not using nested structs because the library
// doesn't support dot notation in this case sadly
type config struct {
LogLevel string `yaml:"log-level"`
ExternalUrl string `yaml:"external-url"`
OpengistHome string `yaml:"opengist-home"`
DBFilename string `yaml:"db-filename"`

HttpHost string `yaml:"http.host"`
HttpPort string `yaml:"http.port"`
HttpGit bool `yaml:"http.git-enabled"`
HttpTLSEnabled bool `yaml:"http.tls-enabled"`
HttpCertFile string `yaml:"http.cert-file"`
HttpKeyFile string `yaml:"http.key-file"`

SshGit bool `yaml:"ssh.git-enabled"`
SshHost string `yaml:"ssh.host"`
SshPort string `yaml:"ssh.port"`
SshExternalDomain string `yaml:"ssh.external-domain"`
SshKeygen string `yaml:"ssh.keygen-executable"`

GithubClientKey string `yaml:"github.client-key"`
GithubSecret string `yaml:"github.secret"`

GiteaClientKey string `yaml:"gitea.client-key"`
GiteaSecret string `yaml:"gitea.secret"`
GiteaUrl string `yaml:"gitea.url"`
LogLevel string `yaml:"log-level" env:"OG_LOG_LEVEL"`
ExternalUrl string `yaml:"external-url" env:"OG_EXTERNAL_URL"`
OpengistHome string `yaml:"opengist-home" env:"OG_OPENGIST_HOME"`
DBFilename string `yaml:"db-filename" env:"OG_DB_FILENAME"`

HttpHost string `yaml:"http.host" env:"OG_HTTP_HOST"`
HttpPort string `yaml:"http.port" env:"OG_HTTP_PORT"`
HttpGit bool `yaml:"http.git-enabled" env:"OG_HTTP_GIT_ENABLED"`
HttpTLSEnabled bool `yaml:"http.tls-enabled" env:"OG_HTTP_TLS_ENABLED"`
HttpCertFile string `yaml:"http.cert-file" env:"OG_HTTP_CERT_FILE"`
HttpKeyFile string `yaml:"http.key-file" env:"OG_HTTP_KEY_FILE"`

SshGit bool `yaml:"ssh.git-enabled" env:"OG_SSH_GIT_ENABLED"`
SshHost string `yaml:"ssh.host" env:"OG_SSH_HOST"`
SshPort string `yaml:"ssh.port" env:"OG_SSH_PORT"`
SshExternalDomain string `yaml:"ssh.external-domain" env:"OG_SSH_EXTERNAL_DOMAIN"`
SshKeygen string `yaml:"ssh.keygen-executable" env:"OG_SSH_KEYGEN_EXECUTABLE"`

GithubClientKey string `yaml:"github.client-key" env:"OG_GITHUB_CLIENT_KEY"`
GithubSecret string `yaml:"github.secret" env:"OG_GITHUB_SECRET"`

GiteaClientKey string `yaml:"gitea.client-key" env:"OG_GITEA_CLIENT_KEY"`
GiteaSecret string `yaml:"gitea.secret" env:"OG_GITEA_SECRET"`
GiteaUrl string `yaml:"gitea.url" env:"OG_GITEA_URL"`
}

func configWithDefaults() (*config, error) {
Expand Down Expand Up @@ -77,37 +78,12 @@ func InitConfig(configPath string) error {
return err
}

if configPath != "" {
absolutePath, _ := filepath.Abs(configPath)
absolutePath = filepath.Clean(absolutePath)
file, err := os.Open(absolutePath)
if err != nil {
if !os.IsNotExist(err) {
return err
}
fmt.Println("No YML config file found at " + absolutePath)
} else {
fmt.Println("Using config file: " + absolutePath)

// Override default values with values from config.yml
d := yaml.NewDecoder(file)
if err = d.Decode(&c); err != nil {
return err
}
defer file.Close()
}
} else {
fmt.Println("No config file specified. Using default values.")
if err = loadConfigFromYaml(c, configPath); err != nil {
return err
}

// Override default values with environment variables (as yaml)
configEnv := os.Getenv("CONFIG")
if configEnv != "" {
fmt.Println("Using config from environment variable: CONFIG")
d := yaml.NewDecoder(strings.NewReader(configEnv))
if err = d.Decode(&c); err != nil {
return err
}
if err = loadConfigFromEnv(c); err != nil {
return err
}

C = c
Expand Down Expand Up @@ -159,3 +135,79 @@ func GetHomeDir() string {
absolutePath, _ := filepath.Abs(C.OpengistHome)
return filepath.Clean(absolutePath)
}

func loadConfigFromYaml(c *config, configPath string) error {
if configPath != "" {
absolutePath, _ := filepath.Abs(configPath)
absolutePath = filepath.Clean(absolutePath)
file, err := os.Open(absolutePath)
if err != nil {
if !os.IsNotExist(err) {
return err
}
fmt.Println("No YAML config file found at " + absolutePath)
} else {
fmt.Println("Using YAML config file: " + absolutePath)

// Override default values with values from config.yml
d := yaml.NewDecoder(file)
if err = d.Decode(&c); err != nil {
return err
}
defer file.Close()
}
} else {
fmt.Println("No YAML config file specified.")
}

// Override default values with environment variables (as yaml)
configEnv := os.Getenv("CONFIG")
if configEnv != "" {
fmt.Println("Using config from environment variable: CONFIG")
d := yaml.NewDecoder(strings.NewReader(configEnv))
if err := d.Decode(&c); err != nil {
return err
}
}

return nil
}

func loadConfigFromEnv(c *config) error {
v := reflect.ValueOf(c).Elem()
var envVars []string

for i := 0; i < v.NumField(); i++ {
tag := v.Type().Field(i).Tag.Get("env")

if tag == "" {
continue
}

envValue := os.Getenv(strings.ToUpper(tag))
if envValue == "" {
continue
}

switch v.Field(i).Kind() {
case reflect.String:
v.Field(i).SetString(envValue)
case reflect.Bool:
boolVal, err := strconv.ParseBool(envValue)
if err != nil {
return err
}
v.Field(i).SetBool(boolVal)
}

envVars = append(envVars, tag)
}

if len(envVars) > 0 {
fmt.Println("Using environment variables config: " + strings.Join(envVars, ", "))
} else {
fmt.Println("No environment variables config specified.")
}

return nil
}
13 changes: 7 additions & 6 deletions internal/web/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,15 +185,16 @@ func adminSyncReposFromDB(ctx echo.Context) error {
return redirect(ctx, "/admin-panel")
}

func adminSettings(ctx echo.Context) error {
setData(ctx, "title", "Admin Settings")
setData(ctx, "htmlTitle", "Admin Settings - Admin panel")
setData(ctx, "adminHeaderPage", "settings")
func adminConfig(ctx echo.Context) error {
setData(ctx, "title", "Configuration")
setData(ctx, "htmlTitle", "Configuration - Admin panel")
setData(ctx, "adminHeaderPage", "config")
setData(ctx, "c", config.C)

return html(ctx, "admin_settings.html")
return html(ctx, "admin_config.html")
}

func adminSetSetting(ctx echo.Context) error {
func adminSetConfig(ctx echo.Context) error {
key := ctx.FormValue("key")
value := ctx.FormValue("value")

Expand Down
4 changes: 2 additions & 2 deletions internal/web/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,8 @@ func Start() {
g2.POST("/gists/:gist/delete", adminGistDelete)
g2.POST("/sync-fs", adminSyncReposFromFS)
g2.POST("/sync-db", adminSyncReposFromDB)
g2.GET("/settings", adminSettings)
g2.PUT("/set-setting", adminSetSetting)
g2.GET("/configuration", adminConfig)
g2.PUT("/set-config", adminSetConfig)
}

g1.GET("/all", allGists, checkRequireLogin)
Expand Down
2 changes: 1 addition & 1 deletion public/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const setSetting = (key: string, value: string) => {
data.append('key', key);
data.append('value', value);
data.append('_csrf', ((document.getElementsByName('_csrf')[0] as HTMLInputElement).value));
return fetch('/admin-panel/set-setting', {
return fetch('/admin-panel/set-config', {
method: 'PUT',
credentials: 'same-origin',
body: data,
Expand Down
12 changes: 12 additions & 0 deletions public/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,15 @@ table.csv-table thead tr th {
table.csv-table tbody td {
@apply border py-1.5 px-1 border-slate-200 dark:border-slate-800;
}

dl.dl-config {
@apply grid grid-cols-3 text-sm;
}

dl.dl-config dt {
@apply col-span-1 text-gray-700 dark:text-slate-300 font-bold;
}

dl.dl-config dd {
@apply ml-1 col-span-2 break-words;
}
4 changes: 2 additions & 2 deletions templates/base/admin_header.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ <h1 class="text-2xl font-bold leading-tight">Admin panel</h1>
{{ else }} text-gray-600 dark:text-gray-400 hover:text-gray-400 dark:hover:text-slate-300 px-3 py-2 font-medium text-sm rounded-md {{ end }}" aria-current="page">Users</a>
<a href="/admin-panel/gists" class="{{ if eq .adminHeaderPage "gists" }}bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300 px-3 py-2 font-medium text-sm rounded-md
{{ else }} text-gray-600 dark:text-gray-400 hover:text-gray-400 dark:hover:text-slate-300 px-3 py-2 font-medium text-sm rounded-md {{ end }}" aria-current="page">Gists</a>
<a href="/admin-panel/settings" class="{{ if eq .adminHeaderPage "settings" }}bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300 px-3 py-2 font-medium text-sm rounded-md
{{ else }} text-gray-600 dark:text-gray-400 hover:text-gray-400 dark:hover:text-slate-300 px-3 py-2 font-medium text-sm rounded-md {{ end }}" aria-current="page">Admin settings</a>
<a href="/admin-panel/configuration" class="{{ if eq .adminHeaderPage "config" }}bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300 px-3 py-2 font-medium text-sm rounded-md
{{ else }} text-gray-600 dark:text-gray-400 hover:text-gray-400 dark:hover:text-slate-300 px-3 py-2 font-medium text-sm rounded-md {{ end }}" aria-current="page">Configuration</a>
</nav>
</div>
</div>
Expand Down
Loading

0 comments on commit 24e3de8

Please sign in to comment.