# Geofiles Scheduled Update Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Add configurable scheduled geofile updates, settable from both the web panel and x-ui.sh. **Architecture:** Follow the existing BackupJob pattern — a cron job registered as `@every 1m` that checks a `shouldRun()` guard based on settings stored in `x-ui.json`. Only the master node executes actual updates; workers sync via shared DB version bump. **Tech Stack:** Go (robfig/cron/v3), Vue.js (Ant Design Vue), Bash (x-ui.sh), JSON config --- ### Task 1: Add entity fields and settings infrastructure **Files:** - Modify: `web/entity/entity.go` (add fields after BackupMaxCount) - Modify: `web/service/setting.go` (add defaults, group mapping, getters, GetDefaultSettings) - [ ] **Step 1: Add fields to AllSetting struct** In `web/entity/entity.go`, after `BackupMaxCount` (line 129), add: ```go // Geofile update schedule settings GeofileUpdateEnabled bool `json:"geofileUpdateEnabled" form:"geofileUpdateEnabled"` GeofileUpdateFrequency string `json:"geofileUpdateFrequency" form:"geofileUpdateFrequency"` GeofileUpdateHour int `json:"geofileUpdateHour" form:"geofileUpdateHour"` ``` - [ ] **Step 2: Add defaultValueMap entries** In `web/service/setting.go`, after line 135 (`"backupMaxCount": "10",`), add: ```go // Geofile update schedule settings "geofileUpdateEnabled": "false", "geofileUpdateFrequency": "daily", "geofileUpdateHour": "4", ``` - [ ] **Step 3: Add settingGroups mapping** In `web/service/setting.go`, after the `"backup"` group (lines 256-261), add: ```go "geofileUpdate": { "enabled": "geofileUpdateEnabled", "frequency": "geofileUpdateFrequency", "hour": "geofileUpdateHour", }, ``` - [ ] **Step 4: Add getter methods** In `web/service/setting.go`, after `GetBackupMaxCount()` (line 1167), add: ```go func (s *SettingService) GetGeofileUpdateEnabled() (bool, error) { return s.getBool("geofileUpdateEnabled") } func (s *SettingService) GetGeofileUpdateFrequency() (string, error) { return s.getString("geofileUpdateFrequency") } func (s *SettingService) GetGeofileUpdateHour() (int, error) { return s.getInt("geofileUpdateHour") } ``` - [ ] **Step 5: Add to GetDefaultSettings** In `web/service/setting.go`, inside the `settings` map in `GetDefaultSettings()` (after line 1251), add: ```go "geofileUpdateEnabled": func() (any, error) { return s.GetGeofileUpdateEnabled() }, "geofileUpdateFrequency": func() (any, error) { return s.GetGeofileUpdateFrequency() }, "geofileUpdateHour": func() (any, error) { return s.GetGeofileUpdateHour() }, ``` - [ ] **Step 6: Verify compilation** ```bash cd /usr/x-ui/3x-ui && go build ./... ``` Expected: no errors - [ ] **Step 7: Commit** ```bash git add web/entity/entity.go web/service/setting.go git commit -m "feat: add geofile update schedule settings infrastructure" ``` --- ### Task 2: Create GeofileUpdateJob **Files:** - Create: `web/job/geofile_update_job.go` - Modify: `web/web.go` (register job) - [ ] **Step 1: Create the job file** Create `web/job/geofile_update_job.go`: ```go package job import ( "time" "github.com/mhsanaei/3x-ui/v2/database" "github.com/mhsanaei/3x-ui/v2/logger" "github.com/mhsanaei/3x-ui/v2/web/service" ) // GeofileUpdateJob handles scheduled geofile updates. type GeofileUpdateJob struct { settingService service.SettingService serverService service.ServerService } // NewGeofileUpdateJob creates a new GeofileUpdateJob instance. func NewGeofileUpdateJob() *GeofileUpdateJob { return &GeofileUpdateJob{} } // Run executes the scheduled geofile update if enabled and time matches the configured frequency. func (j *GeofileUpdateJob) Run() { enabled, err := j.settingService.GetGeofileUpdateEnabled() if err != nil || !enabled { return } frequency, err := j.settingService.GetGeofileUpdateFrequency() if err != nil { return } if !j.shouldRun(frequency) { return } if err := j.serverService.UpdateGeofile(""); err != nil { logger.Warning("scheduled geofile update failed:", err) return } if err := database.BumpSharedGeoVersion(database.GetDB()); err != nil { logger.Warning("bump shared geo version failed:", err) } } // shouldRun checks if the geofile update should run based on the configured frequency. func (j *GeofileUpdateJob) shouldRun(frequency string) bool { now := time.Now() switch frequency { case "hourly": return now.Minute() == 0 case "every12h": return (now.Hour() == 0 || now.Hour() == 12) && now.Minute() == 0 case "daily": hour, err := j.settingService.GetGeofileUpdateHour() if err != nil { hour = 4 } return now.Hour() == hour && now.Minute() == 0 case "weekly": if now.Weekday() != time.Sunday { return false } hour, err := j.settingService.GetGeofileUpdateHour() if err != nil { hour = 4 } return now.Hour() == hour && now.Minute() == 0 default: return false } } ``` - [ ] **Step 2: Register job in web.go** In `web/web.go`, after line 415 (`s.cron.AddJob("@every 1m", job.NewBackupJob())`), add: ```go s.cron.AddJob("@every 1m", job.NewGeofileUpdateJob()) ``` - [ ] **Step 3: Verify compilation** ```bash cd /usr/x-ui/3x-ui && go build ./... ``` Expected: no errors - [ ] **Step 4: Commit** ```bash git add web/job/geofile_update_job.go web/web.go git commit -m "feat: add GeofileUpdateJob for scheduled geofile updates" ``` --- ### Task 3: Add UI controls to the panel **Files:** - Modify: `web/html/index.html` - Modify: `web/translation/translate.en_US.toml` - Modify: `web/translation/translate.zh_CN.toml` - [ ] **Step 1: Add i18n keys — English** In `web/translation/translate.en_US.toml`, after line 168 (`"geofileUpdatePopover"`), add: ```toml "geofileScheduleTitle" = "Scheduled Update" "geofileScheduleEnable" = "Enable" "geofileScheduleFrequency" = "Frequency" "geofileScheduleHour" = "Hour" "geofileScheduleHourly" = "Hourly" "geofileScheduleEvery12h" = "Every 12 Hours" "geofileScheduleDaily" = "Daily" "geofileScheduleWeekly" = "Weekly" ``` - [ ] **Step 2: Add i18n keys — Chinese** In `web/translation/translate.zh_CN.toml`, after line 168, add: ```toml "geofileScheduleTitle" = "定时更新" "geofileScheduleEnable" = "启用" "geofileScheduleFrequency" = "更新频率" "geofileScheduleHour" = "更新小时" "geofileScheduleHourly" = "每小时" "geofileScheduleEvery12h" = "每12小时" "geofileScheduleDaily" = "每天" "geofileScheduleWeekly" = "每周" ``` - [ ] **Step 3: Add Vue data fields** In `web/html/index.html`, in the `data:` section, after line 922 (`ipLimitEnable: false,`), add: ```javascript geofileUpdateEnabled: false, geofileUpdateFrequency: 'daily', geofileUpdateHour: 4, ``` - [ ] **Step 4: Load geofile update settings on mount** In `web/html/index.html`, in the `mounted()` function, after the line that reads `this.ipLimitEnable = msg.obj.ipLimitEnable;` (line 1184), add: ```javascript this.geofileUpdateEnabled = msg.obj.geofileUpdateEnabled; this.geofileUpdateFrequency = msg.obj.geofileUpdateFrequency; this.geofileUpdateHour = msg.obj.geofileUpdateHour; ``` - [ ] **Step 5: Add save method** In `web/html/index.html`, in the `methods:` section, add after the `syncUpdateGeofile` method (after line 1042): ```javascript async saveGeofileSchedule() { try { await HttpUtil.post('/panel/setting/update', { geofileUpdateEnabled: this.geofileUpdateEnabled, geofileUpdateFrequency: this.geofileUpdateFrequency, geofileUpdateHour: this.geofileUpdateHour, }); } catch (e) { console.error("Failed to save geofile schedule:", e); } }, ``` - [ ] **Step 6: Add UI controls in the Geofiles panel** In `web/html/index.html`, after the existing `syncUpdateGeofile` button row (after line 344, the closing `` of the buttons), add: ```html