rework: config and cmd

Renamed `web` command to `server ui` (web is still an alias to ui)

Completely changed the config options and flags

Usage of PersistentFlags now allow clearer `--help`

BREAKING: cmd modified
BREAKING: config overhauled
BREAKING: Bump API to v8
This commit is contained in:
Victor Lacasse-Beaudoin 2024-07-15 16:52:04 -04:00
parent e14ff3d04e
commit eb1982898c
8 changed files with 475 additions and 346 deletions

View file

@ -20,17 +20,18 @@ https://git.agecem.com/agecem/bottin
Remplir .env avec les infos qui seront utilisées pour déployer le container Remplir .env avec les infos qui seront utilisées pour déployer le container
(Remplacer `bottin` par quelque chose de plus sécuritaire) Au minimum, il faut ces 3 entrées:
*Remplacer `bottin` par quelque chose de plus sécuritaire*
```sh ```sh
BOTTIN_API_KEY=bottin BOTTIN_SERVER_DB_DATABASE=bottin
BOTTIN_POSTGRES_DATABASE=bottin BOTTIN_SERVER_DB_PASSWORD=bottin
BOTTIN_POSTGRES_PASSWORD=bottin BOTTIN_SERVER_DB_USER=bottin
BOTTIN_POSTGRES_USER=bottin
BOTTIN_WEB_PASSWORD=bottin
BOTTIN_WEB_USER=bottin
``` ```
*D'autres entrées peuvent être ajoutées, voir `config.go` pour les options*
Déployer avec docker-compose Déployer avec docker-compose
`$ docker-compose up -d` `$ docker-compose up -d`
@ -43,13 +44,13 @@ Pour modifier la configuration du serveur API
`$ docker-compose exec -it api vi /etc/bottin/api.yaml` `$ docker-compose exec -it api vi /etc/bottin/api.yaml`
*Y remplir au minimum le champs `api.key` (string)* *Y remplir au minimum le champs `server.api.key` (string)*
Pour modifier la configuration du client web Pour modifier la configuration du client web
`$ docker-compose exec -it web vi /etc/bottin/web.yaml` `$ docker-compose exec -it ui vi /etc/bottin/ui.yaml`
*Y remplir au minimum les champs `web.api.key` (string), `web.user` (string) et `web.password` (string)* *Y remplir au minimum les champs `server.ui.api.key` (string), `server.ui.user` (string) et `server.ui.password` (string)*
Redémarrer les containers une fois la configuration modifiée Redémarrer les containers une fois la configuration modifiée

View file

