diff options
| author | Kyren223 <Kyren223@proton.me> | 2025-07-07 18:46:47 +0300 |
|---|---|---|
| committer | Kyren223 <Kyren223@proton.me> | 2025-07-07 18:46:47 +0300 |
| commit | 047d33b3aede7f0fa50997abf3c20aa2b56ebcda (patch) | |
| tree | d9a0aedd2c345d9b9b35962533c647998d4b1379 /cmd | |
| parent | d58c7a20f19330625b28d14a6b50a69eed0406cf (diff) | |
Refactored how the terms of service and privacy policy are stored, by
allowing changing them with 0 downtime
Diffstat (limited to 'cmd')
| -rw-r--r-- | cmd/server/main.go | 118 |
1 files changed, 108 insertions, 10 deletions
diff --git a/cmd/server/main.go b/cmd/server/main.go index 862916e..02635f3 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -2,7 +2,9 @@ package main import ( "context" + "crypto/sha256" "flag" + "fmt" "log" "log/slog" "os" @@ -11,14 +13,21 @@ import ( "syscall" "time" + "github.com/kyren223/eko/embeds" "github.com/kyren223/eko/internal/server" "github.com/kyren223/eko/internal/server/api" "github.com/kyren223/eko/internal/server/ctxkeys" + "github.com/kyren223/eko/internal/webserver" "github.com/kyren223/eko/pkg/assert" "gopkg.in/natefinch/lumberjack.v2" ) -const port = 7223 +const ( + port = 7223 + TosEnvVar = "EKO_SERVER_TOS_FILE" + PrivacyEnvVar = "EKO_SERVER_PRIVACY_FILE" + LogDirEnvVar = "EKO_SERVER_LOG_DIR" +) var prod = true @@ -27,31 +36,60 @@ func main() { flag.Parse() prod = !(*prodFlag) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + setupLogging() - slog.Info("starting eko-server...") + handleReload() + handleShutdown(cancel) + + if ok := reloadTosAndPrivacy(); !ok { + return + } api.ConnectToDatabase() assert.AddFlush(api.DB()) defer api.DB().Close() - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + go webserver.ServeEkoWebsite() + + server := server.NewServer(ctx, port) + server.Run() // blocks + slog.Info("exited gracefully") +} + +func handleReload() { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP) + + go func() { + for range c { + slog.Info("SIGHUP received, reloading...") + reloadTosAndPrivacy() + slog.Info("reload completed") + } + }() + + slog.Info("reload handler ready") +} + +func handleShutdown(cancel context.CancelFunc) { signalChan := make(chan os.Signal, 1) signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) + go func() { signal := <-signalChan - slog.Info("received signal", "signal", signal) + slog.Info("shutdown signal received", "signal", signal) cancel() }() - server := server.NewServer(ctx, port) - server.Run() // blocks + slog.Info("shutdown handler ready") } func setupLogging() { - logDir := os.Getenv("EKO_SERVER_LOG_DIR") + logDir := os.Getenv(LogDirEnvVar) if logDir == "" { logDir = "logs" } @@ -63,7 +101,7 @@ func setupLogging() { rotator := &lumberjack.Logger{ Filename: filepath.Join(logDir, "server.log"), MaxSize: 100, // megabytes - MaxAge: 28, // days + MaxAge: 7, // days (see Privacy Section 3: Log Retention) } level := slog.LevelDebug @@ -80,9 +118,11 @@ func setupLogging() { slog.SetDefault(logger) slog.SetLogLoggerLevel(level) // TODO: remove me after fully migrating to slog + slog.Info("logging handler ready") + go func() { for { - now := time.Now() + now := time.Now().UTC() // UTC Time (see Privacy Section 3) next := now.Truncate(24 * time.Hour).Add(24 * time.Hour) time.Sleep(time.Until(next)) // sleep until next midnight @@ -95,3 +135,61 @@ func setupLogging() { } }() } + +func reloadTosAndPrivacy() bool { + if embeds.TosPrivacyHash.Load() == nil { + if prod { + embeds.TosPrivacyHash.Store("") + embeds.TermsOfService.Store("") + embeds.PrivacyPolicy.Store("") + } else { + // Set stub values for development + embeds.TermsOfService.Store(embeds.StubTos) + embeds.PrivacyPolicy.Store(embeds.StubPrivacy) + tosPrivacy := []byte(string(embeds.StubTos) + string(embeds.StubPrivacy)) + stubHash := fmt.Sprintf("%x", sha256.Sum256(tosPrivacy)) + embeds.TosPrivacyHash.Store(stubHash) + return true + } + } + + tosFile := os.Getenv(TosEnvVar) + privacyFile := os.Getenv(PrivacyEnvVar) + if tosFile == "" || privacyFile == "" { + if prod { + slog.Error("TOS or Privacy env vars are undefined", TosEnvVar, tosFile, PrivacyEnvVar, privacyFile) + return false + } + } + tos, err := os.ReadFile(tosFile) + if err != nil { + slog.Error("error reading TOS file", "error", err) + return false + } + privacy, err := os.ReadFile(privacyFile) + if err != nil { + slog.Error("error reading Privacy file", "error", err) + return false + } + + tosPrivacy := []byte(string(tos) + string(privacy)) + hash := fmt.Sprintf("%x", sha256.Sum256(tosPrivacy)) + + oldHash := embeds.TosPrivacyHash.Load().(string) + if oldHash == hash { + slog.Info("updated nothing, tos+privacy hash remained the same", "hash", hash) + return true + } + + embeds.TermsOfService.Store(tos) + embeds.PrivacyPolicy.Store(privacy) + embeds.TosPrivacyHash.Store(hash) + + if oldHash == "" { + slog.Info("loaded Terms of Service and Privacy Policy", "hash", hash) + } else { + slog.Info("updated tos+privacy, hash changed", "hash", hash, "old_hash", hash) + } + + return true +} |
