diff --git a/docs/superpowers/plans/2026-04-28-geofile-scheduled-update.md b/docs/superpowers/plans/2026-04-28-geofile-scheduled-update.md new file mode 100644 index 00000000..0cc899f9 --- /dev/null +++ b/docs/superpowers/plans/2026-04-28-geofile-scheduled-update.md @@ -0,0 +1,520 @@ +# 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 +