@ -34,15 +34,7 @@ func TestAPI(t *testing.T) {
} }
defer httpClient.CloseIdleConnections() defer httpClient.CloseIdleConnections()
var protocol string vokiClient := voki.New(&httpClient, "localhost", cfg.Client.API.Key, cfg.Client.API.Port, cfg.Client.API.Protocol)
switch cfg.API.TLS.Enabled {
case true:
protocol = "https"
case false:
protocol = "http"
}
vokiClient := voki.New(&httpClient, "localhost", cfg.API.Key, cfg.API.Port, protocol)
apiClient := APIClient{vokiClient} apiClient := APIClient{vokiClient}
t.Run("get API health", func(t *testing.T) { t.Run("get API health", func(t *testing.T) {

74
cmd.go
View file

@ -32,6 +32,11 @@ func execute() {
} }
} }
var serverCmd = &cobra.Command{
Use: "server",
Short: "Démarrer serveurs (API ou Web UI)",
}
// apiCmd represents the api command // apiCmd represents the api command
var apiCmd = &cobra.Command{ var apiCmd = &cobra.Command{
Use: "api", Use: "api",
@ -49,10 +54,12 @@ var apiCmd = &cobra.Command{
e.Pre(middleware.AddTrailingSlash()) e.Pre(middleware.AddTrailingSlash())
if cfg.API.Key != "" { if cfg.Server.API.Key != "" {
e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) { e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) {
return subtle.ConstantTimeCompare([]byte(key), []byte(cfg.API.Key)) == 1, nil return subtle.ConstantTimeCompare([]byte(key), []byte(cfg.Server.API.Key)) == 1, nil
})) }))
} else {
log.Println("Server started but no API key (server.api.key) was provided, using empty key (NOT RECOMMENDED FOR PRODUCTION)")
} }
// DataClient // DataClient
@ -63,12 +70,12 @@ var apiCmd = &cobra.Command{
ctx, ctx,
fmt.Sprintf( fmt.Sprintf(
"user=%s password=%s database=%s host=%s port=%d sslmode=%s ", "user=%s password=%s database=%s host=%s port=%d sslmode=%s ",
cfg.DB.User, cfg.Server.API.DB.User,
cfg.DB.Password, cfg.Server.API.DB.Password,
cfg.DB.Database, cfg.Server.API.DB.Database,
cfg.DB.Host, cfg.Server.API.DB.Host,
cfg.DB.Port, cfg.Server.API.DB.Port,
cfg.DB.SSLMode, cfg.Server.API.DB.SSLMode,
)) ))
if err != nil { if err != nil {
log.Fatal("init pgx pool:", err) log.Fatal("init pgx pool:", err)
@ -98,49 +105,50 @@ var apiCmd = &cobra.Command{
/* /*
h := handlers.New(client) h := handlers.New(client)
e.GET("/v7/health/", h.GetHealth) e.GET("/v8/health/", h.GetHealth)
e.POST("/v7/membres/", h.PostMembres) e.POST("/v8/membres/", h.PostMembres)
e.GET("/v7/membres/", h.ListMembres) e.GET("/v8/membres/", h.ListMembres)
e.GET("/v7/membres/:membre_id/", h.ReadMembre) e.GET("/v8/membres/:membre_id/", h.ReadMembre)
e.PUT("/v7/membres/:membre_id/prefered_name/", h.PutMembrePreferedName) e.PUT("/v8/membres/:membre_id/prefered_name/", h.PutMembrePreferedName)
e.POST("/v7/programmes/", h.PostProgrammes) e.POST("/v8/programmes/", h.PostProgrammes)
e.POST("/v7/seed/", h.PostSeed) e.POST("/v8/seed/", h.PostSeed)
*/ */
// Execution // Execution
switch cfg.API.TLS.Enabled { switch cfg.Server.API.TLS.Enabled {
case false: case false:
e.Logger.Fatal( e.Logger.Fatal(
e.Start( e.Start(
fmt.Sprintf(":%d", cfg.API.Port), fmt.Sprintf(":%d", cfg.Server.API.Port),
), ),
) )
case true: case true:
//TODO //TODO
log.Printf("dbg: certfile='%s' keyfile='%s'", cfg.API.TLS.Certfile, cfg.API.TLS.Keyfile) log.Printf("dbg: certfile='%s' keyfile='%s'", cfg.Server.API.TLS.Certfile, cfg.Server.API.TLS.Keyfile)
e.Logger.Fatal( e.Logger.Fatal(
e.StartTLS( e.StartTLS(
fmt.Sprintf(":%d", cfg.API.Port), fmt.Sprintf(":%d", cfg.Server.API.Port),
cfg.API.TLS.Certfile, cfg.Server.API.TLS.Certfile,
cfg.API.TLS.Keyfile, cfg.Server.API.TLS.Keyfile,
), ),
) )
} }
}, },
} }
// webCmd represents the web command // uiCmd represents the ui command
var webCmd = &cobra.Command{ var uiCmd = &cobra.Command{
Use: "web", Use: "ui",
Short: "Démarrer le client web", Aliases: []string{"web", "interface"},
Args: cobra.ExactArgs(0), Short: "Démarrer l'interface Web UI",
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
// Parse config // Parse config
var cfg Config var cfg Config
@ -157,8 +165,8 @@ var webCmd = &cobra.Command{
// Auth // Auth
e.Use(middleware.BasicAuth(func(user, password string, c echo.Context) (bool, error) { e.Use(middleware.BasicAuth(func(user, password string, c echo.Context) (bool, error) {
usersMatch := subtle.ConstantTimeCompare([]byte(user), []byte(cfg.Web.User)) == 1 usersMatch := subtle.ConstantTimeCompare([]byte(user), []byte(cfg.Server.UI.User)) == 1
passwordsMatch := subtle.ConstantTimeCompare([]byte(password), []byte(cfg.Web.Password)) == 1 passwordsMatch := subtle.ConstantTimeCompare([]byte(password), []byte(cfg.Server.UI.Password)) == 1
return usersMatch && passwordsMatch, nil return usersMatch && passwordsMatch, nil
})) }))
@ -170,10 +178,10 @@ var webCmd = &cobra.Command{
// API Client // API Client
apiClient := APIClient{voki.New( apiClient := APIClient{voki.New(
http.DefaultClient, http.DefaultClient,
cfg.Web.API.Host, cfg.Server.UI.API.Host,
cfg.Web.API.Key, cfg.Server.UI.API.Key,
cfg.Web.API.Port, cfg.Server.UI.API.Port,
cfg.Web.API.Protocol, cfg.Server.UI.API.Protocol,
)} )}
defer apiClient.Voki.CloseIdleConnections() defer apiClient.Voki.CloseIdleConnections()
@ -239,6 +247,6 @@ Programme: [%s] %s
// Execution // Execution
e.Logger.Fatal(e.Start( e.Logger.Fatal(e.Start(
fmt.Sprintf(":%d", cfg.Web.Port))) fmt.Sprintf(":%d", cfg.Server.UI.Port)))
}, },
} }

664
config.go
View file

