package config import ( "errors" "log" "os" "strings" "git.artlef.fr/bibliomane/internal/jwtauth" "github.com/alecthomas/kong" kongtoml "github.com/alecthomas/kong-toml" "github.com/pelletier/go-toml" ) type CLI struct { NoConfigFile bool `short:"C" default:"false" help:"Disable config file creation."` ConfigFilePath string `short:"c" default:"bibliomane.toml" type:"path" help:"Config file path."` DisableStoreJWTKeyInConfig bool `default:"false" help:"Do not store the generated key used for JWT when initializing configuration."` ConfigFile Config `embed:"" prefix:""` } type Config struct { Port string `toml:"port" short:"p" default:"8080" help:"Port to listen on for the server." comment:"Port to listen on for the server."` DatabaseFilePath string `toml:"database-file-path" short:"d" default:"bibliomane.db" type:"path" help:"Path to sqlite database file." comment:"Path to sqlite database file."` DemoDataPath string `toml:"demo-data-path" type:"path" help:"Path to the sql file to load for demo data." comment:"Path to the sql file to load for demo data."` JWTKey string `toml:"jwt-key" help:"Key used to encrypt JWT." comment:"Key used to encrypt the generated JWT. Encoded in base64. If empty a random one will be generated on every restart."` ImageFolderPath string `toml:"image-folder-path" short:"i" default:"img" type:"path" help:"Folder where uploaded files will be stored." comment:"Folder where uploaded files will be stored."` Limit int `toml:"limit" default:"100" help:"A single API call will return at most this number of records." comment:"A single API call will return at most this number of records."` InventaireUrl string `toml:"inventaire-url" default:"https://inventaire.io" help:"An inventaire.io instance URL." comment:"An inventaire.io instance URL."` DisableRegistration bool `toml:"disable-registration" short:"n" default:"false" help:"Disable new account creation." comment:"Disable new account creation."` DemoMode bool `toml:"demo-mode" short:"D" default:"false" help:"Activate demo mode." comment:"Activate demo mode: anyone connecting to the instance will be logged in as a single user."` DemoUsername string `toml:"demo-username" default:"demo" help:"Name of the single user used for the demo." comment:"Name of the single user used for the demo."` AddUser UserListAsStrings `toml:"add-user" short:"a" help:"Add users on startup following htpasswd bcrypt format." comment:"Add users on startup following htpasswd bcrypt format, example: [\"demo:$2y$10$UHR2646SZo2W.Rhna7bn5eWNLXWJZ/Sa3oLd9RlxlXs57Bwp6isOS\",\"user:$2y$10$3WYUp.VDpzJRywtrxO1s/uWfUIKpTE4yh5B1d2RCef3hvczYbEWTC\"]"` } type UserListAsStrings []string func (u UserListAsStrings) Validate() error { for _, s := range u { if strings.Count(s, ":") != 1 { return errors.New("For adding users, please follow the format [username]:[bcrypt hashed password]") } } return nil } func defaultConfig() CLI { c := Config{ Port: "8080", DatabaseFilePath: "bibliomane.db", DemoDataPath: "", JWTKey: "", ImageFolderPath: "img", Limit: 100, InventaireUrl: "https://inventaire.io", DisableRegistration: false, DemoMode: false, DemoUsername: "demo", AddUser: []string{}, } return CLI{NoConfigFile: false, ConfigFilePath: "bibliomane.toml", DisableStoreJWTKeyInConfig: false, ConfigFile: c} } func LoadConfig() Config { var cfg CLI //parse first to get config path kong.Parse(&cfg) configPath := cfg.ConfigFilePath f, err := os.ReadFile(configPath) configFileNotExist := false if err != nil { if errors.Is(err, os.ErrNotExist) { cfg = defaultConfig() configFileNotExist = true } else { log.Fatal(err) } } else { err = toml.Unmarshal(f, &cfg) if err != nil { panic(err) } } //parse in configs and cli kong.Parse(&cfg, kong.Configuration(kongtoml.Loader, configPath)) jwtkey, err := jwtauth.InitKey(cfg.ConfigFile.JWTKey) if err != nil { panic(err) } if !cfg.DisableStoreJWTKeyInConfig { cfg.ConfigFile.JWTKey = jwtkey } if configFileNotExist && !cfg.NoConfigFile { b, err := toml.Marshal(cfg.ConfigFile) if err != nil { panic(err) } err = os.WriteFile(configPath, b, 0666) if err != nil { log.Fatal(err) } } return cfg.ConfigFile } // used for testing func LoadConfigFromFile(configPath string) Config { f, err := os.ReadFile(configPath) if err != nil { log.Fatal(err) } var config Config err = toml.Unmarshal(f, &config) if err != nil { panic(err) } return config }