package main import ( "context" "database/sql" "fmt" "log" "net/http" "os" "path/filepath" "os/signal" "syscall" "time" "github.com/example/storage-appliance/internal/audit" httpin "github.com/example/storage-appliance/internal/http" iscsiinfra "github.com/example/storage-appliance/internal/infra/iscsi" "github.com/example/storage-appliance/internal/infra/nfs" "github.com/example/storage-appliance/internal/infra/osexec" "github.com/example/storage-appliance/internal/infra/samba" "github.com/example/storage-appliance/internal/infra/sqlite/db" "github.com/example/storage-appliance/internal/infra/zfs" "github.com/example/storage-appliance/internal/job" iscsiSvcPkg "github.com/example/storage-appliance/internal/service/iscsi" "github.com/example/storage-appliance/internal/service/mock" "github.com/example/storage-appliance/internal/service/shares" "github.com/example/storage-appliance/internal/service/storage" _ "github.com/glebarez/sqlite" "github.com/go-chi/chi/v5" "github.com/google/uuid" ) func main() { ctx := context.Background() // Determine data directory (use /opt/adastra-storage/data in production, current dir in dev) dataDir := os.Getenv("DATA_DIR") if dataDir == "" { dataDir = os.Getenv("INSTALL_DIR") if dataDir != "" { dataDir = filepath.Join(dataDir, "data") } else { dataDir = "." // Development mode } } // Ensure data directory exists if err := os.MkdirAll(dataDir, 0755); err != nil { log.Fatalf("failed to create data directory: %v", err) } // Connect simple sqlite DB (file) dbPath := filepath.Join(dataDir, "appliance.db") dsn := fmt.Sprintf("file:%s?_foreign_keys=on", dbPath) sqldb, err := sql.Open("sqlite", dsn) if err != nil { log.Fatalf("open db: %v", err) } defer sqldb.Close() // Run migrations and seed admin if err := db.MigrateAndSeed(ctx, sqldb); err != nil { log.Fatalf("migrate: %v", err) } r := chi.NewRouter() uuidMiddleware := func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { rid := uuid.New().String() rw := w r = r.WithContext(context.WithValue(r.Context(), httpin.ContextKeyRequestID, rid)) rw.Header().Set("X-Request-Id", rid) next.ServeHTTP(rw, r) }) } // Attach router and app dependencies // wire mocks for now; replace with real adapters in infra diskSvc := &mock.MockDiskService{} // job runner uses sqlite DB and zfs adapter zfsAdapter := zfs.NewAdapter(osexec.Default) jobRunner := &job.Runner{DB: sqldb} auditLogger := audit.NewSQLAuditLogger(sqldb) jobRunner.ZFS = zfsAdapter jobRunner.Audit = auditLogger // storage service wiring: use zfsAdapter and jobRunner and audit logger storageSvc := storage.NewStorageService(zfsAdapter, jobRunner, auditLogger) nfsAdapter := nfs.NewAdapter(osexec.Default, "") sambaAdapter := samba.NewAdapter(osexec.Default, "") sharesSvc := shares.NewSharesService(sqldb, nfsAdapter, sambaAdapter, auditLogger) // iSCSI adapter and service iscsiAdapter := iscsiinfra.NewAdapter(osexec.Default) iscsiSvc := iscsiSvcPkg.NewISCSIService(sqldb, zfsAdapter, iscsiAdapter, auditLogger) zfsSvc := zfsAdapter app := &httpin.App{ DB: sqldb, DiskSvc: diskSvc, ZFSSvc: zfsSvc, JobRunner: jobRunner, HTTPClient: &http.Client{}, StorageSvc: storageSvc, ShareSvc: sharesSvc, ISCSISvc: iscsiSvc, Runner: osexec.Default, } r.Use(uuidMiddleware) httpin.RegisterRoutes(r, app) srv := &http.Server{ Addr: ":8080", Handler: r, } // graceful shutdown go func() { log.Printf("Starting server on %s", srv.Addr) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("server error: %v", err) } }() stop := make(chan os.Signal, 1) signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) <-stop ctxShut, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() if err := srv.Shutdown(ctxShut); err != nil { log.Fatalf("server shutdown failed: %v", err) } log.Println("server stopped") }