271 lines
7.2 KiB
Go
271 lines
7.2 KiB
Go
package httpapp
|
|
|
|
import (
|
|
"fmt"
|
|
"html/template"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"gitea.avt.data-center.id/othman.suseno/atlas/internal/audit"
|
|
"gitea.avt.data-center.id/othman.suseno/atlas/internal/auth"
|
|
"gitea.avt.data-center.id/othman.suseno/atlas/internal/backup"
|
|
"gitea.avt.data-center.id/othman.suseno/atlas/internal/db"
|
|
"gitea.avt.data-center.id/othman.suseno/atlas/internal/job"
|
|
"gitea.avt.data-center.id/othman.suseno/atlas/internal/maintenance"
|
|
"gitea.avt.data-center.id/othman.suseno/atlas/internal/metrics"
|
|
"gitea.avt.data-center.id/othman.suseno/atlas/internal/services"
|
|
"gitea.avt.data-center.id/othman.suseno/atlas/internal/snapshot"
|
|
"gitea.avt.data-center.id/othman.suseno/atlas/internal/storage"
|
|
"gitea.avt.data-center.id/othman.suseno/atlas/internal/tls"
|
|
"gitea.avt.data-center.id/othman.suseno/atlas/internal/zfs"
|
|
)
|
|
|
|
type Config struct {
|
|
Addr string
|
|
TemplatesDir string
|
|
StaticDir string
|
|
DatabaseConn string // Database connection string (SQLite path or PostgreSQL connection string, empty = in-memory mode)
|
|
}
|
|
|
|
type App struct {
|
|
cfg Config
|
|
tmpl *template.Template
|
|
mux *http.ServeMux
|
|
zfs *zfs.Service
|
|
snapshotPolicy *snapshot.PolicyStore
|
|
jobManager *job.Manager
|
|
scheduler *snapshot.Scheduler
|
|
authService *auth.Service
|
|
userStore *auth.UserStore
|
|
auditStore *audit.Store
|
|
smbStore *storage.SMBStore
|
|
nfsStore *storage.NFSStore
|
|
iscsiStore *storage.ISCSIStore
|
|
database *db.DB // Optional database connection
|
|
smbService *services.SMBService
|
|
nfsService *services.NFSService
|
|
iscsiService *services.ISCSIService
|
|
metricsCollector *metrics.Collector
|
|
startTime time.Time
|
|
backupService *backup.Service
|
|
maintenanceService *maintenance.Service
|
|
tlsConfig *tls.Config
|
|
}
|
|
|
|
func New(cfg Config) (*App, error) {
|
|
// Resolve paths relative to executable or current working directory
|
|
if cfg.TemplatesDir == "" {
|
|
// Try multiple locations for templates
|
|
possiblePaths := []string{
|
|
"web/templates",
|
|
"./web/templates",
|
|
"/opt/atlas/web/templates",
|
|
}
|
|
for _, path := range possiblePaths {
|
|
if _, err := os.Stat(path); err == nil {
|
|
cfg.TemplatesDir = path
|
|
break
|
|
}
|
|
}
|
|
if cfg.TemplatesDir == "" {
|
|
cfg.TemplatesDir = "web/templates" // Default fallback
|
|
}
|
|
}
|
|
if cfg.StaticDir == "" {
|
|
// Try multiple locations for static files
|
|
possiblePaths := []string{
|
|
"web/static",
|
|
"./web/static",
|
|
"/opt/atlas/web/static",
|
|
}
|
|
for _, path := range possiblePaths {
|
|
if _, err := os.Stat(path); err == nil {
|
|
cfg.StaticDir = path
|
|
break
|
|
}
|
|
}
|
|
if cfg.StaticDir == "" {
|
|
cfg.StaticDir = "web/static" // Default fallback
|
|
}
|
|
}
|
|
|
|
tmpl, err := parseTemplates(cfg.TemplatesDir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
zfsService := zfs.New()
|
|
policyStore := snapshot.NewPolicyStore()
|
|
jobMgr := job.NewManager()
|
|
scheduler := snapshot.NewScheduler(policyStore, zfsService, jobMgr)
|
|
|
|
// Initialize database (optional)
|
|
var database *db.DB
|
|
if cfg.DatabaseConn != "" {
|
|
dbConn, err := db.New(cfg.DatabaseConn)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("init database: %w", err)
|
|
}
|
|
database = dbConn
|
|
}
|
|
|
|
// Initialize auth
|
|
jwtSecret := os.Getenv("ATLAS_JWT_SECRET")
|
|
authService := auth.New(jwtSecret)
|
|
userStore := auth.NewUserStore(authService)
|
|
|
|
// Initialize audit logging (keep last 10000 logs)
|
|
auditStore := audit.NewStore(10000)
|
|
|
|
// Initialize storage services
|
|
smbStore := storage.NewSMBStore()
|
|
nfsStore := storage.NewNFSStore()
|
|
iscsiStore := storage.NewISCSIStore()
|
|
|
|
// Initialize service daemon integrations
|
|
smbService := services.NewSMBService()
|
|
nfsService := services.NewNFSService()
|
|
iscsiService := services.NewISCSIService()
|
|
|
|
// Initialize metrics collector
|
|
metricsCollector := metrics.NewCollector()
|
|
startTime := time.Now()
|
|
|
|
// Initialize backup service
|
|
backupDir := os.Getenv("ATLAS_BACKUP_DIR")
|
|
if backupDir == "" {
|
|
backupDir = "data/backups"
|
|
}
|
|
backupService, err := backup.New(backupDir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("init backup service: %w", err)
|
|
}
|
|
|
|
// Initialize maintenance service
|
|
maintenanceService := maintenance.NewService()
|
|
|
|
// Initialize TLS configuration
|
|
tlsConfig := tls.LoadConfig()
|
|
if err := tlsConfig.Validate(); err != nil {
|
|
return nil, fmt.Errorf("TLS configuration: %w", err)
|
|
}
|
|
|
|
a := &App{
|
|
cfg: cfg,
|
|
tmpl: tmpl,
|
|
mux: http.NewServeMux(),
|
|
zfs: zfsService,
|
|
snapshotPolicy: policyStore,
|
|
jobManager: jobMgr,
|
|
scheduler: scheduler,
|
|
authService: authService,
|
|
userStore: userStore,
|
|
auditStore: auditStore,
|
|
smbStore: smbStore,
|
|
nfsStore: nfsStore,
|
|
iscsiStore: iscsiStore,
|
|
database: database,
|
|
smbService: smbService,
|
|
nfsService: nfsService,
|
|
iscsiService: iscsiService,
|
|
metricsCollector: metricsCollector,
|
|
startTime: startTime,
|
|
backupService: backupService,
|
|
maintenanceService: maintenanceService,
|
|
tlsConfig: tlsConfig,
|
|
}
|
|
|
|
// Start snapshot scheduler (runs every 15 minutes)
|
|
scheduler.Start(15 * time.Minute)
|
|
|
|
a.routes()
|
|
return a, nil
|
|
}
|
|
|
|
func (a *App) Router() http.Handler {
|
|
// Middleware chain order (outer to inner):
|
|
// 1. HTTPS enforcement (redirect HTTP to HTTPS)
|
|
// 2. CORS (handles preflight)
|
|
// 3. Compression (gzip)
|
|
// 4. Security headers
|
|
// 5. Request size limit (10MB)
|
|
// 6. Content-Type validation
|
|
// 7. Rate limiting
|
|
// 8. Caching (for GET requests)
|
|
// 9. Error recovery
|
|
// 10. Request ID
|
|
// 11. Logging
|
|
// 12. Audit
|
|
// 13. Authentication
|
|
// 14. Maintenance mode (blocks operations during maintenance)
|
|
// 15. Routes
|
|
return a.httpsEnforcementMiddleware(
|
|
a.corsMiddleware(
|
|
a.compressionMiddleware(
|
|
a.securityHeadersMiddleware(
|
|
a.requestSizeMiddleware(10 * 1024 * 1024)(
|
|
a.validateContentTypeMiddleware(
|
|
a.rateLimitMiddleware(
|
|
a.cacheMiddleware(
|
|
a.errorMiddleware(
|
|
requestID(
|
|
logging(
|
|
a.auditMiddleware(
|
|
a.maintenanceMiddleware(
|
|
a.authMiddleware(a.mux),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
)
|
|
}
|
|
|
|
// StopScheduler stops the snapshot scheduler (for graceful shutdown)
|
|
func (a *App) StopScheduler() {
|
|
if a.scheduler != nil {
|
|
a.scheduler.Stop()
|
|
}
|
|
// Close database connection if present
|
|
if a.database != nil {
|
|
a.database.Close()
|
|
}
|
|
}
|
|
|
|
// routes() is now in routes.go
|
|
|
|
func parseTemplates(dir string) (*template.Template, error) {
|
|
pattern := filepath.Join(dir, "*.html")
|
|
files, err := filepath.Glob(pattern)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Allow empty templates for testing
|
|
if len(files) == 0 {
|
|
// Return empty template instead of error for testing
|
|
return template.New("root"), nil
|
|
}
|
|
|
|
funcs := template.FuncMap{
|
|
"nowRFC3339": func() string { return time.Now().Format(time.RFC3339) },
|
|
"getContentTemplate": func(data map[string]any) string {
|
|
if ct, ok := data["ContentTemplate"].(string); ok && ct != "" {
|
|
return ct
|
|
}
|
|
return "content"
|
|
},
|
|
}
|
|
|
|
t := template.New("root").Funcs(funcs)
|
|
return t.ParseFiles(files...)
|
|
}
|