Files
MrixsCraft-server/cmd/server/main.go
Vladimir Zagainov 5fba2e78d5 feat: add Docker infrastructure, migrations, CI/CD client, session cleanup, tests
Docker & Deployment:
- Add Dockerfile (multi-stage, alpine, non-root)
- Add docker-compose.yml (caddy, backend, postgres, watchtower)
- Add Caddyfile (TLS, file_server, reverse proxy)
- Add .env.example

Database:
- Add migrations/001_init.sql (all tables + indexes)

CI/CD:
- Add cmd/ci-release/main.go (launcher binary upload tool)

Session management:
- Add internal/session/cleanup.go (background expired session cleanup)
- Integrate cleanup worker into main.go

Bug fixes:
- Fix launcherLatest download URL to include version segment
- Fix serveLauncherAsset path to match route pattern
- Add Content-Type detection from file extension in CAS serveFile
- Add empty-field validation in webLogin
- Format string fix in ci-release (%d → %s for resp.Status)

Tests:
- Add internal/auth/auth_test.go (8 tests)
- Add internal/cas/cas_test.go (7 tests)
- Add internal/session/cleanup_test.go (1 test)
- Add internal/api/api_test.go (5 tests)
- All tests passing, go vet clean

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-29 20:09:00 +03:00

107 lines
2.7 KiB
Go

package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"
"gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/admin"
"gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/api"
"gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/auth"
"gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/cas"
"gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/config"
"gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/database"
"gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/middleware"
"gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/session"
"gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/templates"
)
func main() {
ctx := context.Background()
cfg, err := config.Load()
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
db, err := database.Open(ctx, cfg.DatabaseURL)
if err != nil {
log.Fatalf("Failed to connect to database: %v", err)
}
defer db.Close()
// Start session cleanup worker (runs every hour in the background).
cleanupCancel := session.StartCleanupWorker(db, 1*time.Hour)
defer cleanupCancel()
mux := http.NewServeMux()
// Health check — no auth needed.
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
})
// Yggdrasil API.
authHandler := auth.NewHandler(db, cfg)
authHandler.RegisterRoutes(mux)
// Public API.
apiHandler := api.NewHandler(db, cfg)
apiHandler.RegisterRoutes(mux)
// CAS (Content-Addressable Storage) file serving.
casHandler := cas.NewHandler(db, cfg)
casHandler.RegisterRoutes(mux)
// Admin panel.
adminHandler := admin.NewHandler(db, cfg)
adminHandler.RegisterRoutes(mux)
// Templates (web UI).
templatesHandler := templates.NewHandler(db, cfg)
templatesHandler.RegisterRoutes(mux)
// Wrapper chain: Logging → CORS → mux.
var handler http.Handler = mux
handler = middleware.CORS(handler)
handler = middleware.Logging(handler)
addr := ":" + itoa(cfg.Port)
srv := &http.Server{
Addr: addr,
Handler: handler,
}
// Graceful shutdown.
done := make(chan os.Signal, 1)
signal.Notify(done, syscall.SIGINT, syscall.SIGTERM)
go func() {
log.Printf("MrixsCraft Server starting on %s", addr)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server error: %v", err)
}
}()
<-done
log.Println("Shutting down…")
shutdownCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Printf("Shutdown error: %v", err)
}
log.Println("Stopped.")
}
// itoa converts int to string (stdlib alias to avoid fmt import just for this).
func itoa(n int) string {
return strconv.Itoa(n)
}