WIP: major/v2 #1

Draft
vlbeaudoin wants to merge 5 commits from major/v2 into main
15 changed files with 349 additions and 173 deletions

View file

@ -4,15 +4,16 @@ LABEL author="vlbeaudoin"
WORKDIR /go/src/app
COPY LICENSE cmd.go config.go db.go entity.go flag.go go.mod go.sum handler.go main.go server.go ./
COPY LICENSE cmd.go config.go db.go entity.go flag.go go.mod go.sum handler.go server.go x509.go ./
ADD cmd/ cmd/
ADD queries/ queries/
ADD ui/ ui/
RUN CGO_ENABLED=0 go build \
-a \
-o presences \
./
./cmd/presences/
# Alpine

View file

@ -4,13 +4,40 @@
.DEFAULT_GOAL := help
# splits current git annotated tag into 'MAJOR MINOR PATCH EXTRAS'.
# intended to be used as arguments for certain scripts requiring a semver as args
current_semver := $(shell git describe | tr -d 'v' | tr '.' ' ' | tr '-' ' ')
.PHONY: help
help: ## Show this help
@egrep -h '\s##\s' $(MAKEFILE_LIST) | \
@grep -E -h '\s##\s' $(MAKEFILE_LIST) | \
sort | \
awk \
'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
.PHONY: build
build: ## Use `docker build` to build the container image
docker build . -f Dockerfile -t git.agecem.com/bottin/presences:latest
.PHONY: go-install
go-install: ## Use `go install` to build and link the executable
go install -a ./cmd/presences/
.PHONY: docker-build
docker-build: ## Build container image
#docker-compose build \
#--build-arg bottin_version=`git describe`
docker-compose build
.PHONY: docker-deploy
docker-deploy: ## Using `docker-compose up`, deploy the database and containers
docker-compose up -d
.PHONY: docker-tag-from-git
docker-tag-from-git: ## Tag latest image according to annotated tag from `git describe`
./scripts/docker-tag.sh $(current_semver)
.PHONY: docker-push-from-git
docker-push-from-git: ## Push images to git.agecem.com according to annotated tag from `git describe`
./scripts/docker-push.sh $(current_semver)
## pipelines
.PHONY: deploy
deploy: docker-build docker-deploy ## Build and deploy container image

50
cmd.go
View file

@ -1,11 +1,15 @@
package main
package presences
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"codeberg.org/vlbeaudoin/voki/v3"
"git.agecem.com/bottin/bottin/v11"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/spf13/cobra"
)
@ -49,3 +53,47 @@ var rootCmd = &cobra.Command{
}
},
}
func run(ctx context.Context, cfg Config) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
bottinClient := bottin.APIClient{Caller: voki.New(
http.DefaultClient,
cfg.Bottin.Host,
cfg.Bottin.Key,
cfg.Bottin.Port,
func() string {
if cfg.Bottin.TLS.Enabled {
return "https"
} else {
return "http"
}
}(),
)}
// connect to db
dbPool, err := pgxpool.New(ctx,
fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=%s",
cfg.DB.Username,
cfg.DB.Password,
cfg.DB.Host,
cfg.DB.Port,
cfg.DB.Database,
cfg.DB.SSLMode,
))
if err != nil {
return err
}
defer dbPool.Close()
dbClient := DBClient{Pool: dbPool}
if err := RunUIServer(ctx, cfg, &bottinClient, &dbClient); err != nil && err != http.ErrServerClosed {
return err
}
return nil
}
}

11
cmd/presences/main.go Normal file
View file

@ -0,0 +1,11 @@
package main
import (
"git.agecem.com/bottin/presences"
)
// Entry
func main() {
// Start commandline parsing then call `run`
presences.Execute()
}

View file

@ -39,7 +39,7 @@ services:
volumes:
- 'presences-config:/etc/presences/'
restart: 'unless-stopped'
command: ['presences', '--config', '/etc/presences/config.yaml', 'server']
command: ['presences', '--config', '/etc/presences/config.yaml']
adminer:
depends_on:

View file

