- Changed blob table to use ID (UUID) as primary key instead of hash - Blob records are now created at packing start, enabling immediate chunk associations - Implemented streaming chunking to process large files without memory exhaustion - Fixed blob manifest generation to include all referenced blobs - Updated all foreign key references from blob_hash to blob_id - Added progress reporting and improved error handling - Enforced encryption requirement for all blob packing - Updated tests to use test encryption keys - Added Cyrillic transliteration to README
109 lines
2.8 KiB
Go
109 lines
2.8 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"git.eeqj.de/sneak/vaultik/internal/config"
|
|
"git.eeqj.de/sneak/vaultik/internal/database"
|
|
"git.eeqj.de/sneak/vaultik/internal/globals"
|
|
"git.eeqj.de/sneak/vaultik/internal/log"
|
|
"go.uber.org/fx"
|
|
)
|
|
|
|
// AppOptions contains common options for creating the fx application
|
|
type AppOptions struct {
|
|
ConfigPath string
|
|
LogOptions log.LogOptions
|
|
Modules []fx.Option
|
|
Invokes []fx.Option
|
|
}
|
|
|
|
// setupGlobals sets up the globals with application startup time
|
|
func setupGlobals(lc fx.Lifecycle, g *globals.Globals) {
|
|
lc.Append(fx.Hook{
|
|
OnStart: func(ctx context.Context) error {
|
|
g.StartTime = time.Now()
|
|
return nil
|
|
},
|
|
})
|
|
}
|
|
|
|
// NewApp creates a new fx application with common modules
|
|
func NewApp(opts AppOptions) *fx.App {
|
|
baseModules := []fx.Option{
|
|
fx.Supply(config.ConfigPath(opts.ConfigPath)),
|
|
fx.Supply(opts.LogOptions),
|
|
fx.Provide(globals.New),
|
|
fx.Provide(log.New),
|
|
config.Module,
|
|
database.Module,
|
|
log.Module,
|
|
fx.Invoke(setupGlobals),
|
|
fx.NopLogger,
|
|
}
|
|
|
|
allOptions := append(baseModules, opts.Modules...)
|
|
allOptions = append(allOptions, opts.Invokes...)
|
|
|
|
return fx.New(allOptions...)
|
|
}
|
|
|
|
// RunApp starts and stops the fx application within the given context
|
|
func RunApp(ctx context.Context, app *fx.App) error {
|
|
// Set up signal handling for graceful shutdown
|
|
sigChan := make(chan os.Signal, 1)
|
|
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
|
|
|
|
// Create a context that will be cancelled on signal
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
|
|
// Start the app
|
|
if err := app.Start(ctx); err != nil {
|
|
return fmt.Errorf("failed to start app: %w", err)
|
|
}
|
|
|
|
// Handle shutdown
|
|
shutdownComplete := make(chan struct{})
|
|
go func() {
|
|
defer close(shutdownComplete)
|
|
<-sigChan
|
|
log.Notice("Received interrupt signal, shutting down gracefully...")
|
|
|
|
// Create a timeout context for shutdown
|
|
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer shutdownCancel()
|
|
|
|
if err := app.Stop(shutdownCtx); err != nil {
|
|
log.Error("Error during shutdown", "error", err)
|
|
}
|
|
}()
|
|
|
|
// Wait for either the signal handler to complete shutdown or the app to request shutdown
|
|
select {
|
|
case <-shutdownComplete:
|
|
// Shutdown completed via signal
|
|
return nil
|
|
case <-ctx.Done():
|
|
// Context cancelled (shouldn't happen in normal operation)
|
|
if err := app.Stop(context.Background()); err != nil {
|
|
log.Error("Error stopping app", "error", err)
|
|
}
|
|
return ctx.Err()
|
|
case <-app.Done():
|
|
// App finished running (e.g., backup completed)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// RunWithApp is a helper that creates and runs an fx app with the given options
|
|
func RunWithApp(ctx context.Context, opts AppOptions) error {
|
|
app := NewApp(opts)
|
|
return RunApp(ctx, app)
|
|
}
|