@ -10,278 +10,52 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
) )
const (
ViperAPITLSEnabled string = "api.tls.enabled"
FlagAPITLSEnabled string = "api-tls-enabled"
DefaultAPITLSEnabled bool = false
DescriptionAPITLSEnabled string = "Whether to use TLS or not. Requires certificate and private key files."
ViperAPITLSCertfile string = "api.tls.certfile"
FlagAPITLSCertfile string = "api-tls-certfile"
DefaultAPITLSCertfile string = "/etc/bottin/cert.pem"
DescriptionAPITLSCertfile string = "Path to TLS certificate file"
ViperAPITLSKeyfile string = "api.tls.keyfile"
FlagAPITLSKeyfile string = "api-tls-keyfile"
DefaultAPITLSKeyfile string = "/etc/bottin/key.pem"
DescriptionAPITLSKeyFile string = "Path to TLS private key file"
ViperAPIPort string = "api.port"
FlagAPIPort string = "api-port"
DefaultAPIPort int = 1312
DescriptionAPIPort string = "API server port"
ViperAPIKey string = "api.key"
FlagAPIKey string = "api-key"
DefaultAPIKey string = "bottin"
DescriptionAPIKey string = "API server key. Leave empty for no key auth (not recommended)"
ViperDBDatabase string = "db.database"
FlagDBDatabase string = "db-database"
DefaultDBDatabase string = "bottin"
DescriptionDBDatabase string = "Postgres database"
ViperDBSSLMode string = "db.sslmode"
FlagDBSSLMode string = "db-sslmode"
DefaultDBSSLMode string = "prefer"
DescriptionDBSSLMode string = "Postgres sslmode"
ViperDBHost string = "db.host"
FlagDBHost string = "db-host"
DefaultDBHost string = "db"
DescriptionDBHost string = "Postgres host"
ViperDBPassword string = "db.password"
FlagDBPassword string = "db-password"
DefaultDBPassword string = "bottin"
DescriptionDBPassword string = "Postgres password"
ViperDBPort string = "db.port"
FlagDBPort string = "db-port"
DefaultDBPort int = 5432
DescriptionDBPort string = "Postgres port"
ViperDBUser string = "db.user"
FlagDBUser string = "db-user"
DefaultDBUser string = "bottin"
DescriptionDBUser string = "Postgres user"
ViperWebUser string = "web.user"
FlagWebUser string = "web-user"
DefaultWebUser string = "bottin"
DescriptionWebUser string = "Web client basic auth user"
ViperWebPassword string = "web.password"
FlagWebPassword string = "web-password"
DefaultWebPassword string = "bottin"
DescriptionWebPassword string = "Web client basic auth password"
ViperWebPort string = "web.port"
FlagWebPort string = "web-port"
DefaultWebPort int = 2312
DescriptionWebPort string = "Web client port"
ViperWebAPIHost string = "web.api.host"
FlagWebAPIHost string = "web-api-host"
DefaultWebAPIHost string = "api"
DescriptionWebAPIHost string = "Target API server host"
ViperWebAPIKey string = "web.api.key"
FlagWebAPIKey string = "web-api-key"
DefaultWebAPIKey string = "bottin"
DescriptionWebAPIKey string = "Target API server key"
ViperWebAPIPort string = "web.api.port"
FlagWebAPIPort string = "web-api-port"
DefaultWebAPIPort int = 1312
DescriptionWebAPIPort string = "Target API server port"
ViperWebAPIProtocol string = "web.api.protocol"
FlagWebAPIProtocol string = "web-api-protocol"
DefaultWebAPIProtocol string = "http"
DescriptionWebAPIProtocol string = "Target API server protocol (http/https)"
)
type Config struct { type Config struct {
API struct { Client struct {
TLS struct { API struct {
Enabled bool `yaml:"enabled"`
// Path to file containing TLS certificate
Certfile string `yaml:"certfile"`
// Path to file containing TLS private key
Keyfile string `yaml:"keyfile"`
} `yaml:"tls"`
Port int `yaml:"port"`
Key string `yaml:"key"`
} `yaml:"api"`
DB struct {
Database string `yaml:"database"`
Host string `yaml:"host"`
SSLMode string `yaml:"sslmode"`
Password string `yaml:"password"`
Port int `yaml:"port"`
User string `yaml:"user"`
} `yaml:"db"`
Web struct {
User string `yaml:"user"`
Password string `yaml:"password"`
Port int `yaml:"port"`
API struct {
Host string `yaml:"host"` Host string `yaml:"host"`
Key string `yaml:"key"` Key string `yaml:"key"`
Port int `yaml:"port"` Port int `yaml:"port"`
Protocol string `yaml:"protocol"` Protocol string `yaml:"protocol"`
} `yaml:"api"` } `yaml:"api"`
} `yaml:"web"` } `yaml:"client"`
}
// DefaultConfig returns a Config filled with the default values from the Server struct {
// `Default*` constants defined in this file. API struct {
func DefaultConfig() (cfg Config) { DB struct {
cfg.API.TLS.Enabled = DefaultAPITLSEnabled Database string `yaml:"database"`
cfg.API.TLS.Certfile = DefaultAPITLSCertfile Host string `yaml:"host"`
cfg.API.TLS.Keyfile = DefaultAPITLSKeyfile Password string `yaml:"password"`
cfg.API.Port = DefaultAPIPort Port int `yaml:"port"`
cfg.API.Key = DefaultAPIKey SSLMode string `yaml:"sslmode"`
cfg.DB.Database = DefaultDBDatabase User string `yaml:"user"`
cfg.DB.Host = DefaultDBHost } `yaml:"db"`
cfg.DB.SSLMode = DefaultDBSSLMode Host string `yaml:"host"`
cfg.DB.Password = DefaultDBPassword Key string `yaml:"key"`
cfg.DB.Port = DefaultDBPort Port int `yaml:"port"`
cfg.DB.User = DefaultDBUser TLS struct {
cfg.Web.User = DefaultWebUser Enabled bool `yaml:"enabled"`
cfg.Web.Password = DefaultWebPassword Certfile string `yaml:"certfile"`
cfg.Web.Port = DefaultWebPort Keyfile string `yaml:"keyfile"`
cfg.Web.API.Host = DefaultWebAPIHost } `yaml:"tls"`
cfg.Web.API.Key = DefaultWebAPIKey } `yaml:"api"`
cfg.Web.API.Port = DefaultWebAPIPort UI struct {
cfg.Web.API.Protocol = DefaultWebAPIProtocol API struct {
return Host string `yaml:"host"`
} Key string `yaml:"key"`
Port int `yaml:"port"`
func init() { Protocol string `yaml:"protocol"`
// rootCmd } `yaml:"api"`
Password string `yaml:"password"`
cobra.OnInitialize(initConfig) Port int `yaml:"port"`
TLS struct {
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.bottin.yaml)") Enabled bool `yaml:"enabled"`
Certfile string `yaml:"certfile"`
// apiCmd Keyfile string `yaml:"keyfile"`
} `yaml:"tls"`
rootCmd.AddCommand(apiCmd) User string `yaml:"user"`
} `yaml:"ui"`
// api.tls.enabled } `yaml:"server"`
apiCmd.Flags().Bool(FlagAPITLSEnabled, DefaultAPITLSEnabled, DescriptionAPITLSEnabled)
if err := viper.BindPFlag(ViperAPITLSEnabled, apiCmd.Flags().Lookup(FlagAPITLSEnabled)); err != nil {
log.Fatal(err)
}
// api.tls.certfile
apiCmd.Flags().String(FlagAPITLSCertfile, DefaultAPITLSCertfile, DescriptionAPITLSCertfile)
if err := viper.BindPFlag(ViperAPITLSCertfile, apiCmd.Flags().Lookup(FlagAPITLSCertfile)); err != nil {
log.Fatal(err)
}
// api.tls.keyfile
apiCmd.Flags().String(FlagAPITLSKeyfile, DefaultAPITLSKeyfile, DescriptionAPITLSKeyFile)
if err := viper.BindPFlag(ViperAPITLSKeyfile, apiCmd.Flags().Lookup(FlagAPITLSKeyfile)); err != nil {
log.Fatal(err)
}
// api.key
apiCmd.Flags().String(FlagAPIKey, DefaultAPIKey, DescriptionAPIKey)
if err := viper.BindPFlag(ViperAPIKey, apiCmd.Flags().Lookup(FlagAPIKey)); err != nil {
log.Fatal(err)
}
// api.port
apiCmd.Flags().Int(FlagAPIPort, DefaultAPIPort, DescriptionAPIPort)
if err := viper.BindPFlag(ViperAPIPort, apiCmd.Flags().Lookup(FlagAPIPort)); err != nil {
log.Fatal(err)
}
// db.database
apiCmd.Flags().String(FlagDBDatabase, DefaultDBDatabase, DescriptionDBDatabase)
if err := viper.BindPFlag(ViperDBDatabase, apiCmd.Flags().Lookup(FlagDBDatabase)); err != nil {
log.Fatal(err)
}
// db.sslmode
apiCmd.Flags().String(FlagDBSSLMode, DefaultDBSSLMode, DescriptionDBSSLMode)
if err := viper.BindPFlag(ViperDBSSLMode, apiCmd.Flags().Lookup(FlagDBSSLMode)); err != nil {
log.Fatal(err)
}
// db.host
apiCmd.Flags().String(FlagDBHost, DefaultDBHost, DescriptionDBHost)
if err := viper.BindPFlag(ViperDBHost, apiCmd.Flags().Lookup(FlagDBHost)); err != nil {
log.Fatal(err)
}
// db.password
apiCmd.Flags().String(FlagDBPassword, DefaultDBPassword, DescriptionDBPassword)
if err := viper.BindPFlag(ViperDBPassword, apiCmd.Flags().Lookup(FlagDBPassword)); err != nil {
log.Fatal(err)
}
// db.port
apiCmd.Flags().Int(FlagDBPort, DefaultDBPort, DescriptionDBPort)
if err := viper.BindPFlag(ViperDBPort, apiCmd.Flags().Lookup(FlagDBPort)); err != nil {
log.Fatal(err)
}
// db.user
apiCmd.Flags().String(FlagDBUser, DefaultDBUser, DescriptionDBUser)
if err := viper.BindPFlag(ViperDBUser, apiCmd.Flags().Lookup(FlagDBUser)); err != nil {
log.Fatal(err)
}
// WebCmd
rootCmd.AddCommand(webCmd)
// web.api.host
webCmd.Flags().String(FlagWebAPIHost, DefaultWebAPIHost, DescriptionWebAPIHost)
if err := viper.BindPFlag(ViperWebAPIHost, webCmd.Flags().Lookup(FlagWebAPIHost)); err != nil {
log.Fatal(err)
}
// web.api.key
webCmd.Flags().String(FlagWebAPIKey, DefaultWebAPIKey, DescriptionWebAPIKey)
if err := viper.BindPFlag(ViperWebAPIKey, webCmd.Flags().Lookup(FlagWebAPIKey)); err != nil {
log.Fatal(err)
}
// web.api.protocol
webCmd.Flags().String(FlagWebAPIProtocol, DefaultWebAPIProtocol, DescriptionWebAPIProtocol)
if err := viper.BindPFlag(ViperWebAPIProtocol, webCmd.Flags().Lookup(FlagWebAPIProtocol)); err != nil {
log.Fatal(err)
}
// web.api.port
webCmd.Flags().Int(FlagWebAPIPort, DefaultWebAPIPort, DescriptionWebAPIPort)
if err := viper.BindPFlag(ViperWebAPIPort, webCmd.Flags().Lookup(FlagWebAPIPort)); err != nil {
log.Fatal(err)
}
// web.password
webCmd.Flags().String(FlagWebPassword, DefaultWebPassword, DescriptionWebPassword)
if err := viper.BindPFlag(ViperWebPassword, webCmd.Flags().Lookup(FlagWebPassword)); err != nil {
log.Fatal(err)
}
// web.port
webCmd.Flags().Int(FlagWebPort, DefaultWebPort, DescriptionWebPort)
if err := viper.BindPFlag(ViperWebPort, webCmd.Flags().Lookup(FlagWebPort)); err != nil {
log.Fatal(err)
}
// web.user
webCmd.Flags().String(FlagWebUser, DefaultWebUser, DescriptionWebUser)
if err := viper.BindPFlag(ViperWebUser, webCmd.Flags().Lookup(FlagWebUser)); err != nil {
log.Fatal(err)
}
} }
var cfgFile string var cfgFile string
@ -311,3 +85,363 @@ func initConfig() {
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
} }
} }
func init() {
// rootCmd
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.bottin.yaml)")
// client.api.host
rootCmd.PersistentFlags().String(
"client-api-host",
"api",
"API server host",
)
if err := viper.BindPFlag(
"client.api.host",
rootCmd.PersistentFlags().Lookup("client-api-host"),
); err != nil {
log.Fatal(err)
}
// client.api.key
rootCmd.PersistentFlags().String(
"client-api-key",
"bottin",
"API server key",
)
if err := viper.BindPFlag(
"client.api.key",
rootCmd.PersistentFlags().Lookup("client-api-key"),
); err != nil {
log.Fatal(err)
}
// client.api.port
rootCmd.PersistentFlags().Int(
"client-api-port",
1312,
"API server port",
)
if err := viper.BindPFlag(
"client.api.port",
rootCmd.PersistentFlags().Lookup("client-api-port"),
); err != nil {
log.Fatal(err)
}
// client.api.protocol
rootCmd.PersistentFlags().String(
"client-api-protocol",
"https",
"API server protocol",
)
if err := viper.BindPFlag(
"client.api.protocol",
rootCmd.PersistentFlags().Lookup("client-api-protocol"),
); err != nil {
log.Fatal(err)
}
// server
rootCmd.AddCommand(serverCmd)
// server api
serverCmd.AddCommand(apiCmd)
// server api db
// server.api.db.database
apiCmd.PersistentFlags().String(
"server-api-db-database",
"bottin",
"Postgres database name",
)
if err := viper.BindPFlag(
"server.api.db.database",
apiCmd.PersistentFlags().Lookup("server-api-db-database"),
); err != nil {
log.Fatal(err)
}
// server.api.db.host
apiCmd.PersistentFlags().String(
"server-api-db-host",
"db",
"Postgres host name",
)
if err := viper.BindPFlag(
"server.api.db.host",
apiCmd.PersistentFlags().Lookup("server-api-db-host"),
); err != nil {
log.Fatal(err)
}
// server.api.db.password
apiCmd.PersistentFlags().String(
"server-api-db-password",
"bottin",
"Postgres password",
)
if err := viper.BindPFlag(
"server.api.db.password",
apiCmd.PersistentFlags().Lookup("server-api-db-password"),
); err != nil {
log.Fatal(err)
}
// server.api.db.port
apiCmd.PersistentFlags().Int(
"server-api-db-port",
5432,
"Postgres port",
)
if err := viper.BindPFlag(
"server.api.db.port",
apiCmd.PersistentFlags().Lookup("server-api-db-port"),
); err != nil {
log.Fatal(err)
}
// server.api.db.sslmode
apiCmd.PersistentFlags().String(
"server-api-db-sslmode",
"prefer",
"Postgres sslmode",
)
if err := viper.BindPFlag(
"server.api.db.sslmode",
apiCmd.PersistentFlags().Lookup("server-api-db-sslmode"),
); err != nil {
log.Fatal(err)
}
// server.api.db.user
apiCmd.PersistentFlags().String(
"server-api-db-user",
"bottin",
"Postgres user name",
)
if err := viper.BindPFlag(
"server.api.db.user",
apiCmd.PersistentFlags().Lookup("server-api-db-user"),
); err != nil {
log.Fatal(err)
}
// server.api.host
apiCmd.PersistentFlags().String(
"server-api-host",
"",
"API server hostname or IP to answer on (empty = any)",
)
if err := viper.BindPFlag(
"server.api.host",
apiCmd.PersistentFlags().Lookup("server-api-host"),
); err != nil {
log.Fatal(err)
}
// server.api.key
apiCmd.PersistentFlags().String(
"server-api-key",
"bottin",
"API server key",
)
if err := viper.BindPFlag(
"server.api.key",
apiCmd.PersistentFlags().Lookup("server-api-key"),
); err != nil {
log.Fatal(err)
}
// server.api.port
apiCmd.PersistentFlags().Int(
"server-api-port",
1312,
"API server port",
)
if err := viper.BindPFlag(
"server.api.port",
apiCmd.PersistentFlags().Lookup("server-api-port"),
); err != nil {
log.Fatal(err)
}
// server api tls
// server.api.tls.enabled
apiCmd.PersistentFlags().Bool(
"server-api-tls-enabled",
true,
"Use TLS for API server connections (requires certfile and keyfile)",
)
if err := viper.BindPFlag(
"server.api.tls.enabled",
apiCmd.PersistentFlags().Lookup("server-api-tls-enabled"),
); err != nil {
log.Fatal(err)
}
// server.api.tls.certfile
apiCmd.PersistentFlags().String(
"server-api-tls-certfile",
"/etc/bottin/cert.pem",
"Path to certificate file",
)
if err := viper.BindPFlag(
"server.api.tls.certfile",
apiCmd.PersistentFlags().Lookup("server-api-tls-certfile"),
); err != nil {
log.Fatal(err)
}
// server.api.tls.keyfile
apiCmd.PersistentFlags().String(
"server-api-tls-keyfile",
"/etc/bottin/key.pem",
"Path to private key file",
)
if err := viper.BindPFlag(
"server.api.tls.keyfile",
apiCmd.PersistentFlags().Lookup("server-api-tls-keyfile"),
); err != nil {
log.Fatal(err)
}
// server ui
serverCmd.AddCommand(uiCmd)
// server ui api
// server.ui.api.host
uiCmd.PersistentFlags().String(
"server-ui-api-host",
"api",
"Web UI backend API server host name",
)
if err := viper.BindPFlag(
"server.ui.api.host",
uiCmd.PersistentFlags().Lookup("server-ui-api-host"),
); err != nil {
log.Fatal(err)
}
// server.ui.api.key
uiCmd.PersistentFlags().String(
"server-ui-api-key",
"bottin",
"Web UI backend API server key",
)
if err := viper.BindPFlag(
"server.ui.api.key",
uiCmd.PersistentFlags().Lookup("server-ui-api-key"),
); err != nil {
log.Fatal(err)
}
// server.ui.api.port
uiCmd.PersistentFlags().Int(
"server-ui-api-port",
1312,
"Web UI backend API server port",
)
if err := viper.BindPFlag(
"server.ui.api.port",
uiCmd.PersistentFlags().Lookup("server-ui-api-port"),
); err != nil {
log.Fatal(err)
}
// server.ui.api.protocol
uiCmd.PersistentFlags().String(
"server-ui-api-protocol",
"https",
"Web UI backend API server protocol",
)
if err := viper.BindPFlag(
"server.ui.api.protocol",
uiCmd.PersistentFlags().Lookup("server-ui-api-protocol"),
); err != nil {
log.Fatal(err)
}
// server.ui.password
uiCmd.PersistentFlags().String(
"server-ui-password",
"bottin",
"Web UI password",
)
if err := viper.BindPFlag(
"server.ui.password",
uiCmd.PersistentFlags().Lookup("server-ui-password"),
); err != nil {
log.Fatal(err)
}
// server.ui.port
uiCmd.PersistentFlags().Int(
"server-ui-port",
2312,
"Web UI port",
)
if err := viper.BindPFlag(
"server.ui.port",
uiCmd.PersistentFlags().Lookup("server-ui-port"),
); err != nil {
log.Fatal(err)
}
// server.ui.user
uiCmd.PersistentFlags().String(
"server-ui-user",
"bottin",
"Web UI user",
)
if err := viper.BindPFlag(
"server.ui.user",
uiCmd.PersistentFlags().Lookup("server-ui-user"),
); err != nil {
log.Fatal(err)
}
// server ui tls
// server.ui.tls.enabled
uiCmd.PersistentFlags().Bool(
"server-ui-tls-enabled",
true,
"Web UI enable TLS (requires certfile and keyfile)",
)
if err := viper.BindPFlag(
"server.ui.tls.enabled",
uiCmd.PersistentFlags().Lookup("server-ui-tls-enabled"),
); err != nil {
log.Fatal(err)
}
// server.ui.tls.certfile
uiCmd.PersistentFlags().String(
"server-ui-tls-certfile",
"/etc/bottin/cert.pem",
"Path to Web UI TLS certificate file",
)
if err := viper.BindPFlag(
"server.ui.tls.certfile",
uiCmd.PersistentFlags().Lookup("server-ui-tls-certfile"),
); err != nil {
log.Fatal(err)
}
// server.ui.tls.keyfile
uiCmd.PersistentFlags().String(
"server-ui-tls-keyfile",
"/etc/bottin/key.pem",
"Path to Web UI TLS private key file",
)
if err := viper.BindPFlag(
"server.ui.tls.keyfile",
uiCmd.PersistentFlags().Lookup("server-ui-tls-keyfile"),
); err != nil {
log.Fatal(err)
}
}

