Initial commit with server startup infrastructure
Core infrastructure: - Uber fx dependency injection - Chi router with middleware stack - SQLite database with embedded migrations - Embedded templates and static assets - Structured logging with slog Features implemented: - Authentication (login, logout, session management, argon2id hashing) - App management (create, edit, delete, list) - Deployment pipeline (clone, build, deploy, health check) - Webhook processing for Gitea - Notifications (ntfy, Slack) - Environment variables, labels, volumes per app - SSH key generation for deploy keys Server startup: - Server.Run() starts HTTP server on configured port - Server.Shutdown() for graceful shutdown - SetupRoutes() wires all handlers with chi router
This commit is contained in:
139
internal/config/config.go
Normal file
139
internal/config/config.go
Normal file
@@ -0,0 +1,139 @@
|
||||
// Package config provides application configuration via Viper.
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/fx"
|
||||
|
||||
"git.eeqj.de/sneak/upaas/internal/globals"
|
||||
"git.eeqj.de/sneak/upaas/internal/logger"
|
||||
)
|
||||
|
||||
// defaultPort is the default HTTP server port.
|
||||
const defaultPort = 8080
|
||||
|
||||
// Params contains dependencies for Config.
|
||||
type Params struct {
|
||||
fx.In
|
||||
|
||||
Globals *globals.Globals
|
||||
Logger *logger.Logger
|
||||
}
|
||||
|
||||
// Config holds application configuration.
|
||||
type Config struct {
|
||||
Port int
|
||||
Debug bool
|
||||
DataDir string
|
||||
DockerHost string
|
||||
SentryDSN string
|
||||
MaintenanceMode bool
|
||||
MetricsUsername string
|
||||
MetricsPassword string
|
||||
SessionSecret string
|
||||
params *Params
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
// New creates a new Config instance from environment and config files.
|
||||
func New(_ fx.Lifecycle, params Params) (*Config, error) {
|
||||
log := params.Logger.Get()
|
||||
|
||||
name := params.Globals.Appname
|
||||
if name == "" {
|
||||
name = "upaas"
|
||||
}
|
||||
|
||||
setupViper(name)
|
||||
|
||||
cfg, err := buildConfig(log, ¶ms)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
configureDebugLogging(cfg, params)
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func setupViper(name string) {
|
||||
// Config file settings
|
||||
viper.SetConfigName(name)
|
||||
viper.SetConfigType("yaml")
|
||||
viper.AddConfigPath("/etc/" + name)
|
||||
viper.AddConfigPath("$HOME/.config/" + name)
|
||||
viper.AddConfigPath(".")
|
||||
|
||||
// Environment variables override everything
|
||||
viper.SetEnvPrefix("UPAAS")
|
||||
viper.AutomaticEnv()
|
||||
|
||||
// Defaults
|
||||
viper.SetDefault("PORT", defaultPort)
|
||||
viper.SetDefault("DEBUG", false)
|
||||
viper.SetDefault("DATA_DIR", "./data")
|
||||
viper.SetDefault("DOCKER_HOST", "unix:///var/run/docker.sock")
|
||||
viper.SetDefault("SENTRY_DSN", "")
|
||||
viper.SetDefault("MAINTENANCE_MODE", false)
|
||||
viper.SetDefault("METRICS_USERNAME", "")
|
||||
viper.SetDefault("METRICS_PASSWORD", "")
|
||||
viper.SetDefault("SESSION_SECRET", "")
|
||||
}
|
||||
|
||||
func buildConfig(log *slog.Logger, params *Params) (*Config, error) {
|
||||
// Read config file (optional)
|
||||
err := viper.ReadInConfig()
|
||||
if err != nil {
|
||||
var configFileNotFoundError viper.ConfigFileNotFoundError
|
||||
if !errors.As(err, &configFileNotFoundError) {
|
||||
log.Error("config file malformed", "error", err)
|
||||
|
||||
return nil, fmt.Errorf("config file malformed: %w", err)
|
||||
}
|
||||
// Config file not found is OK
|
||||
}
|
||||
|
||||
// Build config struct
|
||||
cfg := &Config{
|
||||
Port: viper.GetInt("PORT"),
|
||||
Debug: viper.GetBool("DEBUG"),
|
||||
DataDir: viper.GetString("DATA_DIR"),
|
||||
DockerHost: viper.GetString("DOCKER_HOST"),
|
||||
SentryDSN: viper.GetString("SENTRY_DSN"),
|
||||
MaintenanceMode: viper.GetBool("MAINTENANCE_MODE"),
|
||||
MetricsUsername: viper.GetString("METRICS_USERNAME"),
|
||||
MetricsPassword: viper.GetString("METRICS_PASSWORD"),
|
||||
SessionSecret: viper.GetString("SESSION_SECRET"),
|
||||
params: params,
|
||||
log: log,
|
||||
}
|
||||
|
||||
// Generate session secret if not set
|
||||
if cfg.SessionSecret == "" {
|
||||
cfg.SessionSecret = "change-me-in-production-please"
|
||||
|
||||
log.Warn(
|
||||
"using default session secret, " +
|
||||
"set UPAAS_SESSION_SECRET in production",
|
||||
)
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func configureDebugLogging(cfg *Config, params Params) {
|
||||
// Enable debug logging if configured
|
||||
if cfg.Debug {
|
||||
params.Logger.EnableDebugLogging()
|
||||
cfg.log = params.Logger.Get()
|
||||
}
|
||||
}
|
||||
|
||||
// DatabasePath returns the full path to the SQLite database file.
|
||||
func (c *Config) DatabasePath() string {
|
||||
return c.DataDir + "/upaas.db"
|
||||
}
|
||||
Reference in New Issue
Block a user