diff options
| author | gramanas <grm@eyesin.space> | 2026-09-12 11:24:17 +0300 |
|---|---|---|
| committer | gramanas <grm@eyesin.space> | 2026-09-12 11:24:17 +0300 |
| commit | 3eb04b1a2bdf9e53231fe862cfd76327371a9741 (patch) | |
| tree | b38b2d82a47233fd8e0bb18c59e4a8f3dd2412d7 /internal/config/config.go | |
| download | blogspace-3eb04b1a2bdf9e53231fe862cfd76327371a9741.tar.gz blogspace-3eb04b1a2bdf9e53231fe862cfd76327371a9741.tar.bz2 blogspace-3eb04b1a2bdf9e53231fe862cfd76327371a9741.zip | |
Initial multi-tenant blog host
Go + Postgres application serving a management dashboard on the base
domain and one public blog per subdomain. Markdown posts organised in
pages, form-based theme customisation, image uploads stored in Postgres,
JWT cookie sessions with CSRF, superadmin user management, RSS feeds.
Docker/compose deployment and a Makefile-driven dev environment with
seed data.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
Diffstat (limited to 'internal/config/config.go')
| -rw-r--r-- | internal/config/config.go | 90 |
1 files changed, 90 insertions, 0 deletions
diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..4972178 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,90 @@ +// Package config reads application settings from the environment. +package config + +import ( + "fmt" + "os" + "strconv" + "strings" +) + +type Config struct { + Addr string // listen address, e.g. ":8080" + BaseDomain string // e.g. "example.com" (no port) + PublicPort string // port to append to generated blog URLs ("" in prod behind a proxy) + DatabaseURL string + JWTSecret []byte + // Bootstrap superadmin created on first start if no superadmin exists. + SuperadminUsername string + SuperadminPassword string + MaxUploadBytes int64 + Dev bool // reload templates/static from disk +} + +func Load() (*Config, error) { + c := &Config{ + Addr: env("ADDR", ":8080"), + BaseDomain: strings.ToLower(strings.TrimSpace(env("BASE_DOMAIN", "blogspace.localhost"))), + PublicPort: env("PUBLIC_PORT", ""), + DatabaseURL: env("DATABASE_URL", "postgres://blogspace:blogspace@localhost:5432/blogspace?sslmode=disable"), + JWTSecret: []byte(env("JWT_SECRET", "")), + SuperadminUsername: env("SUPERADMIN_USERNAME", "admin"), + SuperadminPassword: env("SUPERADMIN_PASSWORD", ""), + Dev: envBool("DEV", false), + } + mb, err := strconv.Atoi(env("MAX_UPLOAD_MB", "5")) + if err != nil || mb <= 0 { + return nil, fmt.Errorf("MAX_UPLOAD_MB must be a positive integer") + } + c.MaxUploadBytes = int64(mb) << 20 + + if len(c.JWTSecret) == 0 { + if !c.Dev { + return nil, fmt.Errorf("JWT_SECRET must be set") + } + c.JWTSecret = []byte("dev-insecure-secret-change-me") + } + if len(c.JWTSecret) < 16 && !c.Dev { + return nil, fmt.Errorf("JWT_SECRET must be at least 16 bytes") + } + if c.BaseDomain == "" { + return nil, fmt.Errorf("BASE_DOMAIN must be set") + } + return c, nil +} + +// BlogURL returns the public URL for a blog subdomain. +func (c *Config) BlogURL(sub string) string { + return "http://" + sub + "." + c.HostWithPort() +} + +// RootURL returns the public URL of the management site. +func (c *Config) RootURL() string { + return "http://" + c.HostWithPort() +} + +func (c *Config) HostWithPort() string { + if c.PublicPort != "" { + return c.BaseDomain + ":" + c.PublicPort + } + return c.BaseDomain +} + +func env(key, def string) string { + if v, ok := os.LookupEnv(key); ok { + return v + } + return def +} + +func envBool(key string, def bool) bool { + v, ok := os.LookupEnv(key) + if !ok { + return def + } + switch strings.ToLower(v) { + case "1", "true", "yes", "on": + return true + } + return false +} |
