2024-08-17 21:57:14 +02:00
|
|
|
package common
|
|
|
|
|
|
|
|
|
|
import (
|
2025-07-03 11:34:34 -07:00
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
2025-07-27 06:34:23 +02:00
|
|
|
"log/slog"
|
2025-02-22 14:59:00 +01:00
|
|
|
"net/url"
|
2025-07-27 06:34:23 +02:00
|
|
|
"os"
|
2025-07-30 18:59:25 +02:00
|
|
|
"strings"
|
2025-01-19 06:02:07 -06:00
|
|
|
|
2024-08-17 21:57:14 +02:00
|
|
|
"github.com/caarlos0/env/v11"
|
|
|
|
|
_ "github.com/joho/godotenv/autoload"
|
|
|
|
|
)
|
|
|
|
|
|
2025-07-30 18:59:25 +02:00
|
|
|
func resolveStringOrFile(directValue string, filePath string, varName string, trim bool) (string, error) {
|
|
|
|
|
if directValue != "" {
|
|
|
|
|
return directValue, nil
|
|
|
|
|
}
|
|
|
|
|
if filePath != "" {
|
|
|
|
|
content, err := os.ReadFile(filePath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", fmt.Errorf("failed to read secret '%s' from file '%s': %w", varName, filePath, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if trim {
|
|
|
|
|
return strings.TrimSpace(string(content)), nil
|
|
|
|
|
}
|
|
|
|
|
return string(content), nil
|
|
|
|
|
}
|
|
|
|
|
return "", nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-12 17:21:28 +01:00
|
|
|
type DbProvider string
|
|
|
|
|
|
2025-05-05 15:59:44 +02:00
|
|
|
const (
|
|
|
|
|
// TracerName should be passed to otel.Tracer, trace.SpanFromContext when creating custom spans.
|
|
|
|
|
TracerName = "github.com/pocket-id/pocket-id/backend/tracing"
|
|
|
|
|
// MeterName should be passed to otel.Meter when create custom metrics.
|
|
|
|
|
MeterName = "github.com/pocket-id/pocket-id/backend/metrics"
|
|
|
|
|
)
|
|
|
|
|
|
2024-12-12 17:21:28 +01:00
|
|
|
const (
|
2025-07-03 11:34:34 -07:00
|
|
|
DbProviderSqlite DbProvider = "sqlite"
|
|
|
|
|
DbProviderPostgres DbProvider = "postgres"
|
|
|
|
|
MaxMindGeoLiteCityUrl string = "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=%s&suffix=tar.gz"
|
|
|
|
|
defaultSqliteConnString string = "file:data/pocket-id.db?_pragma=journal_mode(WAL)&_pragma=busy_timeout(2500)&_txlock=immediate"
|
2024-12-12 17:21:28 +01:00
|
|
|
)
|
|
|
|
|
|
2024-08-17 21:57:14 +02:00
|
|
|
type EnvConfigSchema struct {
|
2025-07-30 18:59:25 +02:00
|
|
|
AppEnv string `env:"APP_ENV"`
|
|
|
|
|
AppURL string `env:"APP_URL"`
|
|
|
|
|
DbProvider DbProvider `env:"DB_PROVIDER"`
|
|
|
|
|
DbConnectionString string `env:"DB_CONNECTION_STRING"`
|
|
|
|
|
DbConnectionStringFile string `env:"DB_CONNECTION_STRING_FILE"`
|
|
|
|
|
UploadPath string `env:"UPLOAD_PATH"`
|
|
|
|
|
KeysPath string `env:"KEYS_PATH"`
|
|
|
|
|
KeysStorage string `env:"KEYS_STORAGE"`
|
|
|
|
|
EncryptionKey string `env:"ENCRYPTION_KEY"`
|
|
|
|
|
EncryptionKeyFile string `env:"ENCRYPTION_KEY_FILE"`
|
|
|
|
|
Port string `env:"PORT"`
|
|
|
|
|
Host string `env:"HOST"`
|
|
|
|
|
UnixSocket string `env:"UNIX_SOCKET"`
|
|
|
|
|
UnixSocketMode string `env:"UNIX_SOCKET_MODE"`
|
|
|
|
|
MaxMindLicenseKey string `env:"MAXMIND_LICENSE_KEY"`
|
|
|
|
|
MaxMindLicenseKeyFile string `env:"MAXMIND_LICENSE_KEY_FILE"`
|
|
|
|
|
GeoLiteDBPath string `env:"GEOLITE_DB_PATH"`
|
|
|
|
|
GeoLiteDBUrl string `env:"GEOLITE_DB_URL"`
|
|
|
|
|
LocalIPv6Ranges string `env:"LOCAL_IPV6_RANGES"`
|
|
|
|
|
UiConfigDisabled bool `env:"UI_CONFIG_DISABLED"`
|
|
|
|
|
MetricsEnabled bool `env:"METRICS_ENABLED"`
|
|
|
|
|
TracingEnabled bool `env:"TRACING_ENABLED"`
|
|
|
|
|
LogJSON bool `env:"LOG_JSON"`
|
|
|
|
|
TrustProxy bool `env:"TRUST_PROXY"`
|
|
|
|
|
AnalyticsDisabled bool `env:"ANALYTICS_DISABLED"`
|
2024-08-17 21:57:14 +02:00
|
|
|
}
|
|
|
|
|
|
2025-07-03 11:34:34 -07:00
|
|
|
var EnvConfig = defaultConfig()
|
2024-08-17 21:57:14 +02:00
|
|
|
|
|
|
|
|
func init() {
|
2025-07-03 11:34:34 -07:00
|
|
|
err := parseEnvConfig()
|
|
|
|
|
if err != nil {
|
2025-07-27 06:34:23 +02:00
|
|
|
slog.Error("Configuration error", slog.Any("error", err))
|
|
|
|
|
os.Exit(1)
|
2025-07-03 11:34:34 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func defaultConfig() EnvConfigSchema {
|
|
|
|
|
return EnvConfigSchema{
|
|
|
|
|
AppEnv: "production",
|
|
|
|
|
DbProvider: "sqlite",
|
|
|
|
|
DbConnectionString: "",
|
|
|
|
|
UploadPath: "data/uploads",
|
|
|
|
|
KeysPath: "data/keys",
|
|
|
|
|
KeysStorage: "", // "database" or "file"
|
|
|
|
|
EncryptionKey: "",
|
|
|
|
|
AppURL: "http://localhost:1411",
|
|
|
|
|
Port: "1411",
|
|
|
|
|
Host: "0.0.0.0",
|
|
|
|
|
UnixSocket: "",
|
|
|
|
|
UnixSocketMode: "",
|
|
|
|
|
MaxMindLicenseKey: "",
|
|
|
|
|
GeoLiteDBPath: "data/GeoLite2-City.mmdb",
|
|
|
|
|
GeoLiteDBUrl: MaxMindGeoLiteCityUrl,
|
|
|
|
|
LocalIPv6Ranges: "",
|
|
|
|
|
UiConfigDisabled: false,
|
|
|
|
|
MetricsEnabled: false,
|
|
|
|
|
TracingEnabled: false,
|
|
|
|
|
TrustProxy: false,
|
|
|
|
|
AnalyticsDisabled: false,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func parseEnvConfig() error {
|
|
|
|
|
err := env.ParseWithOptions(&EnvConfig, env.Options{})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("error parsing env config: %w", err)
|
2024-08-17 21:57:14 +02:00
|
|
|
}
|
2025-03-13 09:01:15 -07:00
|
|
|
|
2025-07-30 18:59:25 +02:00
|
|
|
// Resolve string/file environment variables
|
|
|
|
|
EnvConfig.DbConnectionString, err = resolveStringOrFile(
|
|
|
|
|
EnvConfig.DbConnectionString,
|
|
|
|
|
EnvConfig.DbConnectionStringFile,
|
|
|
|
|
"DB_CONNECTION_STRING",
|
|
|
|
|
true,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
EnvConfig.DbConnectionStringFile = ""
|
|
|
|
|
|
|
|
|
|
EnvConfig.MaxMindLicenseKey, err = resolveStringOrFile(
|
|
|
|
|
EnvConfig.MaxMindLicenseKey,
|
|
|
|
|
EnvConfig.MaxMindLicenseKeyFile,
|
|
|
|
|
"MAXMIND_LICENSE_KEY",
|
|
|
|
|
true,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
EnvConfig.MaxMindLicenseKeyFile = ""
|
|
|
|
|
|
2024-12-12 17:21:28 +01:00
|
|
|
// Validate the environment variables
|
2025-03-13 09:01:15 -07:00
|
|
|
switch EnvConfig.DbProvider {
|
|
|
|
|
case DbProviderSqlite:
|
2025-03-29 15:12:48 -07:00
|
|
|
if EnvConfig.DbConnectionString == "" {
|
2025-07-03 11:34:34 -07:00
|
|
|
EnvConfig.DbConnectionString = defaultSqliteConnString
|
2025-03-13 09:01:15 -07:00
|
|
|
}
|
|
|
|
|
case DbProviderPostgres:
|
2025-03-29 15:12:48 -07:00
|
|
|
if EnvConfig.DbConnectionString == "" {
|
2025-07-03 11:34:34 -07:00
|
|
|
return errors.New("missing required env var 'DB_CONNECTION_STRING' for Postgres database")
|
2025-03-13 09:01:15 -07:00
|
|
|
}
|
|
|
|
|
default:
|
2025-07-03 11:34:34 -07:00
|
|
|
return errors.New("invalid DB_PROVIDER value. Must be 'sqlite' or 'postgres'")
|
2024-12-12 17:21:28 +01:00
|
|
|
}
|
|
|
|
|
|
2025-02-22 14:59:00 +01:00
|
|
|
parsedAppUrl, err := url.Parse(EnvConfig.AppURL)
|
|
|
|
|
if err != nil {
|
2025-07-03 11:34:34 -07:00
|
|
|
return errors.New("APP_URL is not a valid URL")
|
2025-02-22 14:59:00 +01:00
|
|
|
}
|
|
|
|
|
if parsedAppUrl.Path != "" {
|
2025-07-03 11:34:34 -07:00
|
|
|
return errors.New("APP_URL must not contain a path")
|
2025-02-22 14:59:00 +01:00
|
|
|
}
|
2025-07-03 11:34:34 -07:00
|
|
|
|
|
|
|
|
switch EnvConfig.KeysStorage {
|
|
|
|
|
// KeysStorage defaults to "file" if empty
|
|
|
|
|
case "":
|
|
|
|
|
EnvConfig.KeysStorage = "file"
|
|
|
|
|
case "database":
|
2025-07-30 18:59:25 +02:00
|
|
|
// Resolve encryption key using the same pattern
|
|
|
|
|
encryptionKey, err := resolveStringOrFile(
|
|
|
|
|
EnvConfig.EncryptionKey,
|
|
|
|
|
EnvConfig.EncryptionKeyFile,
|
|
|
|
|
"ENCRYPTION_KEY",
|
|
|
|
|
// Do not trim spaces because the file should be interpreted as binary
|
|
|
|
|
false,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if encryptionKey == "" {
|
2025-07-03 11:34:34 -07:00
|
|
|
return errors.New("ENCRYPTION_KEY or ENCRYPTION_KEY_FILE must be non-empty when KEYS_STORAGE is database")
|
|
|
|
|
}
|
2025-07-30 18:59:25 +02:00
|
|
|
// Update the config with resolved value
|
|
|
|
|
EnvConfig.EncryptionKey = encryptionKey
|
|
|
|
|
EnvConfig.EncryptionKeyFile = ""
|
2025-07-03 11:34:34 -07:00
|
|
|
case "file":
|
|
|
|
|
// All good, these are valid values
|
|
|
|
|
default:
|
|
|
|
|
return fmt.Errorf("invalid value for KEYS_STORAGE: %s", EnvConfig.KeysStorage)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
2024-08-17 21:57:14 +02:00
|
|
|
}
|