1package app
2
3import (
4 "context"
5 "fmt"
6 "io"
7
8 "github.com/bnema/zerowrap"
9
10 "github.com/bnema/gordon/internal/boundaries/in"
11 configusecase "github.com/bnema/gordon/internal/usecase/config"
12 secretsusecase "github.com/bnema/gordon/internal/usecase/secrets"
13)
14
15// Kernel provides in-process service access for local CLI execution.
16//
17// It intentionally does not start HTTP servers or register signal handlers.
18type Kernel struct {
19 authEnabled bool
20 configSvc in.ConfigService
21 secretSvc in.SecretService
22 containerSvc in.ContainerService
23 backupSvc in.BackupService
24 registrySvc in.RegistryService
25 healthSvc in.HealthService
26 logSvc in.LogService
27 volumeSvc in.VolumeService
28 cleanup func()
29}
30
31// NewKernel initializes local services without starting server listeners.
32func NewKernel(configPath string) (*Kernel, error) {
33 return newKernel(configPath, initLogger)
34}
35
36// NewKernelQuiet initializes local services without emitting console logs.
37func NewKernelQuiet(configPath string) (*Kernel, error) {
38 return newKernel(configPath, quietInitLogger)
39}
40
41type kernelLoggerInit func(cfg Config) (zerowrap.Logger, func(), error)
42
43func newKernel(configPath string, initLog kernelLoggerInit) (*Kernel, error) {
44 ctx := context.Background()
45 v, cfg, err := initConfig(configPath)
46 if err != nil {
47 return nil, err
48 }
49
50 log, cleanup, err := initLog(cfg)
51 if err != nil {
52 return nil, err
53 }
54 if cleanup == nil {
55 cleanup = func() {}
56 }
57
58 ctx = zerowrap.WithCtx(ctx, log)
59
60 // Prefer full service wiring so local CLI can execute the same operations
61 // as remote mode without going through HTTP admin endpoints.
62 if svc, fullErr := createServices(ctx, v, cfg, log); fullErr == nil {
63 return &Kernel{
64 authEnabled: cfg.Auth.Enabled,
65 configSvc: svc.configSvc,
66 secretSvc: svc.secretSvc,
67 containerSvc: svc.containerSvc,
68 backupSvc: svc.backupSvc,
69 registrySvc: svc.registrySvc,
70 healthSvc: svc.healthSvc,
71 logSvc: svc.logSvc,
72 volumeSvc: svc.volumeSvc,
73 cleanup: cleanup,
74 }, nil
75 } else {
76 log.Warn().Err(fullErr).Msg("local kernel running in minimal mode")
77 }
78
79 configSvc := configusecase.NewService(v, nil)
80 if err := configSvc.Load(ctx); err != nil {
81 cleanup()
82 return nil, fmt.Errorf("failed to load configuration: %w", err)
83 }
84
85 _, _, _, domainSecretStore, err := createDomainSecretStore(cfg, log)
86 if err != nil {
87 cleanup()
88 return nil, fmt.Errorf("failed to create local secret store: %w", err)
89 }
90
91 secretSvc := secretsusecase.NewService(domainSecretStore, log, nil)
92
93 return &Kernel{
94 authEnabled: cfg.Auth.Enabled,
95 configSvc: configSvc,
96 secretSvc: secretSvc,
97 cleanup: cleanup,
98 }, nil
99}
100
101func quietInitLogger(Config) (zerowrap.Logger, func(), error) {
102 return zerowrap.New(zerowrap.Config{Level: "disabled", Output: io.Discard}), func() {}, nil
103}
104
105func (k *Kernel) Close() error {
106 if k == nil || k.cleanup == nil {
107 return nil
108 }
109 k.cleanup()
110 return nil
111}
112
113func (k *Kernel) Config() in.ConfigService { return k.configSvc }
114
115func (k *Kernel) Secrets() in.SecretService { return k.secretSvc }
116
117func (k *Kernel) Container() in.ContainerService { return k.containerSvc }
118
119func (k *Kernel) Backup() in.BackupService { return k.backupSvc }
120
121func (k *Kernel) Registry() in.RegistryService { return k.registrySvc }
122
123func (k *Kernel) Health() in.HealthService { return k.healthSvc }
124
125func (k *Kernel) Logs() in.LogService { return k.logSvc }
126
127func (k *Kernel) Volumes() in.VolumeService { return k.volumeSvc }
128
129func (k *Kernel) AuthEnabled() bool { return k != nil && k.authEnabled }