initial commit

This commit is contained in:
2026-01-21 20:03:02 +11:00
commit e0ec6d06d3
41 changed files with 3069 additions and 0 deletions

44
cmd/oslstats/auth.go Normal file
View File

@@ -0,0 +1,44 @@
package main
import (
"context"
"git.haelnorr.com/h/golib/hlog"
"git.haelnorr.com/h/golib/hws"
"git.haelnorr.com/h/golib/hwsauth"
"git.haelnorr.com/h/oslstats/internal/db"
"git.haelnorr.com/h/oslstats/internal/handlers"
"git.haelnorr.com/h/oslstats/pkg/contexts"
"github.com/pkg/errors"
"github.com/uptrace/bun"
)
func setupAuth(
config *hwsauth.Config,
logger *hlog.Logger,
conn *bun.DB,
server *hws.Server,
ignoredPaths []string,
) (*hwsauth.Authenticator[*db.User, bun.Tx], error) {
beginTx := func(ctx context.Context) (hwsauth.DBTransaction, error) {
tx, err := conn.BeginTx(ctx, nil)
return tx, err
}
auth, err := hwsauth.NewAuthenticator(
config,
db.GetUserByID,
server,
beginTx,
logger,
handlers.ErrorPage,
)
if err != nil {
return nil, errors.Wrap(err, "hwsauth.NewAuthenticator")
}
auth.IgnorePaths(ignoredPaths...)
contexts.CurrentUser = auth.CurrentModel
return auth, nil
}

53
cmd/oslstats/db.go Normal file
View File

@@ -0,0 +1,53 @@
package main
import (
"context"
"database/sql"
"fmt"
"git.haelnorr.com/h/oslstats/internal/config"
"git.haelnorr.com/h/oslstats/internal/db"
"github.com/pkg/errors"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/pgdialect"
"github.com/uptrace/bun/driver/pgdriver"
)
func setupBun(ctx context.Context, cfg *config.Config) (conn *bun.DB, close func() error, err error) {
dsn := fmt.Sprintf("postgres://%s:%s@%s:%v/%s?sslmode=%s",
cfg.DB.User, cfg.DB.Password, cfg.DB.Host, cfg.DB.Port, cfg.DB.DB, cfg.DB.SSL)
sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn)))
conn = bun.NewDB(sqldb, pgdialect.New())
close = sqldb.Close
err = loadModels(ctx, conn, cfg.Flags.ResetDB)
if err != nil {
return nil, nil, errors.Wrap(err, "loadModels")
}
return conn, close, nil
}
func loadModels(ctx context.Context, conn *bun.DB, resetDB bool) error {
models := []any{
(*db.User)(nil),
}
for _, model := range models {
_, err := conn.NewCreateTable().
Model(model).
IfNotExists().
Exec(ctx)
if err != nil {
return errors.Wrap(err, "db.NewCreateTable")
}
if resetDB {
err = conn.ResetModel(ctx, model)
if err != nil {
return errors.Wrap(err, "db.ResetModel")
}
}
}
return nil
}

View File

@@ -0,0 +1,67 @@
package main
import (
"io/fs"
"net/http"
"git.haelnorr.com/h/golib/hws"
"git.haelnorr.com/h/oslstats/internal/config"
"git.haelnorr.com/h/oslstats/internal/handlers"
"git.haelnorr.com/h/golib/hlog"
"github.com/pkg/errors"
"github.com/uptrace/bun"
)
func setupHttpServer(
staticFS *fs.FS,
config *config.Config,
logger *hlog.Logger,
bun *bun.DB,
) (server *hws.Server, err error) {
if staticFS == nil {
return nil, errors.New("No filesystem provided")
}
fs := http.FS(*staticFS)
httpServer, err := hws.NewServer(config.HWS)
if err != nil {
return nil, errors.Wrap(err, "hws.NewServer")
}
ignoredPaths := []string{
"/static/css/output.css",
"/static/favicon.ico",
}
auth, err := setupAuth(config.HWSAuth, logger, bun, httpServer, ignoredPaths)
if err != nil {
return nil, errors.Wrap(err, "setupAuth")
}
err = httpServer.AddErrorPage(handlers.ErrorPage)
if err != nil {
return nil, errors.Wrap(err, "httpServer.AddErrorPage")
}
err = httpServer.AddLogger(logger)
if err != nil {
return nil, errors.Wrap(err, "httpServer.AddLogger")
}
err = httpServer.LoggerIgnorePaths(ignoredPaths...)
if err != nil {
return nil, errors.Wrap(err, "httpServer.LoggerIgnorePaths")
}
err = addRoutes(httpServer, &fs, config, logger, bun, auth)
if err != nil {
return nil, errors.Wrap(err, "addRoutes")
}
err = addMiddleware(httpServer, auth)
if err != nil {
return nil, errors.Wrap(err, "httpServer.AddMiddleware")
}
return httpServer, nil
}

