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:
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user