feat: scaffold project with config, store, health endpoint, CI, and Dockerfile

Phase 1 of foreman: initialize the Go module, project layout, and core
infrastructure. Includes env-based configuration (FOREMAN_* namespace),
SQLite-backed durable job queue with WAL mode via modernc.org/sqlite,
stdlib HTTP server with /healthz and optional bearer-token auth middleware,
subcommand dispatch (serve + stubs), Gitea CI workflow, multi-stage
distroless Dockerfile, and comprehensive tests for all packages.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-23 17:58:36 -04:00
parent d5702f7a75
commit 9cdf4b2472
15 changed files with 1474 additions and 0 deletions
+78
View File
@@ -0,0 +1,78 @@
// Package main is the entry point for the foreman daemon.
//
// Why: foreman is a single binary with subcommands; the main package handles
// argument dispatch and wiring.
// What: parses subcommands (serve, submit, jobs, ps) and runs the selected one.
// Test: build and run with --help; test individual packages separately.
package main
import (
"fmt"
"log/slog"
"os"
"gitea.stevedudenhoeffer.com/steve/foreman/internal/config"
"gitea.stevedudenhoeffer.com/steve/foreman/internal/server"
"gitea.stevedudenhoeffer.com/steve/foreman/internal/store"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
cmd := "serve"
if len(os.Args) > 1 {
cmd = os.Args[1]
}
switch cmd {
case "serve":
if err := runServe(logger); err != nil {
logger.Error("serve failed", "error", err)
os.Exit(1)
}
case "submit":
fmt.Fprintln(os.Stderr, "submit: not yet implemented")
os.Exit(1)
case "jobs":
fmt.Fprintln(os.Stderr, "jobs: not yet implemented")
os.Exit(1)
case "ps":
fmt.Fprintln(os.Stderr, "ps: not yet implemented")
os.Exit(1)
default:
fmt.Fprintf(os.Stderr, "unknown command: %s\nusage: foreman [serve|submit|jobs|ps]\n", cmd)
os.Exit(1)
}
}
// runServe loads configuration, opens the store, and starts the HTTP server.
//
// Why: the serve subcommand is the daemon's primary mode of operation.
// What: wires config -> store -> server and blocks on ListenAndServe.
// Test: tested indirectly via integration tests; each component is unit tested.
func runServe(logger *slog.Logger) error {
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("load config: %w", err)
}
logger.Info("configuration loaded",
"addr", cfg.Addr,
"ollama_url", cfg.OllamaURL,
"db_path", cfg.DBPath,
"poll_interval", cfg.PollInterval,
"embed_model", cfg.EmbedModel,
"auth_enabled", cfg.Token != "",
)
st, err := store.Open(cfg.DBPath)
if err != nil {
return fmt.Errorf("open store: %w", err)
}
defer st.Close()
srv := server.New(cfg, st, logger)
return srv.ListenAndServe()
}