Skip to content

Commit 4ede599

Browse files
authored
Merge branch 'main' into feat/shoutrrr
2 parents 471d046 + 3044d91 commit 4ede599

5 files changed

Lines changed: 80 additions & 14 deletions

File tree

backend/internal/bootstrap/jobs_bootstrap.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,13 @@ func registerJobs(appCtx context.Context, scheduler *job.Scheduler, appServices
4545
slog.ErrorContext(appCtx, "Failed to register event cleanup job", "error", err)
4646
}
4747

48-
if err := job.RegisterFilesystemWatcherJob(appCtx, scheduler, appServices.Project, appServices.Template, appServices.Settings); err != nil {
48+
fsWatcherJob, err := job.RegisterFilesystemWatcherJob(appCtx, scheduler, appServices.Project, appServices.Template, appServices.Settings)
49+
if err != nil {
4950
slog.ErrorContext(appCtx, "Failed to register filesystem watcher job", "error", err)
5051
}
5152

5253
setupJobScheduleCallbacks(appServices, appConfig, environmentHealthJob, analyticsJob, eventCleanupJob)
53-
setupSettingsCallbacks(appServices, appConfig, imagePollingJob, autoUpdateJob, environmentHealthJob)
54+
setupSettingsCallbacks(appServices, appConfig, imagePollingJob, autoUpdateJob, environmentHealthJob, fsWatcherJob)
5455
}
5556

5657
func setupJobScheduleCallbacks(appServices *Services, appConfig *config.Config, environmentHealthJob *job.EnvironmentHealthJob, analyticsJob *job.AnalyticsJob, eventCleanupJob *job.EventCleanupJob) {
@@ -71,7 +72,7 @@ func setupJobScheduleCallbacks(appServices *Services, appConfig *config.Config,
7172
}
7273
}
7374