View file

@ -3,9 +3,9 @@ services:
db: db:
image: 'docker.io/library/postgres:16' image: 'docker.io/library/postgres:16'
environment: environment:
POSTGRES_DATABASE: "${BOTTIN_POSTGRES_DATABASE}" POSTGRES_DATABASE: "${BOTTIN_SERVER_API_DB_DATABASE}"
POSTGRES_PASSWORD: "${BOTTIN_POSTGRES_PASSWORD}" POSTGRES_PASSWORD: "${BOTTIN_SERVER_API_DB_PASSWORD}"
POSTGRES_USER: "${BOTTIN_POSTGRES_USER}" POSTGRES_USER: "${BOTTIN_SERVER_API_DB_USER}"
volumes: volumes:
- 'db-data:/var/lib/postgresql/data' - 'db-data:/var/lib/postgresql/data'
restart: 'unless-stopped' restart: 'unless-stopped'
@ -15,33 +15,26 @@ services:
- db - db
build: . build: .
image: 'git.agecem.com/agecem/bottin:latest' image: 'git.agecem.com/agecem/bottin:latest'
environment: env_file: '.env'
BOTTIN_DB_DATABASE: "${BOTTIN_POSTGRES_DATABASE}"
BOTTIN_DB_PASSWORD: "${BOTTIN_POSTGRES_PASSWORD}"
BOTTIN_DB_USER: "${BOTTIN_POSTGRES_USER}"
BOTTIN_API_KEY: "${BOTTIN_API_KEY}"
ports: ports:
- '1312:1312' - '1312:1312'
volumes: volumes:
- 'api-config:/etc/bottin' - 'api-config:/etc/bottin'
restart: 'unless-stopped' restart: 'unless-stopped'
command: ['bottin', '--config', '/etc/bottin/api.yaml', 'api'] command: ['bottin', '--config', '/etc/bottin/api.yaml', 'server', 'api']
web: ui:
depends_on: depends_on:
- api - api
build: . build: .
image: 'git.agecem.com/agecem/bottin:latest' image: 'git.agecem.com/agecem/bottin:latest'
environment: env_file: '.env'
BOTTIN_WEB_API_KEY: "${BOTTIN_API_KEY}"
BOTTIN_WEB_PASSWORD: "${BOTTIN_WEB_PASSWORD}"
BOTTIN_WEB_USER: "${BOTTIN_WEB_USER}"
ports: ports:
- '2312:2312' - '2312:2312'
volumes: volumes:
- 'web-config:/etc/bottin' - 'ui-config:/etc/bottin'
restart: 'unless-stopped' restart: 'unless-stopped'
command: ['bottin', '--config', '/etc/bottin/web.yaml', 'web'] command: ['bottin', '--config', '/etc/bottin/ui.yaml', 'server', 'ui']
# adminer: # adminer:
# image: adminer # image: adminer
@ -54,4 +47,4 @@ services:
volumes: volumes:
db-data: db-data:
api-config: api-config:
web-config: ui-config:

