From 5e2043d54e3dc6747df546c8f9be05ceee67e15b374c982403e7ad24f55df700 Mon Sep 17 00:00:00 2001 From: Daan Selen Date: Thu, 23 Apr 2026 21:50:53 +0200 Subject: [PATCH] feat: add cmd flags --- cmd/server/main.go | 11 +-- internal/server/bootstrap/bootstrap.go | 101 +++++++++++++++++++++++ internal/server/bootstrap/environment.go | 58 ------------- internal/server/bootstrap/filesystem.go | 23 ------ internal/server/bootstrap/vargrab.go | 50 ----------- internal/server/database/functions.go | 4 +- internal/server/database/watchdog.go | 6 +- internal/shared/utility/utility.go | 23 +++--- 8 files changed, 120 insertions(+), 156 deletions(-) create mode 100644 internal/server/bootstrap/bootstrap.go delete mode 100644 internal/server/bootstrap/environment.go delete mode 100644 internal/server/bootstrap/filesystem.go delete mode 100644 internal/server/bootstrap/vargrab.go diff --git a/cmd/server/main.go b/cmd/server/main.go index 3f94ffe..f684e11 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -12,7 +12,7 @@ import ( func main() { // grab the environment variables from the runtime environment // unfortunately since its before we configure the loglevel, we cannot use slog logging in those functions - env := bootstrap.GrabEnvironment() + env := bootstrap.LoadConfig() // configure the logger so we have nice json level := utility.ParseSlogLevel(env.LogLevel) // defaults to Info logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ @@ -23,15 +23,6 @@ func main() { // print our running environment variable set slog.Debug("displaying environment variables", "environment", env) - // TO DO, allow cmd args parsing - - // checking directories to ensure its expected environment is ready - slog.Debug("checking if directories are present") - if err := bootstrap.EnsureOperation(env.DataDirectory); err != nil { - slog.Error("failed to ensure the operating environment", "error", err) - os.Exit(1) - } - // initiating the database connection for which we safe things slog.Info("kicking off database connection") db, err := database.KickoffDatabase(env.DataDirectory) diff --git a/internal/server/bootstrap/bootstrap.go b/internal/server/bootstrap/bootstrap.go new file mode 100644 index 0000000..0eec7fd --- /dev/null +++ b/internal/server/bootstrap/bootstrap.go @@ -0,0 +1,101 @@ +package bootstrap + +import ( + "flag" + "os" + "reflect" + "strconv" +) + +type Environment struct { + Version string `env:"VERSION" default:"0.0.1" flag:"version"` + Codename string `env:"CODENAME" default:"Magical Anomaly" flag:"codename"` + LogLevel string `env:"LOG_LEVEL" default:"debug" flag:"log-level"` + + DataDirectory string `env:"DATA_DIR" default:"./data" flag:"data-dir" usage:"option to specify where the state data gets stored"` + ContentDirectory string `env:"CONTENT_DIR" default:"./content" flag:"content-dir" usage:"option to specify where the content gets stored"` + Hostname string `env:"HOSTNAME" default:"0.0.0.0" flag:"hostname" usage:"option specify the address/hostname to bind the api server to"` + Port int `env:"PORT" default:"8080" flag:"port" usage:"option to specify the port to bind the api server to"` + AuthenticationEnabled bool `env:"AUTH_ENABLED" default:"true" flag:"auth" usage:"option to disable authentication"` + + WatchdogEnabled bool `env:"WATCHDOG_ENABLED" default:"true" flag:"watchdog" usage:"option to disable watchdog"` + WatchdogInterval int `env:"WATCHDOG_INTERVAL" default:"60" flag:"watchdog-interval" usage:"option to specify the interval in second(s) on which watchdog runs"` + WatchdogSyncMode string `env:"WATCHDOG_SYNC_MODE" default:"strict" flag:"watchdog-mode" usage:"option to specify the mode watchdog will run with: strict|sync|dry"` +} + +func loadFromEnv(env *Environment) { + v := reflect.ValueOf(env).Elem() + t := v.Type() + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + valueField := v.Field(i) + + // It is of great importance we set these fields above + key := field.Tag.Get("env") + fallback := field.Tag.Get("default") + + // If the variable is not found or is empty + raw, ok := os.LookupEnv(key) + if !ok || raw == "" { + raw = fallback + } + + switch valueField.Kind() { + case reflect.String: + valueField.SetString(raw) + + case reflect.Int: + if parsed, err := strconv.Atoi(raw); err == nil { + valueField.SetInt(int64(parsed)) + } + + case reflect.Bool: + if parsed, err := strconv.ParseBool(raw); err == nil { + valueField.SetBool(parsed) + } + } + } +} + +func bindFlags(env *Environment) { + v := reflect.ValueOf(env).Elem() + t := v.Type() + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + valueField := v.Field(i) + + flagName := field.Tag.Get("flag") + flagUsage := field.Tag.Get("usage") + if flagName == "" { + continue + } + + switch valueField.Kind() { + case reflect.String: + flag.StringVar(valueField.Addr().Interface().(*string), flagName, + valueField.String(), flagUsage, + ) + case reflect.Int: + flag.IntVar(valueField.Addr().Interface().(*int), flagName, + int(valueField.Int()), flagUsage, + ) + case reflect.Bool: + flag.BoolVar( + valueField.Addr().Interface().(*bool), flagName, + valueField.Bool(), flagUsage, + ) + } + } +} + +func LoadConfig() Environment { + var env Environment + + loadFromEnv(&env) + bindFlags(&env) + flag.Parse() + + return env +} diff --git a/internal/server/bootstrap/environment.go b/internal/server/bootstrap/environment.go deleted file mode 100644 index 15a22d9..0000000 --- a/internal/server/bootstrap/environment.go +++ /dev/null @@ -1,58 +0,0 @@ -package bootstrap - -import ( - "os" - "path/filepath" -) - -type Environment struct { - // Items made for the presentation of ORBITS - Version string - Codename string - LogLevel string - - // Items made for the configuration of the server parts - DataDirectory string - ContentDirectory string - Hostname string - Port int - AuthenticationEnabled bool - - // Items made for the server watchdog - WatchdogEnabled bool - WatchdogInterval int - WatchdogSyncMode string -} - -func GrabEnvironment() Environment { - cwd, err := os.Getwd() - if err != nil { - cwd = "." - } - - fbBase := filepath.Join(cwd, "data") - fbContent := filepath.Join(fbBase, "content") - - return Environment{ - // Basic server configuration - Version: safeStringGrab("VERSION", "0.0.1"), - Codename: safeStringGrab("CODENAME", "Magical Anomaly"), - LogLevel: safeStringGrab("LOG_LEVEL", "debug"), - - // GIN API configuration - DataDirectory: safeStringGrab("DATA_DIR", fbBase), - ContentDirectory: safeStringGrab("CONTENT_DIR", fbContent), - Hostname: safeStringGrab("HOSTNAME", "0.0.0.0"), - Port: safeIntGrab("PORT", 8080), - AuthenticationEnabled: safeBoolGrab("AUTH_ENABLED", true), - - // watchdog configuration - // watchdog is the integrity checker/steward - WatchdogEnabled: safeBoolGrab("WATCHDOG_ENABLED", true), - WatchdogInterval: safeIntGrab("WATCHDOG_INTERVAL", 60), - // sync: sync local files to the database, for example when you want to allow local inserting files which then get added to the database - // strict: make the database leading, this is when you only allow API uploads to be registered, and remove orphaned filesystem files - // dry: do nothing - WatchdogSyncMode: safeStringGrab("WATCHDOG_SYNC_MODE", "strict"), - } -} diff --git a/internal/server/bootstrap/filesystem.go b/internal/server/bootstrap/filesystem.go deleted file mode 100644 index 4d103af..0000000 --- a/internal/server/bootstrap/filesystem.go +++ /dev/null @@ -1,23 +0,0 @@ -package bootstrap - -import ( - "os" - "path/filepath" -) - -// part of filesystem checking -func EnsureOperation(workDir string) error { - nDirs := []string{ - workDir, - filepath.Join(workDir), - filepath.Join(workDir, "content"), - } - - for _, p := range nDirs { - if err := os.MkdirAll(p, 0755); err != nil { - return err - } - } - - return nil -} diff --git a/internal/server/bootstrap/vargrab.go b/internal/server/bootstrap/vargrab.go deleted file mode 100644 index 6123666..0000000 --- a/internal/server/bootstrap/vargrab.go +++ /dev/null @@ -1,50 +0,0 @@ -package bootstrap - -import ( - "os" - "slices" - "strconv" -) - -var ( - validSyncModes = []string{ - "sync", - "strict", - "dry", - } -) - -// part of environment checking -func safeStringGrab(key, fallback string) string { - if v, ok := os.LookupEnv(key); ok { - return v - } - return fallback -} - -func safeIntGrab(key string, fallback int) int { - if v, ok := os.LookupEnv(key); ok { - if i, err := strconv.Atoi(v); err == nil { - return i - } - } - return fallback -} - -func safeBoolGrab(key string, fallback bool) bool { - if v, ok := os.LookupEnv(key); ok { - if b, err := strconv.ParseBool(v); err == nil { - return b - } - } - return fallback -} - -func safeSyncModeGrab(key, fallback string) string { - if v, ok := os.LookupEnv(key); ok { - if slices.Contains(validSyncModes, v) { - return v - } - } - return fallback -} diff --git a/internal/server/database/functions.go b/internal/server/database/functions.go index 524b69d..69793c5 100644 --- a/internal/server/database/functions.go +++ b/internal/server/database/functions.go @@ -14,8 +14,8 @@ import ( // this function should only be called after manually checking the filetype func BuildFileRecord(r io.Reader, origName string, contentDirectory string) (File, error) { ext := filepath.Ext(origName) - category, ok := utility.CategorizeMediaType(ext) - if !ok { + category := utility.CategorizeMediaType(ext) + if category == utility.Unspecified { return File{}, fmt.Errorf("unsupported filetype") } diff --git a/internal/server/database/watchdog.go b/internal/server/database/watchdog.go index 6af4eb4..03ac128 100644 --- a/internal/server/database/watchdog.go +++ b/internal/server/database/watchdog.go @@ -78,12 +78,14 @@ func watchdog(env bootstrap.Environment, db *gorm.DB) { readerStream, err := os.Open(fp) if err != nil { slog.Error("failed to a reader stream") + continue } defer readerStream.Close() fileData, err := BuildFileRecord(readerStream, filepath.Base(fp), env.ContentDirectory) if err != nil { slog.Error("failed to enroll local file into the database", "error", err) + continue } if err := RegisterFile(db, fileData); err != nil { @@ -92,6 +94,7 @@ func watchdog(env bootstrap.Environment, db *gorm.DB) { } else { slog.Error("failed to insert filedata to the database", "error", err) } + continue } // to fully finalize the enrollment process, we rename the locally inserted file to a unique filename @@ -99,7 +102,6 @@ func watchdog(env bootstrap.Environment, db *gorm.DB) { if err := os.Rename(fp, fileData.FilePath); err != nil { slog.Error("failed to move the locally inserted file", "error", err) } - case "strict": err := utility.RemoveFile(fp) if err != nil { @@ -107,6 +109,8 @@ func watchdog(env bootstrap.Environment, db *gorm.DB) { } case "dry": slog.Debug("dry mode enabled, not purging", "filepath", fp) + default: + slog.Warn("unknown watchdog mode", "mode", env.WatchdogSyncMode) } } diff --git a/internal/shared/utility/utility.go b/internal/shared/utility/utility.go index 98b9f7c..a3b9204 100644 --- a/internal/shared/utility/utility.go +++ b/internal/shared/utility/utility.go @@ -3,6 +3,7 @@ package utility import ( "log/slog" "os" + "slices" "strings" ) @@ -15,20 +16,18 @@ func RemoveFile(p string) error { return nil } -func CategorizeMediaType(ext string) (MediaType, bool) { +func CategorizeMediaType(ext string) MediaType { // Lets categorize - /* - switch ext { - case slices.Contains(videoFormats, ext): - return Video, true - case slices.Contains(presentationFormats, ext): - return Presentation, true - default: - slog.Debug("marking file as invalid due to its undefined extension") - return "", false - } - */ + if slices.Contains(videoFormats, ext) { + return Video + } + + if slices.Contains(presentationFormats, ext) { + return Presentation + } + + return Unspecified } func ParseSlogLevel(s string) slog.Level {