Files
vikunja/pkg/config/config.go
rhclayto c5969d9898 feat: add configurable gravatar-compatible base URL (#2083)
This adds the ability to set a base URL for a Gravatar-compatible avatar
service (Gravatar itself, or Libravatar, for instance). The default will
be www.gravatar.com, so nothing will change from current behaviour unless
the user explicitly configures another URL.

Resolves #2082
2026-01-13 14:58:08 +01:00

689 lines
24 KiB
Go

// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present 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 License 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 License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package config
import (
"crypto/rand"
"fmt"
"net/url"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"strings"
"time"
_ "time/tzdata" // Imports time zone data instead of relying on the os
"code.vikunja.io/api/pkg/log"
"github.com/c2h5oh/datasize"
"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`
ServicePublicURL Key = `service.publicurl`
ServiceEnableCaldav Key = `service.enablecaldav`
ServiceRootpath Key = `service.rootpath`
ServiceMaxItemsPerPage Key = `service.maxitemsperpage`
ServiceDemoMode Key = `service.demomode`
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`
ServiceTestingtoken Key = `service.testingtoken`
ServiceEnableEmailReminders Key = `service.enableemailreminders`
ServiceEnableUserDeletion Key = `service.enableuserdeletion`
ServiceMaxAvatarSize Key = `service.maxavatarsize`
ServiceAllowIconChanges Key = `service.allowiconchanges`
ServiceCustomLogoURL Key = `service.customlogourl`
ServiceCustomLogoURLDark Key = `service.customlogourldark`
ServiceEnablePublicTeams Key = `service.enablepublicteams`
ServiceBcryptRounds Key = `service.bcryptrounds`
ServiceEnableOpenIDTeamUserOnlySearch Key = `service.enableopenidteamusersearch`
SentryEnabled Key = `sentry.enabled`
SentryDsn Key = `sentry.dsn`
SentryFrontendEnabled Key = `sentry.frontendenabled`
SentryFrontendDsn Key = `sentry.frontenddsn`
AuthLocalEnabled Key = `auth.local.enabled`
AuthOpenIDEnabled Key = `auth.openid.enabled`
AuthOpenIDProviders Key = `auth.openid.providers`
AuthLdapEnabled Key = `auth.ldap.enabled`
AuthLdapHost Key = `auth.ldap.host`
AuthLdapPort Key = `auth.ldap.port`
AuthLdapBaseDN Key = `auth.ldap.basedn`
AuthLdapUserFilter Key = `auth.ldap.userfilter`
AuthLdapUseTLS Key = `auth.ldap.usetls`
AuthLdapVerifyTLS Key = `auth.ldap.verifytls`
AuthLdapBindDN Key = `auth.ldap.binddn`
// #nosec G101
AuthLdapBindPassword Key = `auth.ldap.bindpassword`
AuthLdapGroupSyncEnabled Key = `auth.ldap.groupsyncenabled`
AuthLdapGroupSyncFilter Key = `auth.ldap.groupsyncfilter`
AuthLdapAvatarSyncAttribute Key = `auth.ldap.avatarsyncattribute`
AuthLdapAttributeUsername Key = `auth.ldap.attribute.username`
AuthLdapAttributeEmail Key = `auth.ldap.attribute.email`
AuthLdapAttributeDisplayname Key = `auth.ldap.attribute.displayname`
AuthLdapAttributeMemberID Key = `auth.ldap.attribute.memberid`
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`
DatabaseSchema Key = `database.schema`
TypesenseEnabled Key = `typesense.enabled`
TypesenseURL Key = `typesense.url`
TypesenseAPIKey Key = `typesense.apikey`
MailerEnabled Key = `mailer.enabled`
MailerHost Key = `mailer.host`
MailerPort Key = `mailer.port`
MailerUsername Key = `mailer.username`
MailerPassword Key = `mailer.password`
MailerAuthType Key = `mailer.authtype`
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`
LogFormat Key = `log.format`
LogDatabase Key = `log.database`
LogDatabaseLevel Key = `log.databaselevel`
LogHTTP Key = `log.http`
LogPath Key = `log.path`
LogEvents Key = `log.events`
LogEventsLevel Key = `log.eventslevel`
LogMail Key = `log.mail`
LogMailLevel Key = `log.maillevel`
RateLimitEnabled Key = `ratelimit.enabled`
RateLimitKind Key = `ratelimit.kind`
RateLimitPeriod Key = `ratelimit.period`
RateLimitLimit Key = `ratelimit.limit`
RateLimitStore Key = `ratelimit.store`
RateLimitNoAuthRoutesLimit Key = `ratelimit.noauthlimit`
FilesBasePath Key = `files.basepath`
FilesMaxSize Key = `files.maxsize`
FilesType Key = `files.type`
// S3 Configuration
FilesS3Endpoint Key = `files.s3.endpoint`
FilesS3Bucket Key = `files.s3.bucket`
FilesS3Region Key = `files.s3.region`
FilesS3AccessKey Key = `files.s3.accesskey`
FilesS3SecretKey Key = `files.s3.secretkey`
FilesS3UsePathStyle Key = `files.s3.usepathstyle`
FilesS3TempDir Key = `files.s3.tempdir`
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`
AvatarGravatarBaseURL Key = `avatar.gravatarbaseurl`
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`
DefaultSettingsAvatarProvider Key = `defaultsettings.avatar_provider`
DefaultSettingsAvatarFileID Key = `defaultsettings.avatar_file_id`
DefaultSettingsEmailRemindersEnabled Key = `defaultsettings.email_reminders_enabled`
DefaultSettingsDiscoverableByName Key = `defaultsettings.discoverable_by_name`
DefaultSettingsDiscoverableByEmail Key = `defaultsettings.discoverable_by_email`
DefaultSettingsOverdueTaskRemindersEnabled Key = `defaultsettings.overdue_tasks_reminders_enabled`
DefaultSettingsDefaultProjectID Key = `defaultsettings.default_project_id`
DefaultSettingsWeekStart Key = `defaultsettings.week_start`
DefaultSettingsLanguage Key = `defaultsettings.language`
DefaultSettingsTimezone Key = `defaultsettings.timezone`
DefaultSettingsOverdueTaskRemindersTime Key = `defaultsettings.overdue_tasks_reminders_time`
WebhooksEnabled Key = `webhooks.enabled`
WebhooksTimeoutSeconds Key = `webhooks.timeoutseconds`
WebhooksProxyURL Key = `webhooks.proxyurl`
WebhooksProxyPassword Key = `webhooks.proxypassword`
AutoTLSEnabled Key = `autotls.enabled`
AutoTLSEmail Key = `autotls.email`
AutoTLSRenewBefore Key = `autotls.renewbefore`
PluginsEnabled Key = `plugins.enabled`
PluginsDir Key = `plugins.dir`
)
var maxFileSizeInBytes uint64
// 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 {
log.Fatalf("Error parsing time zone: %s", err)
}
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("")
ServicePublicURL.setDefault("")
ServiceEnableCaldav.setDefault(true)
ServiceRootpath.setDefault(getBinaryDirLocation())
ServiceMaxItemsPerPage.setDefault(50)
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)
ServiceDemoMode.setDefault(false)
ServiceAllowIconChanges.setDefault(true)
ServiceEnablePublicTeams.setDefault(false)
ServiceBcryptRounds.setDefault(11)
ServiceEnableOpenIDTeamUserOnlySearch.setDefault(false)
// Sentry
SentryDsn.setDefault("https://440eedc957d545a795c17bbaf477497c@o1047380.ingest.sentry.io/4504254983634944")
SentryFrontendDsn.setDefault("https://85694a2d757547cbbc90cd4b55c5a18d@o1047380.ingest.sentry.io/6024480")
// Auth
AuthLocalEnabled.setDefault(true)
AuthOpenIDEnabled.setDefault(false)
AuthLdapEnabled.setDefault(false)
AuthLdapHost.setDefault("localhost")
AuthLdapPort.setDefault(389)
AuthLdapUseTLS.setDefault(true)
AuthLdapVerifyTLS.setDefault(true)
AuthLdapGroupSyncEnabled.setDefault(false)
AuthLdapGroupSyncFilter.setDefault("(&(objectclass=*)(|(objectclass=group)(objectclass=groupOfNames)))")
AuthLdapAttributeUsername.setDefault("uid")
AuthLdapAttributeEmail.setDefault("mail")
AuthLdapAttributeDisplayname.setDefault("displayName")
AuthLdapAttributeMemberID.setDefault("member")
// Database
DatabaseType.setDefault("sqlite")
DatabaseHost.setDefault("localhost")
DatabaseUser.setDefault("vikunja")
DatabasePassword.setDefault("")
DatabaseDatabase.setDefault("vikunja")
DatabasePath.setDefault(filepath.Join(ServiceRootpath.GetString(), "vikunja.db"))
DatabaseMaxOpenConnections.setDefault(100)
DatabaseMaxIdleConnections.setDefault(50)
DatabaseMaxConnectionLifetime.setDefault(10000)
DatabaseSslMode.setDefault("disable")
DatabaseSslCert.setDefault("")
DatabaseSslKey.setDefault("")
DatabaseSslRootCert.setDefault("")
DatabaseTLS.setDefault("false")
DatabaseSchema.setDefault("public")
// Typesense
TypesenseEnabled.setDefault(false)
// Mailer
MailerEnabled.setDefault(false)
MailerHost.setDefault("")
MailerPort.setDefault("587")
MailerUsername.setDefault("")
MailerPassword.setDefault("")
MailerSkipTLSVerify.setDefault(false)
MailerFromEmail.setDefault("mail@vikunja")
MailerQueuelength.setDefault(100)
MailerQueueTimeout.setDefault(30)
MailerForceSSL.setDefault(false)
MailerAuthType.setDefault("plain")
// Redis
RedisEnabled.setDefault(false)
RedisHost.setDefault("localhost:6379")
RedisPassword.setDefault("")
RedisDB.setDefault(0)
// Logger
LogEnabled.setDefault(true)
LogStandard.setDefault("stdout")
LogLevel.setDefault("INFO")
LogFormat.setDefault("text")
LogDatabase.setDefault("off")
LogDatabaseLevel.setDefault("WARNING")
LogHTTP.setDefault("stdout")
LogPath.setDefault(ServiceRootpath.GetString() + "/logs")
LogEvents.setDefault("off")
LogEventsLevel.setDefault("INFO")
LogMail.setDefault("off")
LogMailLevel.setDefault("INFO")
// Rate Limit
RateLimitEnabled.setDefault(false)
RateLimitKind.setDefault("user")
RateLimitLimit.setDefault(100)
RateLimitPeriod.setDefault(60)
RateLimitStore.setDefault("memory")
RateLimitNoAuthRoutesLimit.setDefault(10)
// Files
FilesBasePath.setDefault("files")
FilesMaxSize.setDefault("20MB")
FilesType.setDefault("local")
// S3 Configuration
FilesS3Endpoint.setDefault("")
FilesS3Bucket.setDefault("")
FilesS3Region.setDefault("")
FilesS3AccessKey.setDefault("")
FilesS3SecretKey.setDefault("")
FilesS3UsePathStyle.setDefault(false)
FilesS3TempDir.setDefault("")
// Cors
CorsEnable.setDefault(true)
CorsOrigins.setDefault([]string{"http://127.0.0.1:*", "http://localhost:*"})
CorsMaxAge.setDefault(0)
// Migration
MigrationTodoistEnable.setDefault(false)
MigrationTrelloEnable.setDefault(false)
MigrationMicrosoftTodoEnable.setDefault(false)
// Avatar
AvatarGravaterExpiration.setDefault(3600)
AvatarGravatarBaseURL.setDefault("https://www.gravatar.com")
// Project Backgrounds
BackgroundsEnabled.setDefault(true)
BackgroundsUploadEnabled.setDefault(true)
BackgroundsUnsplashEnabled.setDefault(false)
// Key Value
KeyvalueType.setDefault("memory")
// Metrics
MetricsEnabled.setDefault(false)
// Settings
DefaultSettingsAvatarProvider.setDefault("initials")
DefaultSettingsOverdueTaskRemindersEnabled.setDefault(true)
DefaultSettingsOverdueTaskRemindersTime.setDefault("9:00")
// Webhook
WebhooksEnabled.setDefault(true)
WebhooksTimeoutSeconds.setDefault(30)
// AutoTLS
AutoTLSRenewBefore.setDefault("720h") // 30days in hours
// Plugins
PluginsEnabled.setDefault(false)
PluginsDir.setDefault(filepath.Join(ServiceRootpath.GetString(), "plugins"))
}
func GetConfigValueFromFile(configKey string) string {
if !strings.HasSuffix(configKey, ".file") {
configKey += ".file"
}
var valuePath = viper.GetString(configKey)
if valuePath == "" {
return ""
}
if !strings.HasPrefix(valuePath, "/") {
valuePath = path.Join(ServiceRootpath.GetString(), valuePath)
}
contents, err := os.ReadFile(valuePath)
if err == nil {
return strings.Trim(string(contents), "\n")
}
log.Fatalf("Failed to read the config file at %s for key %s: %v", valuePath, configKey, err)
return ""
}
func readConfigValuesFromFiles() {
keys := viper.AllKeys()
for _, key := range keys {
if strings.Contains(key, "auth.openid.providers") {
// Setting openid provider values will remove everything but the value from file
continue
}
// Env is evaluated manually at runtime, so we need to check this for each key
value := GetConfigValueFromFile(key)
if value != "" {
viper.Set(strings.TrimSuffix(key, ".file"), value)
}
}
}
func setConfigFromEnv() error {
envKeys := os.Environ()
configMap := make(map[string]any)
for _, envKeyValue := range envKeys {
keyValue := strings.SplitN(envKeyValue, "=", 2)
if len(keyValue) != 2 {
continue
}
key, value := keyValue[0], keyValue[1]
if strings.HasPrefix(key, "VIKUNJA_") {
formattedKey := strings.ToLower(strings.TrimPrefix(key, "VIKUNJA_"))
keys := strings.Split(formattedKey, "_")
currentMap := configMap
for i, part := range keys {
_, isString := currentMap[part].(string)
if i == len(keys)-1 || isString {
// Set the value at the final level
currentMap[part] = value
} else {
// Check if the key exists at the current level, create a new map if not
if _, exists := currentMap[part]; !exists {
currentMap[part] = make(map[string]any)
}
// Move into the nested map
typed, is := currentMap[part].(map[string]any)
if !is {
log.Errorf("Failed to set config value from environment variable %s: %s, failed on part %s, type is not map, is %T", key, value, part, currentMap[part])
continue
}
currentMap = typed
}
}
}
}
return viper.MergeConfigMap(configMap)
}
// 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()
log.ConfigureStandardLogger(LogEnabled.GetBool(), LogStandard.GetString(), LogPath.GetString(), LogLevel.GetString(), LogFormat.GetString())
// Load the config file
viper.AddConfigPath(ServiceRootpath.GetString())
viper.AddConfigPath("/etc/vikunja/")
homeDir, err := os.UserHomeDir()
if err != nil {
log.Debugf("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.Infof("Using config file: %s", viper.ConfigFileUsed())
if err != nil {
log.Warning(err.Error())
log.Warning("Using default config.")
} else {
log.ConfigureStandardLogger(LogEnabled.GetBool(), LogStandard.GetString(), LogPath.GetString(), LogLevel.GetString(), LogFormat.GetString())
}
} else {
log.Info("No config file found, using default or config from environment variables.")
}
err = setConfigFromEnv()
if err != nil {
log.Warningf("Failed to set config from environment variables: %s", err.Error())
}
readConfigValuesFromFiles()
if _, err := url.ParseRequestURI(AvatarGravatarBaseURL.GetString()); err != nil {
log.Fatalf("Could not parse gravatarbaseurl: %s", err)
}
AvatarGravatarBaseURL.Set(strings.TrimRight(AvatarGravatarBaseURL.GetString(), "/"))
if RateLimitStore.GetString() == "keyvalue" {
RateLimitStore.Set(KeyvalueType.GetString())
}
if CorsEnable.GetBool() && ServicePublicURL.GetString() == "" {
log.Fatalf("service.publicurl is required when cors.enable is true")
}
if ServicePublicURL.GetString() != "" {
if !strings.HasSuffix(ServicePublicURL.GetString(), "/") {
ServicePublicURL.Set(ServicePublicURL.GetString() + "/")
}
parsed, err := url.Parse(ServicePublicURL.GetString())
if err != nil {
log.Fatalf("Could not parse publicurl: %s", err)
}
if parsed.Scheme != "http" && parsed.Scheme != "https" {
log.Fatalf("service.publicurl must include http:// or https:// scheme, got: %s", ServicePublicURL.GetString())
}
}
if MigrationTodoistRedirectURL.GetString() == "" {
MigrationTodoistRedirectURL.Set(ServicePublicURL.GetString() + "migrate/todoist")
}
if MigrationTrelloRedirectURL.GetString() == "" {
MigrationTrelloRedirectURL.Set(ServicePublicURL.GetString() + "migrate/trello")
}
if MigrationMicrosoftTodoRedirectURL.GetString() == "" {
MigrationMicrosoftTodoRedirectURL.Set(ServicePublicURL.GetString() + "migrate/microsoft-todo")
}
if DefaultSettingsTimezone.GetString() == "" {
DefaultSettingsTimezone.Set(ServiceTimeZone.GetString())
}
publicURL := strings.TrimSuffix(ServicePublicURL.GetString(), "/")
CorsOrigins.Set(append(CorsOrigins.GetStringSlice(), publicURL))
err = SetMaxFileSizeMBytesFromString(FilesMaxSize.GetString())
if err != nil {
log.Fatalf("Could not parse files.maxsize: %s", err)
}
}
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
}
func SetMaxFileSizeMBytesFromString(size string) error {
var maxSize datasize.ByteSize
err := maxSize.UnmarshalText([]byte(size))
if err != nil {
return err
}
maxFileSizeInBytes = uint64(maxSize.MBytes())
return nil
}
func GetMaxFileSizeInMBytes() uint64 {
if maxFileSizeInBytes == 0 {
return 20
}
return maxFileSizeInBytes
}