feat: add first page with auth and containers list and agents
This commit is contained in:
139
server/cmd/server/main.go
Normal file
139
server/cmd/server/main.go
Normal file
@ -0,0 +1,139 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/containarr/server/internal/api"
|
||||
"github.com/containarr/server/internal/broker"
|
||||
grpcgateway "github.com/containarr/server/internal/grpc"
|
||||
agentv1 "github.com/containarr/server/internal/proto/agentv1"
|
||||
"github.com/containarr/server/internal/store"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func main() {
|
||||
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, nil)))
|
||||
|
||||
dbPath := getenv("DB_PATH", "/data/containarr.db")
|
||||
httpAddr := getenv("HTTP_ADDR", ":8080")
|
||||
grpcAddr := getenv("GRPC_ADDR", ":9090")
|
||||
|
||||
db, err := store.New(dbPath)
|
||||
must(err, "open store")
|
||||
defer db.Close()
|
||||
|
||||
bootstrapAdmin(db)
|
||||
bootstrapTokens(db)
|
||||
|
||||
reg := grpcgateway.NewRegistry()
|
||||
brk := broker.New()
|
||||
|
||||
// gRPC server.
|
||||
gw := grpcgateway.NewGateway(db, reg, brk)
|
||||
grpcServer := grpc.NewServer()
|
||||
agentv1.RegisterAgentGatewayServer(grpcServer, gw)
|
||||
|
||||
lis, err := net.Listen("tcp", grpcAddr)
|
||||
must(err, "listen grpc")
|
||||
|
||||
go func() {
|
||||
slog.Info("gRPC listening", "addr", grpcAddr)
|
||||
if err := grpcServer.Serve(lis); err != nil {
|
||||
slog.Error("gRPC serve", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// HTTP server.
|
||||
h := api.NewHandler(db, reg, brk)
|
||||
httpServer := &http.Server{
|
||||
Addr: httpAddr,
|
||||
Handler: api.NewRouter(h),
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 0, // disabled for WebSocket handlers
|
||||
}
|
||||
|
||||
go func() {
|
||||
slog.Info("HTTP listening", "addr", httpAddr)
|
||||
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
slog.Error("HTTP serve", "err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Graceful shutdown.
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
|
||||
slog.Info("shutting down")
|
||||
grpcServer.GracefulStop()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
_ = httpServer.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func getenv(key, fallback string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
// bootstrapAdmin creates the admin user from env vars if it doesn't exist yet.
|
||||
func bootstrapAdmin(db *store.Store) {
|
||||
username := getenv("ADMIN_USER", "admin")
|
||||
password := getenv("ADMIN_PASSWORD", "admin")
|
||||
|
||||
exists, err := db.UserExists(username)
|
||||
if err != nil || exists {
|
||||
return
|
||||
}
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
slog.Error("bcrypt admin", "err", err)
|
||||
return
|
||||
}
|
||||
if err := db.UpsertUser(username, string(hash)); err != nil {
|
||||
slog.Error("seed admin user", "err", err)
|
||||
return
|
||||
}
|
||||
slog.Info("admin user created", "username", username)
|
||||
}
|
||||
|
||||
// bootstrapTokens seeds agent tokens from BOOTSTRAP_TOKENS env var.
|
||||
// Format: "hostname:token,hostname2:token2"
|
||||
func bootstrapTokens(db *store.Store) {
|
||||
raw := os.Getenv("BOOTSTRAP_TOKENS")
|
||||
if raw == "" {
|
||||
return
|
||||
}
|
||||
for _, pair := range strings.Split(raw, ",") {
|
||||
parts := strings.SplitN(strings.TrimSpace(pair), ":", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
hostname, token := parts[0], parts[1]
|
||||
if err := db.CreateAgentToken(uuid.NewString(), token, hostname); err != nil {
|
||||
slog.Warn("bootstrap token already exists", "hostname", hostname)
|
||||
} else {
|
||||
slog.Info("bootstrapped agent token", "hostname", hostname)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func must(err error, msg string) {
|
||||
if err != nil {
|
||||
slog.Error(msg, "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user