74-
func setupSettingsCallbacks(appServices *Services, appConfig *config.Config, imagePollingJob *job.ImagePollingJob, autoUpdateJob *job.AutoUpdateJob, environmentHealthJob *job.EnvironmentHealthJob) {
75+
func setupSettingsCallbacks(appServices *Services, appConfig *config.Config, imagePollingJob *job.ImagePollingJob, autoUpdateJob *job.AutoUpdateJob, environmentHealthJob *job.EnvironmentHealthJob, fsWatcherJob *job.FilesystemWatcherJob) {
7576
appServices.Settings.OnImagePollingSettingsChanged = func(ctx context.Context) {
7677
if err := imagePollingJob.Reschedule(ctx); err != nil {
7778
slog.WarnContext(ctx, "Failed to reschedule image-polling job", "error", err)
@@ -90,4 +91,11 @@ func setupSettingsCallbacks(appServices *Services, appConfig *config.Config, ima
9091
slog.WarnContext(ctx, "Failed to reschedule auto-update job", "error", err)
9192
}
9293
}
94+
appServices.Settings.OnProjectsDirectoryChanged = func(ctx context.Context) {
95+
if fsWatcherJob != nil {
96+
if err := fsWatcherJob.RestartProjectsWatcher(ctx); err != nil {
97+
slog.WarnContext(ctx, "Failed to restart projects filesystem watcher", "error", err)
98+
}
99+
}
100+
}
93101
}

backend/internal/job/filesystem_watcher_job.go

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func NewFilesystemWatcherJob(
3030
}
3131
}
3232

33-
func RegisterFilesystemWatcherJob(ctx context.Context, scheduler *Scheduler, projectService *services.ProjectService, templateService *services.TemplateService, settingsService *services.SettingsService) error {
33+
func RegisterFilesystemWatcherJob(ctx context.Context, scheduler *Scheduler, projectService *services.ProjectService, templateService *services.TemplateService, settingsService *services.SettingsService) (*FilesystemWatcherJob, error) {
3434
job := NewFilesystemWatcherJob(projectService, templateService, settingsService)
3535

3636
go func() {
@@ -40,7 +40,7 @@ func RegisterFilesystemWatcherJob(ctx context.Context, scheduler *Scheduler, pro
4040
}()
4141

4242
slog.InfoContext(ctx, "Filesystem watcher job registered")
43-
return nil
43+
return job, nil
4444
}
4545

4646
func (j *FilesystemWatcherJob) Start(ctx context.Context) error {
@@ -153,3 +153,50 @@ func (j *FilesystemWatcherJob) handleTemplatesChange(ctx context.Context) {
153153
slog.InfoContext(ctx, "Template sync completed after filesystem change")
154154
}
155155
}
156+
157+
func (j *FilesystemWatcherJob) RestartProjectsWatcher(ctx context.Context) error {
158+
slog.InfoContext(ctx, "Restarting projects filesystem watcher")
159+
160+
// Stop the existing projects watcher if it exists
161+
if j.projectsWatcher != nil {
162+
if err := j.projectsWatcher.Stop(); err != nil {
163+
slog.WarnContext(ctx, "Failed to stop projects watcher during restart", "error", err)
164+
}
165+
}
166+
167+
// Get fresh settings to get the new projects directory
168+
settings, err := j.settingsService.GetSettings(ctx)
169+
if err != nil {
170+
return err
171+
}
172+
projectsDirectory, err := fs.GetProjectsDirectory(ctx, settings.ProjectsDirectory.Value)
173+
if err != nil {
174+
return err
175+
}
176+
177+
// Create a new watcher with the updated path
178+
sw, err := utils.NewFilesystemWatcher(projectsDirectory, utils.WatcherOptions{
179+
Debounce: 3 * time.Second,
180+
OnChange: j.handleFilesystemChange,
181+
MaxDepth: 1,
182+
})
183+
if err != nil {
184+
return err
185+
}
186+
187+
j.projectsWatcher = sw
188+
189+
// Start the new watcher
190+
if err := j.projectsWatcher.Start(ctx); err != nil {
191+
return err
192+
}
193+
194+
slog.InfoContext(ctx, "Projects filesystem watcher restarted", "path", projectsDirectory)
195+
196+
// Perform a sync to ensure we have the latest state from the new directory
197+
if err := j.projectService.SyncProjectsFromFileSystem(ctx); err != nil {
198+
slog.ErrorContext(ctx, "Initial project sync after watcher restart failed", "error", err)
199+
}
200+
201+
return nil
202+
}

backend/internal/models/settings.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func (s SettingVariable) AsDurationSeconds() time.Duration {
4444

4545
type Settings struct {
4646
// General category
47-
ProjectsDirectory SettingVariable `key:"projectsDirectory" meta:"label=Projects Directory;type=text;keywords=projects,directory,path,folder,location,storage,files,compose,docker-compose;category=internal;description=Configure where project files are stored"`
47+
ProjectsDirectory SettingVariable `key:"projectsDirectory,envOverride" meta:"label=Projects Directory;type=text;keywords=projects,directory,path,folder,location,storage,files,compose,docker-compose;category=internal;description=Configure where project files are stored"`
4848
DiskUsagePath SettingVariable `key:"diskUsagePath" meta:"label=Disk Usage Path;type=text;keywords=disk,usage,path,storage,folder,files;category=general;description=Path used for disk usage calculations"`
4949
BaseServerURL SettingVariable `key:"baseServerUrl" meta:"label=Base Server URL;type=text;keywords=base,url,server,domain,host,endpoint,address,link;category=general;description=Set the base URL for the application"`
5050
EnableGravatar SettingVariable `key:"enableGravatar" meta:"label=Enable Gravatar;type=boolean;keywords=gravatar,avatar,profile,picture,image,user,photo;category=general;description=Enable Gravatar profile pictures for users"`

backend/internal/services/settings_service.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"log/slog"
1111
"os"
1212
"reflect"
13+
"slices"
1314
"strconv"
1415
"strings"
1516
"sync/atomic"
@@ -31,6 +32,7 @@ type SettingsService struct {
3132

3233
OnImagePollingSettingsChanged func(ctx context.Context)
3334
OnAutoUpdateSettingsChanged func(ctx context.Context)
35+
OnProjectsDirectoryChanged func(ctx context.Context)
3436
}
3537

3638
func NewSettingsService(ctx context.Context, db *database.DB) (*SettingsService, error) {
@@ -431,6 +433,9 @@ func (s *SettingsService) UpdateSettings(ctx context.Context, updates settings.U
431433
if changedAutoUpdate && s.OnAutoUpdateSettingsChanged != nil {
432434
s.OnAutoUpdateSettingsChanged(ctx)
433435
}
436+
if slices.ContainsFunc(valuesToUpdate, func(sv models.SettingVariable) bool { return sv.Key == "projectsDirectory" }) && s.OnProjectsDirectoryChanged != nil {
437+
s.OnProjectsDirectoryChanged(ctx)
438+
}
434439

435440
return settings.ToSettingVariableSlice(false, false), nil
436441
}
@@ -586,13 +591,21 @@ func (s *SettingsService) EnsureDefaultSettings(ctx context.Context) error {
586591

587592
func (s *SettingsService) PersistEnvSettingsIfMissing(ctx context.Context) error {
588593
rt := reflect.TypeOf(models.Settings{})
594+
appCfg := config.Load()
595+
isEnvOnlyMode := appCfg.AgentMode || appCfg.UIConfigurationDisabled
589596

590597
if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
591598
for i := 0; i < rt.NumField(); i++ {
592599
field := rt.Field(i)
593-
key, attrs, _ := strings.Cut(field.Tag.Get("key"), ",")
600+
tag := field.Tag.Get("key")
601+
key, attrs, _ := strings.Cut(tag, ",")
602+
603+
if key == "" || strings.Contains(attrs, "internal") {
604+
continue
605+
}
594606

595-
if key == "" || attrs == "internal" {
607+
// If not in env-only mode, only persist if it's explicitly marked as envOverride
608+
if !isEnvOnlyMode && !strings.Contains(attrs, "envOverride") {
596609
continue
597610
}
598611

backend/internal/utils/bootstrap_util.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,10 @@ func InitializeDefaultSettings(ctx context.Context, cfg *config.Config, settings
4242
slog.InfoContext(ctx, "Default settings initialized successfully")
4343
}
4444

45-
if cfg.AgentMode || cfg.UIConfigurationDisabled {
46-
if err := settingsMgr.PersistEnvSettingsIfMissing(ctx); err != nil {
47-
slog.WarnContext(ctx, "Failed to persist env-driven settings", "error", err.Error())
48-
} else {
49-
slog.DebugContext(ctx, "Persisted env-driven settings if missing")
50-
}
45+
if err := settingsMgr.PersistEnvSettingsIfMissing(ctx); err != nil {
46+
slog.WarnContext(ctx, "Failed to persist env-driven settings", "error", err.Error())
47+
} else {
48+
slog.DebugContext(ctx, "Persisted env-driven settings")
5149
}
5250
}
5351

0 commit comments

Comments
 (0)