36
cmd/oslstats/main.go Normal file
View File

@@ -0,0 +1,36 @@
package main
import (
"context"
"fmt"
"os"
"git.haelnorr.com/h/oslstats/internal/config"
"github.com/pkg/errors"
)
func main() {
flags := config.SetupFlags()
ctx := context.Background()
cfg, loader, err := config.GetConfig(flags)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", errors.Wrap(err, "Failed to load config"))
os.Exit(1)
}
if flags.EnvDoc || flags.ShowEnv {
loader.PrintEnvVarsStdout(flags.ShowEnv)
return
}
if flags.GenEnv != "" {
loader.GenerateEnvFile(flags.GenEnv, true)
return
}
if err := run(ctx, os.Stdout, cfg); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
}

View File

@@ -0,0 +1,24 @@
package main
import (
"git.haelnorr.com/h/golib/hws"
"git.haelnorr.com/h/golib/hwsauth"
"git.haelnorr.com/h/oslstats/internal/db"
"github.com/pkg/errors"
"github.com/uptrace/bun"
)
func addMiddleware(
server *hws.Server,
auth *hwsauth.Authenticator[*db.User, bun.Tx],
) error {
err := server.AddMiddleware(
auth.Authenticate(),
)
if err != nil {
return errors.Wrap(err, "server.AddMiddleware")
}
return nil
}

45
cmd/oslstats/routes.go Normal file
View File

@@ -0,0 +1,45 @@
package main
import (
"net/http"
"git.haelnorr.com/h/golib/hws"
"git.haelnorr.com/h/golib/hwsauth"
"git.haelnorr.com/h/oslstats/internal/config"
"git.haelnorr.com/h/oslstats/internal/db"
"git.haelnorr.com/h/oslstats/internal/handlers"
"git.haelnorr.com/h/golib/hlog"
"github.com/pkg/errors"
"github.com/uptrace/bun"
)
func addRoutes(
server *hws.Server,
staticFS *http.FileSystem,
config *config.Config,
logger *hlog.Logger,
conn *bun.DB,
auth *hwsauth.Authenticator[*db.User, bun.Tx],
) error {
// Create the routes
routes := []hws.Route{
{
Path: "/static/",
Method: hws.MethodGET,
Handler: http.StripPrefix("/static/", handlers.StaticFS(staticFS, server)),
},
{
Path: "/",
Method: hws.MethodGET,
Handler: handlers.Index(server),
},
}
// Register the routes with the server
err := server.AddRoutes(routes...)
if err != nil {
return errors.Wrap(err, "server.AddRoutes")
}
return nil
}

72
cmd/oslstats/run.go Normal file
View File

@@ -0,0 +1,72 @@
package main
import (
"context"
"io"
"os"
"os/signal"
"sync"
"time"
"git.haelnorr.com/h/golib/hlog"
"git.haelnorr.com/h/oslstats/internal/config"
"git.haelnorr.com/h/oslstats/pkg/embedfs"
"github.com/pkg/errors"
)
// Initializes and runs the server
func run(ctx context.Context, w io.Writer, config *config.Config) error {
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
defer cancel()
// Setup the logger
logger, err := hlog.NewLogger(config.HLOG, w)
if err != nil {
return errors.Wrap(err, "hlog.NewLogger")
}
// Setup the database connection
logger.Debug().Msg("Config loaded and logger started")
logger.Debug().Msg("Connecting to database")
bun, closedb, err := setupBun(ctx, config)
if err != nil {
return errors.Wrap(err, "setupDBConn")
}
defer closedb()
// Setup embedded files
logger.Debug().Msg("Getting embedded files")
staticFS, err := embedfs.GetEmbeddedFS()
if err != nil {
return errors.Wrap(err, "getStaticFiles")
}
logger.Debug().Msg("Setting up HTTP server")
httpServer, err := setupHttpServer(&staticFS, config, logger, bun)
if err != nil {
return errors.Wrap(err, "setupHttpServer")
}
// Runs the http server
logger.Debug().Msg("Starting up the HTTP server")
err = httpServer.Start(ctx)
if err != nil {
return errors.Wrap(err, "httpServer.Start")
}
// Handles graceful shutdown
var wg sync.WaitGroup
wg.Go(func() {
<-ctx.Done()
shutdownCtx := context.Background()
shutdownCtx, cancel := context.WithTimeout(shutdownCtx, 10*time.Second)
defer cancel()
err := httpServer.Shutdown(shutdownCtx)
if err != nil {
logger.Error().Err(err).Msg("Graceful shutdown failed")
}
})
wg.Wait()
logger.Info().Msg("Shutting down")
return nil
}