Files
Maintainarr/internal/app/app.go
GigabiteStudios 20103d9793
Some checks failed
Verify / verify (push) Failing after 17s
feat(logs): archive command history daily
2026-06-20 20:05:16 -05:00

152 lines
5.1 KiB
Go

package app
import (
"context"
"database/sql"
"fmt"
"net/http"
"time"
"github.com/go-chi/chi/v5"
chimiddleware "github.com/go-chi/chi/v5/middleware"
"maintainarr/internal/config"
"maintainarr/internal/db"
"maintainarr/internal/handlers"
localmiddleware "maintainarr/internal/middleware"
"maintainarr/internal/models"
"maintainarr/internal/services"
"maintainarr/internal/views"
)
type App struct {
config config.Config
db *sql.DB
server *http.Server
scheduler *services.SchedulerService
}
func New() (*App, error) {
cfg := config.Load()
database, err := db.Open(cfg.DatabasePath)
if err != nil {
return nil, err
}
repo := services.NewRepository(database)
auth := services.NewAuthService(cfg.OrgName)
crypto, err := services.NewCryptoService(cfg.EncryptionKey)
if err != nil {
return nil, err
}
sessions := services.NewSessionService(cfg.SessionKey)
nodes := services.NewNodeService(database, crypto)
renderer, err := views.NewRenderer()
if err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
org, err := repo.EnsureOrganization(ctx, cfg.OrgName, cfg.DefaultTheme)
if err != nil {
return nil, err
}
if org.ThemeMode == "" {
if err := repo.UpdateOrganizationTheme(ctx, org.Theme, cfg.DefaultMode); err != nil {
return nil, err
}
org.ThemeMode = cfg.DefaultMode
}
if err := repo.SeedDefaults(ctx, org.ID); err != nil {
return nil, err
}
if err := repo.EnsurePrimaryAdmin(ctx); err != nil {
return nil, err
}
handler := handlers.New(repo, auth, sessions, nodes, renderer, org, cfg.BaseURL)
router := chi.NewRouter()
router.Use(chimiddleware.RequestID)
router.Use(chimiddleware.RealIP)
router.Use(chimiddleware.Recoverer)
router.Use(chimiddleware.Logger)
router.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir("web/static"))))
router.Get("/", handler.Home)
router.Get("/login", handler.LoginPage)
router.Post("/login", handler.Login)
router.Get("/login/otp", handler.LoginOTPPage)
router.Post("/login/otp", handler.LoginOTP)
router.Get("/register", handler.RegisterPage)
router.Post("/register", handler.Register)
router.Get("/logout", handler.Logout)
router.Group(func(protected chi.Router) {
protected.Use(localmiddleware.RequireAuth(sessions, repo))
protected.Get("/setup-2fa", handler.SetupOTPPage)
protected.Post("/setup-2fa", handler.SetupOTP)
protected.Get("/dashboard", handler.Dashboard)
protected.Get("/dashboard/nodes", handler.DashboardNodes)
protected.Get("/nodes/{nodeID}", handler.NodeOverview)
protected.Get("/nodes/{nodeID}/stats", handler.NodeStatsAPI)
protected.Get("/nodes/{nodeID}/console", handler.NodeConsole)
protected.Get("/nodes/{nodeID}/console/ws", handler.NodeConsoleWebSocket)
protected.Get("/groups", handler.GroupsPage)
protected.Get("/automations", handler.AutomationsPage)
protected.Get("/updates", handler.UpdatesPage)
protected.Get("/uptime", handler.UptimePage)
protected.Get("/settings", handler.SettingsPage)
protected.Get("/settings/user", handler.UserSettingsPage)
protected.Post("/settings/theme", handler.UpdateTheme)
protected.Post("/settings/user/theme", handler.UpdateUserTheme)
protected.Post("/settings/user/password", handler.UpdateUserPassword)
protected.Post("/settings/user/2fa/{action}", handler.UpdateUserOTP)
protected.Group(func(editor chi.Router) {
editor.Use(localmiddleware.RequireRole(models.RoleEditor))
editor.Post("/groups", handler.CreateGroup)
editor.Post("/groups/{groupID}", handler.UpdateGroup)
editor.Post("/nodes", handler.CreateNode)
editor.Post("/nodes/{nodeID}", handler.UpdateNode)
editor.Post("/nodes/{nodeID}/actions/{action}", handler.NodeAction)
editor.Post("/nodes/{nodeID}/commands", handler.NodeQuickCommand)
editor.Post("/nodes/{nodeID}/delete", handler.DeleteNode)
editor.Post("/automations", handler.CreateAutomation)
editor.Post("/updates/scan", handler.ScanAllUpdates)
editor.Post("/updates/apply", handler.ApplyAllUpdates)
editor.Post("/updates/settings/window", handler.UpdateGlobalUpdateWindow)
editor.Post("/updates/nodes/{nodeID}/policy", handler.UpdateNodePolicy)
editor.Post("/updates/nodes/{nodeID}/scan", handler.ScanNodeUpdates)
editor.Post("/updates/nodes/{nodeID}/apply", handler.ApplyNodeUpdates)
editor.Post("/updates/groups/{groupID}/policy", handler.UpdateGroupPolicy)
editor.Post("/updates/groups/{groupID}/scan", handler.ScanGroupUpdates)
editor.Post("/updates/groups/{groupID}/apply", handler.ApplyGroupUpdates)
editor.Post("/uptime/run", handler.RunUptimeChecks)
})
})
scheduler := services.NewSchedulerService(database, nodes, cfg.LogArchiveDir)
if err := scheduler.Start(context.Background(), org.ID, cfg.RefreshCron); err != nil {
return nil, err
}
return &App{
config: cfg,
db: database,
server: &http.Server{
Addr: cfg.Address,
Handler: router,
ReadHeaderTimeout: 10 * time.Second,
},
scheduler: scheduler,
}, nil
}
func (a *App) Run() error {
fmt.Printf("Maintainarr listening on %s\n", a.config.Address)
return a.server.ListenAndServe()
}