@ -1,11 +1,11 @@
package main
package presences
import (
"fmt"
"os"
"strings"
"git.agecem.com/bottin/bottin/v10/pkg/bottin"
"git.agecem.com/bottin/bottin/v11"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@ -33,13 +33,12 @@ type Config struct {
Key string
}
// Credentials holds username-password pairs for basic authentication
// not part of minimum viable product
//Credentials map[string]string
UI struct {
Username string
Password string
//Username string
//Password string
// Credentials holds username-password pairs for basic authentication
Credentials map[string]string
}
// Port holds the port on which to expose the user interface

2
db.go
View file

@ -1,4 +1,4 @@
package main
package presences
import (
"context"

View file

@ -1,4 +1,4 @@
package main
package presences
import "time"

101
flag.go
View file

@ -1,4 +1,4 @@
package main
package presences
import (
"github.com/spf13/pflag"
@ -6,37 +6,106 @@ import (
)
const (
configPort = "Port"
defaultPort = 8080
flagPort = "port"
flagPortConfig = "Port"
flagPortDefault = 8080
flagPort = "port"
configTLSCert = "TLS.Cert"
defaultTLSCert = ""
flagTLSCert = "tls-cert"
flagTLSCertConfig = "TLS.Cert"
flagTLSCertDefault = ""
flagTLSCert = "tls-cert"
configTLSKey = "TLS.Key"
defaultTLSKey = ""
flagTLSKey = "tls-key"
flagTLSKeyConfig = "TLS.Key"
flagTLSKeyDefault = ""
flagTLSKey = "tls-key"
flagDBDatabaseConfig = "DB.Database"
flagDBDatabaseDefault = "presences"
flagDBDatabase = "db-database"
flagDBHostConfig = "DB.Host"
flagDBHostDefault = "presences-db"
flagDBHost = "db-host"
flagDBPasswordConfig = "DB.Password"
flagDBPasswordDefault = "presences"
flagDBPassword = "db-password"
flagDBPortConfig = "DB.Port"
flagDBPortDefault = 5432
flagDBPort = "db-port"
flagDBSSLModeConfig = "DB.SSLMode"
flagDBSSLModeDefault = "prefer"
flagDBSSLMode = "db-sslmode"
flagDBUsernameConfig = "DB.Username"
flagDBUsernameDefault = "presences"
flagDBUsername = "db-username"
/*
flagUICredentialsConfig = "UI.Credentials"
//flagUICredentialsDefault = make(map[string]string)
flagUICredentials = "ui-credentials"
*/
//TODO check if any necessary flags are missing
)
// BindClientFlags declares client-related flags and config options in the specified *pflag.FlagSet
func BindFlags(set *pflag.FlagSet) error {
// Credentials -> seulement par config
set.Int(flagPort, defaultPort, "User interface port")
if err := viper.BindPFlag(configPort, set.Lookup(flagPort)); err != nil {
set.Int(flagPort, flagPortDefault, "User interface port")
if err := viper.BindPFlag(flagPortConfig, set.Lookup(flagPort)); err != nil {
return err
}
set.String(flagTLSKey, defaultTLSKey, "User interface TLS private key (or path to file)")
if err := viper.BindPFlag(configTLSKey, set.Lookup(flagTLSKey)); err != nil {
set.String(flagTLSKey, flagTLSKeyDefault, "User interface TLS private key (or path to file)")
if err := viper.BindPFlag(flagTLSKeyConfig, set.Lookup(flagTLSKey)); err != nil {
return err
}
set.String(flagTLSCert, defaultTLSCert, "User interface TLS certificate (or path to file)")
if err := viper.BindPFlag(configTLSCert, set.Lookup(flagTLSCert)); err != nil {
set.String(flagTLSCert, flagTLSCertDefault, "User interface TLS certificate (or path to file)")
if err := viper.BindPFlag(flagTLSCertConfig, set.Lookup(flagTLSCert)); err != nil {
return err
}
set.String(flagDBDatabase, flagDBDatabaseDefault, "PostgreSQL database")
if err := viper.BindPFlag(flagDBDatabaseConfig, set.Lookup(flagDBDatabase)); err != nil {
return err
}
set.String(flagDBHost, flagDBHostDefault, "PostgreSQL host")
if err := viper.BindPFlag(flagDBHostConfig, set.Lookup(flagDBHost)); err != nil {
return err
}
set.String(flagDBPassword, flagDBPasswordDefault, "PostgreSQL password")
if err := viper.BindPFlag(flagDBPasswordConfig, set.Lookup(flagDBPassword)); err != nil {
return err
}
set.Int(flagDBPort, flagDBPortDefault, "PostgreSQL port")
if err := viper.BindPFlag(flagDBPortConfig, set.Lookup(flagDBPort)); err != nil {
return err
}
set.String(flagDBSSLMode, flagDBSSLModeDefault, "PostgreSQL ssl mode")
if err := viper.BindPFlag(flagDBSSLModeConfig, set.Lookup(flagDBSSLMode)); err != nil {
return err
}
set.String(flagDBUsername, flagDBUsernameDefault, "PostgreSQL username")
if err := viper.BindPFlag(flagDBUsernameConfig, set.Lookup(flagDBUsername)); err != nil {
return err
}
/*
set.StringToString(flagUICredentials, nil, "Sets of credentials for the UI console")
if err := viper.BindPFlag(flagUICredentialsConfig, set.Lookup(flagUICredentials)); err != nil {
return err
}
*/
return nil
}

35
go.mod
View file

@ -4,44 +4,39 @@ go 1.23.6
require (
codeberg.org/vlbeaudoin/voki/v3 v3.0.1
git.agecem.com/bottin/bottin/v10 v10.6.0
github.com/jackc/pgx/v5 v5.7.2
git.agecem.com/bottin/bottin/v11 v11.0.2
github.com/jackc/pgx/v5 v5.7.4
github.com/labstack/echo/v4 v4.13.3
github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.6
github.com/spf13/viper v1.19.0
github.com/spf13/viper v1.20.1
)
require (
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/magiconair/properties v1.8.9 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/sagikazarmark/locafero v0.9.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.12.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/afero v1.14.0 // indirect
github.com/spf13/cast v1.8.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.35.0 // indirect
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.10.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/time v0.11.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

76
go.sum
View file

@ -1,30 +1,29 @@
codeberg.org/vlbeaudoin/voki/v3 v3.0.1 h1:pFjd/ZKsu4eOzRJYViE9F1S3RglSkAuIiqCo9IH9tUI=
codeberg.org/vlbeaudoin/voki/v3 v3.0.1/go.mod h1:+6LMXosAu2ijNKV04sMwkeujpH+cghZU1fydqj2y95g=
git.agecem.com/bottin/bottin/v10 v10.6.0 h1:I9xQMizlqfWHrJ1ZL2PlX/EeTpRm+B7bsheaBOVChFM=
git.agecem.com/bottin/bottin/v10 v10.6.0/go.mod h1:KTwlqY5XdVi9F7cpwy3hxYN1DQm+74tBv7Wc9rfKXuM=
git.agecem.com/bottin/bottin/v11 v11.0.2 h1:QtbjTDln3jcd4Vkc66uil+YoLisOPaDy2SerZQ95weo=
git.agecem.com/bottin/bottin/v11 v11.0.2/go.mod h1:c0sV8VOTLtnwel3oCo8xyBTW9zdmrVRwiGEF5NyYjjY=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ=
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg=
github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@ -35,38 +34,31 @@ github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaa
github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k=
github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk=
github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@ -80,26 +72,22 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4=
golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -1,4 +1,4 @@
package main
package presences
import (
"context"
@ -6,7 +6,7 @@ import (
"log"
"net/http"
"git.agecem.com/bottin/bottin/v10/pkg/bottin"
"git.agecem.com/bottin/bottin/v11"
"github.com/labstack/echo/v4"
)

61
main.go
View file

@ -1,61 +0,0 @@
package main
import (
"context"
"fmt"
"net/http"
"codeberg.org/vlbeaudoin/voki/v3"
"git.agecem.com/bottin/bottin/v10/pkg/bottin"
"github.com/jackc/pgx/v5/pgxpool"
)
// Entry
func main() {
// Start commandline parsing then call `run`
Execute()
}
func run(ctx context.Context, cfg Config) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
bottinClient := bottin.APIClient{Caller: voki.New(
http.DefaultClient,
cfg.Bottin.Host,
cfg.Bottin.Key,
cfg.Bottin.Port,
func() string {
if cfg.Bottin.TLS.Enabled {
return "https"
} else {
return "http"
}
}(),
)}
// connect to db
dbPool, err := pgxpool.New(ctx,
fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=%s",
cfg.DB.Username,
cfg.DB.Password,
cfg.DB.Host,
cfg.DB.Port,
cfg.DB.Database,
cfg.DB.SSLMode,
))
if err != nil {
return err
}
defer dbPool.Close()
dbClient := DBClient{Pool: dbPool}
if err := RunUIServer(ctx, cfg, &bottinClient, &dbClient); err != nil && err != http.ErrServerClosed {
return err
}
return nil
}
}

View file

@ -1,12 +1,14 @@
package main
package presences
import (
"context"
"crypto/subtle"
"crypto/tls"
"fmt"
"log"
"net/http"
"git.agecem.com/bottin/bottin/v10/pkg/bottin"
"git.agecem.com/bottin/bottin/v11"
"git.agecem.com/bottin/presences/ui"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
@ -41,19 +43,19 @@ func RunUIServer(ctx context.Context, cfg Config, bottinClient *bottin.APIClient
e.Pre(middleware.AddTrailingSlash())
// basic auth
if cfg.UI.Username == "" || cfg.UI.Password == "" {
return fmt.Errorf("UI username and password cannot be empty, please set UI.Password and UI.Username (PRESENCES_UI_PASSWORD and PRESENCES_UI_USERNAME")
if len(cfg.UI.Credentials) == 0 {
return fmt.Errorf("UI.Credentials config file option of type map[string]string must contain at least one username-password key-value pair. Note that there is no flag or ENV counterpart, this must be done through a config file.")
}
e.Pre(middleware.BasicAuth(
func(username, password string, c echo.Context) (bool, error) {
userOK := subtle.ConstantTimeCompare([]byte(username), []byte(cfg.UI.Username)) == 1
passOK := subtle.ConstantTimeCompare([]byte(password), []byte(cfg.UI.Password)) == 1
if userOK && passOK {
return true, nil
rightPassword, userExists := cfg.UI.Credentials[username]
if !userExists {
return false, nil
}
return false, nil
passwordOK := subtle.ConstantTimeCompare([]byte(password), []byte(rightPassword)) == 1
return passwordOK, nil
}),
)
@ -69,7 +71,30 @@ func RunUIServer(ctx context.Context, cfg Config, bottinClient *bottin.APIClient
address := fmt.Sprintf(":%d", cfg.Port)
return e.StartTLS(address, cfg.TLS.Cert, cfg.TLS.Key)
}
switch {
case cfg.TLS.Cert != "" && cfg.TLS.Key != "":
return e.StartTLS(address, cfg.TLS.Cert, cfg.TLS.Key)
case cfg.TLS.Cert != "" && cfg.TLS.Key == "":
return fmt.Errorf("found TLS certificate but missing associated TLS private key")
case cfg.TLS.Cert == "" && cfg.TLS.Key != "":
return fmt.Errorf("found TLS private key but missing associated TLS certificate")
default:
log.Println("No TLS pair was provided. Generating self-signed pair.")
tlsPair, err := newTLSPair()
if err != nil {
return err
}
server := &http.Server{
Addr: address,
Handler: e,
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{tlsPair},
},
}
return server.ListenAndServeTLS("", "")
}
}
}

74
x509.go Normal file
View file

@ -0,0 +1,74 @@
package presences
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"time"
)
func newTLSPair() (tls.Certificate, error) {
//TODO revise this code to make sure it is satisfying
// Generate an ECDSA private key using P256.
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return tls.Certificate{}, fmt.Errorf("Failed to generate private key: %v", err)
}
// Generate a random serial number.
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
if err != nil {
return tls.Certificate{}, fmt.Errorf("Failed to generate serial number: %v", err)
}
// Create a certificate template.
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"AGECEM-bottin-presences"},
CommonName: "localhost", // common name localhost for local development
},
NotBefore: time.Now().Add(-time.Hour), // valid from 1 hour ago
NotAfter: time.Now().Add(365 * 24 * time.Hour), // valid for 1 year
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: true, // self-signed, so we can mark it as CA (optional)
}
// Self-sign the certificate.
derCert, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return tls.Certificate{}, fmt.Errorf("Failed to create certificate: %v", err)
}
// Encode the certificate PEM block.
certPEM := pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: derCert,
})
// Encode the private key PEM block.
keyBytes, err := x509.MarshalECPrivateKey(priv)
if err != nil {
return tls.Certificate{}, fmt.Errorf("Unable to marshal ECDSA private key: %v", err)
}
keyPEM := pem.EncodeToMemory(&pem.Block{
Type: "EC PRIVATE KEY",
Bytes: keyBytes,
})
// Load the generated certificate into a tls.Certificate.
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil {
return tls.Certificate{}, fmt.Errorf("failed to load X509 key pair: %v", err)
}
return tlsCert, nil
}