97d4243cfe
All REST endpoints wired with chi router: projects, stages, instances, deploys, registries, settings, quick deploy, webhook. Full main.go wiring with graceful shutdown. Consistent JSON envelope responses. Sensitive fields stripped from API responses.
143 lines
3.8 KiB
Go
143 lines
3.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/alexei/docker-watcher/internal/api"
|
|
"github.com/alexei/docker-watcher/internal/config"
|
|
"github.com/alexei/docker-watcher/internal/crypto"
|
|
"github.com/alexei/docker-watcher/internal/deployer"
|
|
"github.com/alexei/docker-watcher/internal/docker"
|
|
"github.com/alexei/docker-watcher/internal/health"
|
|
"github.com/alexei/docker-watcher/internal/notify"
|
|
"github.com/alexei/docker-watcher/internal/npm"
|
|
"github.com/alexei/docker-watcher/internal/registry"
|
|
"github.com/alexei/docker-watcher/internal/store"
|
|
"github.com/alexei/docker-watcher/internal/webhook"
|
|
)
|
|
|
|
func main() {
|
|
dataDir := envOrDefault("DATA_DIR", "./data")
|
|
|
|
if err := os.MkdirAll(dataDir, 0o755); err != nil {
|
|
log.Fatalf("create data directory: %v", err)
|
|
}
|
|
|
|
// Open database.
|
|
dbPath := filepath.Join(dataDir, "docker-watcher.db")
|
|
db, err := store.New(dbPath)
|
|
if err != nil {
|
|
log.Fatalf("open store: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
// Import seed config on first launch (idempotent).
|
|
seedPath := envOrDefault("SEED_FILE", "./docker-watcher.yaml")
|
|
if err := config.ImportSeed(db, seedPath); err != nil {
|
|
log.Fatalf("seed import: %v", err)
|
|
}
|
|
|
|
// Derive encryption key from environment.
|
|
encKey, err := crypto.KeyFromEnv()
|
|
if err != nil {
|
|
log.Printf("WARNING: %v — encrypted fields will not work", err)
|
|
encKey = crypto.DeriveKey("docker-watcher-default-key")
|
|
}
|
|
|
|
// Initialize Docker client.
|
|
dockerClient, err := docker.New()
|
|
if err != nil {
|
|
log.Fatalf("create docker client: %v", err)
|
|
}
|
|
defer dockerClient.Close()
|
|
|
|
// Read settings for NPM URL and polling interval.
|
|
settings, err := db.GetSettings()
|
|
if err != nil {
|
|
log.Fatalf("get settings: %v", err)
|
|
}
|
|
|
|
// Initialize NPM client.
|
|
npmURL := envOrDefault("NPM_URL", settings.NpmURL)
|
|
npmClient := npm.New(npmURL)
|
|
|
|
// Initialize services.
|
|
healthChecker := health.New()
|
|
notifier := notify.New()
|
|
|
|
dep := deployer.New(dockerClient, npmClient, db, healthChecker, notifier, encKey)
|
|
|
|
// Initialize webhook handler.
|
|
webhookHandler := webhook.NewHandler(db, dep, dockerClient)
|
|
|
|
// Ensure webhook secret exists.
|
|
secret, err := webhook.EnsureWebhookSecret(db)
|
|
if err != nil {
|
|
log.Fatalf("ensure webhook secret: %v", err)
|
|
}
|
|
log.Printf("Webhook secret: %s", secret)
|
|
|
|
// Initialize registry poller.
|
|
poller := registry.NewPoller(db, dep, encKey)
|
|
pollingInterval := envOrDefault("POLLING_INTERVAL", settings.PollingInterval)
|
|
if pollingInterval != "" {
|
|
if err := poller.Start(pollingInterval); err != nil {
|
|
log.Printf("WARNING: failed to start poller: %v", err)
|
|
}
|
|
}
|
|
|
|
// Build API server.
|
|
apiServer := api.NewServer(db, dockerClient, dep, webhookHandler, encKey)
|
|
router := apiServer.Router()
|
|
|
|
// Start HTTP server.
|
|
addr := envOrDefault("LISTEN_ADDR", ":8080")
|
|
httpServer := &http.Server{
|
|
Addr: addr,
|
|
Handler: router,
|
|
ReadTimeout: 30 * time.Second,
|
|
WriteTimeout: 60 * time.Second,
|
|
IdleTimeout: 120 * time.Second,
|
|
}
|
|
|
|
// Graceful shutdown.
|
|
done := make(chan os.Signal, 1)
|
|
signal.Notify(done, os.Interrupt, syscall.SIGTERM)
|
|
|
|
go func() {
|
|
log.Printf("Docker Watcher started. Listening on %s", addr)
|
|
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
log.Fatalf("HTTP server error: %v", err)
|
|
}
|
|
}()
|
|
|
|
<-done
|
|
log.Println("Shutting down...")
|
|
|
|
poller.Stop()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
if err := httpServer.Shutdown(ctx); err != nil {
|
|
log.Printf("HTTP server shutdown error: %v", err)
|
|
}
|
|
|
|
log.Println("Docker Watcher stopped.")
|
|
}
|
|
|
|
// envOrDefault reads an environment variable or returns the fallback value.
|
|
func envOrDefault(key, fallback string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|