2
go.mod
View file

@ -1,4 +1,4 @@
module git.agecem.com/agecem/bottin/v7 module git.agecem.com/agecem/bottin/v8
go 1.22.0 go 1.22.0

View file

@ -23,7 +23,7 @@ func (request HealthGETRequest) Request(v *voki.Voki) (response HealthGETRespons
statusCode, body, err := v.CallAndParse( statusCode, body, err := v.CallAndParse(
http.MethodGet, http.MethodGet,
"/api/v7/health/", "/api/v8/health/",
nil, nil,
true, true,
) )
@ -64,7 +64,7 @@ func (request ProgrammesPOSTRequest) Request(v *voki.Voki) (response ProgrammesP
statusCode, body, err := v.CallAndParse( statusCode, body, err := v.CallAndParse(
http.MethodPost, http.MethodPost,
"/api/v7/programme/", "/api/v8/programme/",
&buf, &buf,
true, true,
) )
@ -105,7 +105,7 @@ func (request MembresPOSTRequest) Request(v *voki.Voki) (response MembresPOSTRes
statusCode, body, err := v.CallAndParse( statusCode, body, err := v.CallAndParse(
http.MethodPost, http.MethodPost,
"/api/v7/membre/", "/api/v8/membre/",
&buf, &buf,
true, true,
) )
@ -146,7 +146,7 @@ func (request MembreGETRequest) Request(v *voki.Voki) (response MembreGETRespons
statusCode, body, err := v.CallAndParse( statusCode, body, err := v.CallAndParse(
http.MethodGet, http.MethodGet,
fmt.Sprintf("/api/v7/membre/%s/", request.Param.MembreID), fmt.Sprintf("/api/v8/membre/%s/", request.Param.MembreID),
nil, nil,
true, true,
) )
@ -180,7 +180,7 @@ func (request MembresGETRequest) Request(v *voki.Voki) (response MembresGETRespo
statusCode, body, err := v.CallAndParse( statusCode, body, err := v.CallAndParse(
http.MethodGet, http.MethodGet,
fmt.Sprintf("/api/v7/membre/?limit=%d", request.Query.Limit), fmt.Sprintf("/api/v8/membre/?limit=%d", request.Query.Limit),
nil, nil,
true, true,
) )
@ -224,7 +224,7 @@ func (request MembrePreferedNamePUTRequest) Request(v *voki.Voki) (response Memb
statusCode, body, err := v.CallAndParse( statusCode, body, err := v.CallAndParse(
http.MethodPut, http.MethodPut,
fmt.Sprintf("/api/v7/membre/%s/prefered_name/", request.Param.MembreID), fmt.Sprintf("/api/v8/membre/%s/prefered_name/", request.Param.MembreID),
&buf, &buf,
true, true,
) )
@ -258,7 +258,7 @@ func (request ProgrammesGETRequest) Request(v *voki.Voki) (response ProgrammesGE
statusCode, body, err := v.CallAndParse( statusCode, body, err := v.CallAndParse(
http.MethodGet, http.MethodGet,
fmt.Sprintf("/api/v7/programme/?limit=%d", request.Query.Limit), fmt.Sprintf("/api/v8/programme/?limit=%d", request.Query.Limit),
nil, nil,
true, true,
) )
@ -292,7 +292,7 @@ func (request MembresDisplayGETRequest) Request(v *voki.Voki) (response MembresD
statusCode, body, err := v.CallAndParse( statusCode, body, err := v.CallAndParse(
http.MethodGet, http.MethodGet,
fmt.Sprintf("/api/v7/membre/display/?limit=%d", request.Query.Limit), fmt.Sprintf("/api/v8/membre/display/?limit=%d", request.Query.Limit),
nil, nil,
true, true,
) )
@ -333,7 +333,7 @@ func (request MembreDisplayGETRequest) Request(v *voki.Voki) (response MembreDis
statusCode, body, err := v.CallAndParse( statusCode, body, err := v.CallAndParse(
http.MethodGet, http.MethodGet,
fmt.Sprintf("/api/v7/membre/%s/display/", request.Param.MembreID), fmt.Sprintf("/api/v8/membre/%s/display/", request.Param.MembreID),
nil, nil,
true, true,
) )

View file

@ -17,7 +17,7 @@ func addRoutes(e *echo.Echo, db *PostgresClient, cfg Config) error {
_ = db _ = db
_ = cfg _ = cfg
apiPath := "/api/v7" apiPath := "/api/v8"
apiGroup := e.Group(apiPath) apiGroup := e.Group(apiPath)
p := pave.New() p := pave.New()
if err := pave.EchoRegister[HealthGETRequest]( if err := pave.EchoRegister[HealthGETRequest](
@ -248,7 +248,8 @@ func addRoutes(e *echo.Echo, db *PostgresClient, cfg Config) error {
} }
} else { } else {
//TODO cfg.API.DefaultLimit //TODO cfg.Server.API.DefaultLimit
//TODO cfg.Client.API.Limit
request.Query.Limit = 1000 request.Query.Limit = 1000
} }