feat: scaffold app skeleton with config, ULID, health endpoints, compose stack (phase 1)

- internal .env loader (real env wins) + strictly validated config struct
- ULID package: 48-bit ms timestamp + 80-bit crypto randomness, sortable
- HTTP server with recovery/request-id/trusted-proxy/access-log middleware
- /healthz, /readyz (pluggable checkers), /metricsz (counter registry)
- multi-stage Dockerfile (alpine, non-root) + compose with healthchecks,
  app bound to 127.0.0.1:8787, mongo unpublished, graceful 15s shutdown

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
etalon
2026-06-12 18:10:30 +02:00
commit 976f5238f8
26 changed files with 2478 additions and 0 deletions
+50
View File
@@ -0,0 +1,50 @@
package config
import (
"bufio"
"fmt"
"os"
"strings"
)
// LoadDotEnv reads a dotenv file and sets each variable into the process
// environment unless it is already set (real environment wins). Supported
// syntax: KEY=VALUE, blank lines, full-line and unquoted inline comments,
// optional `export ` prefix, single- or double-quoted values.
func LoadDotEnv(path string) error {
f, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return fmt.Errorf("open %s: %w", path, err)
}
defer f.Close()
sc := bufio.NewScanner(f)
for n := 1; sc.Scan(); n++ {
line := strings.TrimSpace(sc.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
line = strings.TrimPrefix(line, "export ")
key, val, ok := strings.Cut(line, "=")
if !ok || strings.TrimSpace(key) == "" {
return fmt.Errorf("%s:%d: not a KEY=VALUE line", path, n)
}
key = strings.TrimSpace(key)
val = strings.TrimSpace(val)
switch {
case len(val) >= 2 && (val[0] == '"' || val[0] == '\'') && val[len(val)-1] == val[0]:
val = val[1 : len(val)-1]
default: // unquoted: strip trailing inline comment
if i := strings.Index(val, " #"); i >= 0 {
val = strings.TrimSpace(val[:i])
}
}
if _, exists := os.LookupEnv(key); !exists {
os.Setenv(key, val)
}
}
return sc.Err()
}