vikunja-api/pkg/config/config.go
2022-06-12 18:29:12 +02:00

452 lines
15 KiB
Go

// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public Licensee for more details.
//
// You should have received a copy of the GNU Affero General Public Licensee
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package config
import (
"crypto/rand"
"fmt"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"strings"
"time"
_ "time/tzdata" // Imports time zone data instead of relying on the os
"github.com/spf13/viper"
)
// Key is used as a config key
type Key string
// These constants hold all config value keys
const (
// #nosec
ServiceJWTSecret Key = `service.JWTSecret`
ServiceJWTTTL Key = `service.jwtttl`
ServiceJWTTTLLong Key = `service.jwtttllong`
ServiceInterface Key = `service.interface`
ServiceUnixSocket Key = `service.unixsocket`
ServiceUnixSocketMode Key = `service.unixsocketmode`
ServiceFrontendurl Key = `service.frontendurl`
ServiceEnableCaldav Key = `service.enablecaldav`
ServiceRootpath Key = `service.rootpath`
ServiceStaticpath Key = `service.staticpath`
ServiceMaxItemsPerPage Key = `service.maxitemsperpage`
// Deprecated: Use metrics.enabled
ServiceEnableMetrics Key = `service.enablemetrics`
ServiceMotd Key = `service.motd`
ServiceEnableLinkSharing Key = `service.enablelinksharing`
ServiceEnableRegistration Key = `service.enableregistration`
ServiceEnableTaskAttachments Key = `service.enabletaskattachments`
ServiceTimeZone Key = `service.timezone`
ServiceEnableTaskComments Key = `service.enabletaskcomments`
ServiceEnableTotp Key = `service.enabletotp`
ServiceSentryDsn Key = `service.sentrydsn`
ServiceTestingtoken Key = `service.testingtoken`
ServiceEnableEmailReminders Key = `service.enableemailreminders`
ServiceEnableUserDeletion Key = `service.enableuserdeletion`
ServiceMaxAvatarSize Key = `service.maxavatarsize`
AuthLocalEnabled Key = `auth.local.enabled`
AuthOpenIDEnabled Key = `auth.openid.enabled`
AuthOpenIDRedirectURL Key = `auth.openid.redirecturl`
AuthOpenIDProviders Key = `auth.openid.providers`
LegalImprintURL Key = `legal.imprinturl`
LegalPrivacyURL Key = `legal.privacyurl`
DatabaseType Key = `database.type`
DatabaseHost Key = `database.host`
DatabaseUser Key = `database.user`
DatabasePassword Key = `database.password`
DatabaseDatabase Key = `database.database`
DatabasePath Key = `database.path`
DatabaseMaxOpenConnections Key = `database.maxopenconnections`
DatabaseMaxIdleConnections Key = `database.maxidleconnections`
DatabaseMaxConnectionLifetime Key = `database.maxconnectionlifetime`
DatabaseSslMode Key = `database.sslmode`
DatabaseSslCert Key = `database.sslcert`
DatabaseSslKey Key = `database.sslkey`
DatabaseSslRootCert Key = `database.sslrootcert`
DatabaseTLS Key = `database.tls`
CacheEnabled Key = `cache.enabled`
CacheType Key = `cache.type`
CacheMaxElementSize Key = `cache.maxelementsize`
MailerEnabled Key = `mailer.enabled`
MailerHost Key = `mailer.host`
MailerPort Key = `mailer.port`
MailerUsername Key = `mailer.username`
MailerPassword Key = `mailer.password`
MailerSkipTLSVerify Key = `mailer.skiptlsverify`
MailerFromEmail Key = `mailer.fromemail`
MailerQueuelength Key = `mailer.queuelength`
MailerQueueTimeout Key = `mailer.queuetimeout`
MailerForceSSL Key = `mailer.forcessl`
RedisEnabled Key = `redis.enabled`
RedisHost Key = `redis.host`
RedisPassword Key = `redis.password`
RedisDB Key = `redis.db`
LogEnabled Key = `log.enabled`
LogStandard Key = `log.standard`
LogLevel Key = `log.level`
LogDatabase Key = `log.database`
LogDatabaseLevel Key = `log.databaselevel`
LogHTTP Key = `log.http`
LogEcho Key = `log.echo`
LogPath Key = `log.path`
LogEvents Key = `log.events`
LogEventsLevel Key = `log.eventslevel`
RateLimitEnabled Key = `ratelimit.enabled`
RateLimitKind Key = `ratelimit.kind`
RateLimitPeriod Key = `ratelimit.period`
RateLimitLimit Key = `ratelimit.limit`
RateLimitStore Key = `ratelimit.store`
FilesBasePath Key = `files.basepath`
FilesMaxSize Key = `files.maxsize`
MigrationWunderlistEnable Key = `migration.wunderlist.enable`
MigrationWunderlistClientID Key = `migration.wunderlist.clientid`
MigrationWunderlistClientSecret Key = `migration.wunderlist.clientsecret`
MigrationWunderlistRedirectURL Key = `migration.wunderlist.redirecturl`
MigrationTodoistEnable Key = `migration.todoist.enable`
MigrationTodoistClientID Key = `migration.todoist.clientid`
MigrationTodoistClientSecret Key = `migration.todoist.clientsecret`
MigrationTodoistRedirectURL Key = `migration.todoist.redirecturl`
MigrationTrelloEnable Key = `migration.trello.enable`
MigrationTrelloKey Key = `migration.trello.key`
MigrationTrelloRedirectURL Key = `migration.trello.redirecturl`
MigrationMicrosoftTodoEnable Key = `migration.microsofttodo.enable`
MigrationMicrosoftTodoClientID Key = `migration.microsofttodo.clientid`
MigrationMicrosoftTodoClientSecret Key = `migration.microsofttodo.clientsecret`
MigrationMicrosoftTodoRedirectURL Key = `migration.microsofttodo.redirecturl`
CorsEnable Key = `cors.enable`
CorsOrigins Key = `cors.origins`
CorsMaxAge Key = `cors.maxage`
AvatarGravaterExpiration Key = `avatar.gravatarexpiration`
BackgroundsEnabled Key = `backgrounds.enabled`
BackgroundsUploadEnabled Key = `backgrounds.providers.upload.enabled`
BackgroundsUnsplashEnabled Key = `backgrounds.providers.unsplash.enabled`
BackgroundsUnsplashAccessToken Key = `backgrounds.providers.unsplash.accesstoken`
BackgroundsUnsplashApplicationID Key = `backgrounds.providers.unsplash.applicationid`
KeyvalueType Key = `keyvalue.type`
MetricsEnabled Key = `metrics.enabled`
MetricsUsername Key = `metrics.username`
MetricsPassword Key = `metrics.password`
)
// GetString returns a string config value
func (k Key) GetString() string {
return viper.GetString(string(k))
}
// GetBool returns a bool config value
func (k Key) GetBool() bool {
return viper.GetBool(string(k))
}
// GetInt returns an int config value
func (k Key) GetInt() int {
return viper.GetInt(string(k))
}
// GetInt64 returns an int64 config value
func (k Key) GetInt64() int64 {
return viper.GetInt64(string(k))
}
// GetDuration returns a duration config value
func (k Key) GetDuration() time.Duration {
return viper.GetDuration(string(k))
}
// GetStringSlice returns a string slice from a config option
func (k Key) GetStringSlice() []string {
return viper.GetStringSlice(string(k))
}
// Get returns the raw value from a config option
func (k Key) Get() interface{} {
return viper.Get(string(k))
}
var timezone *time.Location
// GetTimeZone returns the time zone configured for vikunja
// It is a separate function and not done through viper because that makes handling
// it way easier, especially when testing.
func GetTimeZone() *time.Location {
if timezone == nil {
loc, err := time.LoadLocation(ServiceTimeZone.GetString())
if err != nil {
fmt.Printf("Error parsing time zone: %s", err)
os.Exit(1)
}
timezone = loc
}
return timezone
}
// Set sets a value
func (k Key) Set(i interface{}) {
viper.Set(string(k), i)
}
// sets the default config value
func (k Key) setDefault(i interface{}) {
viper.SetDefault(string(k), i)
}
// Tries different methods to figure out the binary folder.
// Copied and adopted from https://github.com/speedata/publisher/commit/3b668668d57edef04ea854d5bbd58f83eb1b799f
func getBinaryDirLocation() string {
// First, check if the standard library gives us the path. This will work 99% of the time.
ex, err := os.Executable()
if err == nil {
return filepath.Dir(ex)
}
// Then check if the binary was run with a full path and use that if that's the case.
if strings.Contains(os.Args[0], "/") {
binDir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
log.Fatal(err)
}
return binDir
}
exeSuffix := ""
if runtime.GOOS == "windows" {
exeSuffix = ".exe"
}
// All else failing, search for a vikunja binary in the current $PATH.
// This can give wrong results.
exeLocation, err := exec.LookPath("vikunja" + exeSuffix)
if err != nil {
log.Fatal(err)
}
return filepath.Dir(exeLocation)
}
// InitDefaultConfig sets default config values
// This is an extra function so we can call it when initializing tests without initializing the full config
func InitDefaultConfig() {
// Service config
random, err := random(32)
if err != nil {
log.Fatal(err.Error())
}
// Service
ServiceJWTSecret.setDefault(random)
ServiceJWTTTL.setDefault(259200) // 72 hours
ServiceJWTTTLLong.setDefault(2592000) // 30 days
ServiceInterface.setDefault(":3456")
ServiceUnixSocket.setDefault("")
ServiceFrontendurl.setDefault("")
ServiceEnableCaldav.setDefault(true)
ServiceRootpath.setDefault(getBinaryDirLocation())
ServiceStaticpath.setDefault("")
ServiceMaxItemsPerPage.setDefault(50)
ServiceEnableMetrics.setDefault(false)
ServiceMotd.setDefault("")
ServiceEnableLinkSharing.setDefault(true)
ServiceEnableRegistration.setDefault(true)
ServiceEnableTaskAttachments.setDefault(true)
ServiceTimeZone.setDefault("GMT")
ServiceEnableTaskComments.setDefault(true)
ServiceEnableTotp.setDefault(true)
ServiceEnableEmailReminders.setDefault(true)
ServiceEnableUserDeletion.setDefault(true)
ServiceMaxAvatarSize.setDefault(1024)
// Auth
AuthLocalEnabled.setDefault(true)
AuthOpenIDEnabled.setDefault(false)
// Database
DatabaseType.setDefault("sqlite")
DatabaseHost.setDefault("localhost")
DatabaseUser.setDefault("vikunja")
DatabasePassword.setDefault("")
DatabaseDatabase.setDefault("vikunja")
DatabasePath.setDefault("./vikunja.db")
DatabaseMaxOpenConnections.setDefault(100)
DatabaseMaxIdleConnections.setDefault(50)
DatabaseMaxConnectionLifetime.setDefault(10000)
DatabaseSslMode.setDefault("disable")
DatabaseSslCert.setDefault("")
DatabaseSslKey.setDefault("")
DatabaseSslRootCert.setDefault("")
DatabaseTLS.setDefault("false")
// Cacher
CacheEnabled.setDefault(false)
CacheType.setDefault("memory")
CacheMaxElementSize.setDefault(1000)
// Mailer
MailerEnabled.setDefault(false)
MailerHost.setDefault("")
MailerPort.setDefault("587")
MailerUsername.setDefault("user")
MailerPassword.setDefault("")
MailerSkipTLSVerify.setDefault(false)
MailerFromEmail.setDefault("mail@vikunja")
MailerQueuelength.setDefault(100)
MailerQueueTimeout.setDefault(30)
MailerForceSSL.setDefault(false)
// Redis
RedisEnabled.setDefault(false)
RedisHost.setDefault("localhost:6379")
RedisPassword.setDefault("")
RedisDB.setDefault(0)
// Logger
LogEnabled.setDefault(true)
LogStandard.setDefault("stdout")
LogLevel.setDefault("INFO")
LogDatabase.setDefault("off")
LogDatabaseLevel.setDefault("WARNING")
LogHTTP.setDefault("stdout")
LogEcho.setDefault("off")
LogPath.setDefault(ServiceRootpath.GetString() + "/logs")
LogEvents.setDefault("stdout")
LogEventsLevel.setDefault("INFO")
// Rate Limit
RateLimitEnabled.setDefault(false)
RateLimitKind.setDefault("user")
RateLimitLimit.setDefault(100)
RateLimitPeriod.setDefault(60)
RateLimitStore.setDefault("memory")
// Files
FilesBasePath.setDefault("files")
FilesMaxSize.setDefault("20MB")
// Cors
CorsEnable.setDefault(true)
CorsOrigins.setDefault([]string{"*"})
CorsMaxAge.setDefault(0)
// Migration
MigrationWunderlistEnable.setDefault(false)
MigrationTodoistEnable.setDefault(false)
MigrationTrelloEnable.setDefault(false)
MigrationMicrosoftTodoEnable.setDefault(false)
// Avatar
AvatarGravaterExpiration.setDefault(3600)
// List Backgrounds
BackgroundsEnabled.setDefault(true)
BackgroundsUploadEnabled.setDefault(true)
BackgroundsUnsplashEnabled.setDefault(false)
// Key Value
KeyvalueType.setDefault("memory")
// Metrics
MetricsEnabled.setDefault(false)
}
// InitConfig initializes the config, sets defaults etc.
func InitConfig() {
// Set defaults
InitDefaultConfig()
// Init checking for environment variables
viper.SetEnvPrefix("vikunja")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
// Load the config file
viper.AddConfigPath(ServiceRootpath.GetString())
viper.AddConfigPath("/etc/vikunja/")
homeDir, err := os.UserHomeDir()
if err != nil {
log.Printf("No home directory found, not using config from ~/.config/vikunja/. Error was: %s\n", err.Error())
} else {
viper.AddConfigPath(path.Join(homeDir, ".config", "vikunja"))
}
viper.AddConfigPath(".")
viper.SetConfigName("config")
err = viper.ReadInConfig()
if viper.ConfigFileUsed() != "" {
log.Printf("Using config file: %s", viper.ConfigFileUsed())
if err != nil {
log.Println(err.Error())
log.Println("Using default config.")
}
} else {
log.Println("No config file found, using default or config from environment variables.")
}
if CacheType.GetString() == "keyvalue" {
CacheType.Set(KeyvalueType.GetString())
}
if RateLimitStore.GetString() == "keyvalue" {
RateLimitStore.Set(KeyvalueType.GetString())
}
if ServiceFrontendurl.GetString() != "" && !strings.HasSuffix(ServiceFrontendurl.GetString(), "/") {
ServiceFrontendurl.Set(ServiceFrontendurl.GetString() + "/")
}
if AuthOpenIDRedirectURL.GetString() == "" {
AuthOpenIDRedirectURL.Set(ServiceFrontendurl.GetString() + "auth/openid/")
}
if MigrationTodoistRedirectURL.GetString() == "" {
MigrationTodoistRedirectURL.Set(ServiceFrontendurl.GetString() + "migrate/todoist")
}
if MigrationTrelloRedirectURL.GetString() == "" {
MigrationTrelloRedirectURL.Set(ServiceFrontendurl.GetString() + "migrate/trello")
}
if MigrationMicrosoftTodoRedirectURL.GetString() == "" {
MigrationMicrosoftTodoRedirectURL.Set(ServiceFrontendurl.GetString() + "migrate/microsoft-todo")
}
if ServiceEnableMetrics.GetBool() {
log.Println("WARNING: service.enablemetrics is deprecated and will be removed in a future release. Please use metrics.enable.")
MetricsEnabled.Set(true)
}
}
func random(length int) (string, error) {
b := make([]byte, length)
if _, err := rand.Read(b); err != nil {
return "", err
}
return fmt.Sprintf("%X", b), nil
}