Files
storage-appliance/cmd/appliance/main.go

135 lines
4.0 KiB
Go

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")
}