feat: track and display geofile version from GitHub release tag to v1.8.1.1

This commit is contained in:
root 2026-04-28 00:32:01 +08:00
parent 6e04e6d247
commit b4c42bb89f
5 changed files with 157 additions and 13 deletions

View file

@ -1 +1 @@
v1.8.1.0
v1.8.1.1

View file

@ -0,0 +1,39 @@
# Task Record
Date: 2026-04-27
Related Module: web/service/server, web/controller/server, web/html/index
Change Type: Add
## Background
When downloading geoip.dat and geosite.dat from GitHub releases, the version information (GitHub release tag like `202604262232`) was not captured or displayed. The user wanted to track and show the version of geofiles in the UI.
## Changes
- `web/service/server.go`:
- Changed `downloadFile` closure to return the captured version string alongside the error
- Modified `http.Client` to use `CheckRedirect` callback that extracts the release tag from the 302 redirect URL path (format: `/releases/download/{version}/{filename}`)
- Added `GeofileVersion` struct and `GeofileVersions` map type for version metadata storage
- Added `loadGeofileVersions()` and `saveGeofileVersions()` for reading/writing `geofile_versions.json` in the bin folder
- Added `GetGeofileVersions()` public method for API access
- `web/controller/server.go`:
- Added `GET /getGeofileVersions` endpoint returning version metadata
- `web/html/index.html`:
- Added `geofileVersions` to Vue data
- Added `loadGeofileVersions()` method, called when the Xray version modal opens
- Geofiles panel now displays version string (e.g. `202604262232`) next to each file name
- Added CSS classes for version text in light/dark themes
## Impact
- New file: `bin/geofile_versions.json` stores version metadata per geofile
- New API: `GET /panel/api/server/getGeofileVersions`
- No database schema changes
- Xray binary filename expectations unchanged (files still saved as `geoip.dat`/`geosite.dat`)
## Verification
- `gofmt -l -w .` passed
- `go vet ./...` passed
- Tested redirect URL parsing logic: path `/releases/download/202604262232/geoip.dat` correctly extracts `202604262232`
- Confirmed `http.Client` with `CheckRedirect` does not interfere with `If-Modified-Since`/`Last-Modified` caching
## Risks And Follow-Up
- Version extraction depends on GitHub's redirect URL format; if GitHub changes the URL structure, version will be empty (graceful degradation — shows `-` in UI)
- Worker nodes: version metadata is written locally on each node after their own download via `syncGeoIfNeeded()`, so each worker has its own `geofile_versions.json`

View file

@ -52,6 +52,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
g.GET("/getNewmldsa65", a.getNewmldsa65)
g.GET("/getNewmlkem768", a.getNewmlkem768)
g.GET("/getNewVlessEnc", a.getNewVlessEnc)
g.GET("/getGeofileVersions", a.getGeofileVersions)
g.POST("/stopXrayService", a.stopXrayService)
g.POST("/restartXrayService", a.restartXrayService)
@ -177,6 +178,12 @@ func (a *ServerController) syncUpdateGeofile(c *gin.Context) {
jsonMsg(c, I18nWeb(c, "pages.index.geofileUpdatePopover"), nil)
}
// getGeofileVersions returns version metadata for all geofiles.
func (a *ServerController) getGeofileVersions(c *gin.Context) {
versions := a.serverService.GetGeofileVersions()
jsonObj(c, versions, nil)
}
// stopXrayService stops the Xray service.
func (a *ServerController) stopXrayService(c *gin.Context) {
err := a.serverService.StopXrayService()

View file

@ -332,6 +332,9 @@
<a-list-item class="ant-version-list-item"
v-for="file, index in ['geosite.dat', 'geoip.dat']">
<a-tag :color="index % 2 == 0 ? 'purple' : 'green'">[[ file ]]</a-tag>
<span class="geofile-version-text" v-if="geofileVersions[file]">
[[ geofileVersions[file].version || '-' ]]
</span>
<a-icon type="reload" @click="updateGeofile(file)" class="mr-8" />
</a-list-item>
</a-list>
@ -827,6 +830,16 @@
table td, table th {
padding: 2px 15px;
}
.geofile-version-text {
font-size: 11px;
color: #999;
margin-left: 8px;
}
.dark .geofile-version-text {
color: #666;
}
</style>
<table>
@ -902,6 +915,7 @@
logModal,
xraylogModal,
backupModal,
geofileVersions: {},
loadingTip: '{{ i18n "loading"}}',
showAlert: false,
showIp: false,
@ -974,6 +988,7 @@
return;
}
versionModal.show(msg.obj);
this.loadGeofileVersions();
},
switchV2rayVersion(version) {
this.$confirm({
@ -1026,6 +1041,16 @@
},
});
},
async loadGeofileVersions() {
try {
const msg = await HttpUtil.get('/panel/api/server/getGeofileVersions');
if (msg.success && msg.obj) {
this.geofileVersions = msg.obj;
}
} catch (e) {
console.error("Failed to load geofile versions:", e);
}
},
async stopXrayService() {
this.loading(true);
const msg = await HttpUtil.post('/panel/api/server/stopXrayService');

View file

@ -1080,11 +1080,11 @@ func (s *ServerService) UpdateGeofile(fileName string) error {
}
}
downloadFile := func(url, destPath string) error {
downloadFile := func(url, destPath string) (version string, err error) {
var req *http.Request
req, err := http.NewRequest("GET", url, nil)
req, err = http.NewRequest("GET", url, nil)
if err != nil {
return common.NewErrorf("Failed to create HTTP request for %s: %v", url, err)
return "", common.NewErrorf("Failed to create HTTP request for %s: %v", url, err)
}
var localFileModTime time.Time
@ -1095,10 +1095,26 @@ func (s *ServerService) UpdateGeofile(fileName string) error {
}
}
client := &http.Client{}
var capturedVersion string
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if len(via) > 5 {
return common.NewErrorf("too many redirects for %s", url)
}
path := req.URL.Path
if idx := strings.Index(path, "/releases/download/"); idx != -1 {
rest := path[idx+len("/releases/download/"):]
slash := strings.Index(rest, "/")
if slash > 0 {
capturedVersion = rest[:slash]
}
}
return nil
},
}
resp, err := client.Do(req)
if err != nil {
return common.NewErrorf("Failed to download Geofile from %s: %v", url, err)
return "", common.NewErrorf("Failed to download Geofile from %s: %v", url, err)
}
defer resp.Body.Close()
@ -1126,46 +1142,57 @@ func (s *ServerService) UpdateGeofile(fileName string) error {
// Handle 304 Not Modified
if resp.StatusCode == http.StatusNotModified {
updateFileModTime()
return nil
return capturedVersion, nil
}
if resp.StatusCode != http.StatusOK {
return common.NewErrorf("Failed to download Geofile from %s: received status code %d", url, resp.StatusCode)
return "", common.NewErrorf("Failed to download Geofile from %s: received status code %d", url, resp.StatusCode)
}
file, err := os.Create(destPath)
if err != nil {
return common.NewErrorf("Failed to create Geofile %s: %v", destPath, err)
return "", common.NewErrorf("Failed to create Geofile %s: %v", destPath, err)
}
defer file.Close()
_, err = io.Copy(file, resp.Body)
if err != nil {
return common.NewErrorf("Failed to save Geofile %s: %v", destPath, err)
return "", common.NewErrorf("Failed to save Geofile %s: %v", destPath, err)
}
updateFileModTime()
return nil
return capturedVersion, nil
}
var errorMessages []string
versions := loadGeofileVersions(config.GetBinFolderPath())
if fileName == "" {
// Download all geofiles
for _, entry := range geofileAllowlist {
destPath := filepath.Join(config.GetBinFolderPath(), entry.FileName)
if err := downloadFile(entry.URL, destPath); err != nil {
ver, err := downloadFile(entry.URL, destPath)
if err != nil {
errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", entry.FileName, err))
} else {
versions.Update(entry.FileName, ver)
}
}
} else {
entry := geofileAllowlist[fileName]
destPath := filepath.Join(config.GetBinFolderPath(), entry.FileName)
if err := downloadFile(entry.URL, destPath); err != nil {
ver, err := downloadFile(entry.URL, destPath)
if err != nil {
errorMessages = append(errorMessages, fmt.Sprintf("Error downloading Geofile '%s': %v", entry.FileName, err))
} else {
versions.Update(entry.FileName, ver)
}
}
if err := saveGeofileVersions(config.GetBinFolderPath(), versions); err != nil {
logger.Warningf("Failed to save geofile versions: %v", err)
}
err := s.RestartXrayService()
if err != nil {
errorMessages = append(errorMessages, fmt.Sprintf("Updated Geofile '%s' but Failed to start Xray: %v", fileName, err))
@ -1178,6 +1205,52 @@ func (s *ServerService) UpdateGeofile(fileName string) error {
return nil
}
// GeofileVersion holds version metadata for a single geofile.
type GeofileVersion struct {
Version string `json:"version"`
UpdatedAt string `json:"updatedAt"`
}
// GeofileVersions is a map of filename -> version metadata.
type GeofileVersions map[string]GeofileVersion
// Update sets or updates the version entry for a geofile.
func (v GeofileVersions) Update(fileName, version string) {
entry := v[fileName]
if version != "" {
entry.Version = version
}
entry.UpdatedAt = time.Now().UTC().Format(time.RFC3339)
v[fileName] = entry
}
func geofileVersionsPath(binFolder string) string {
return filepath.Join(binFolder, "geofile_versions.json")
}
func loadGeofileVersions(binFolder string) GeofileVersions {
versions := make(GeofileVersions)
data, err := os.ReadFile(geofileVersionsPath(binFolder))
if err != nil {
return versions
}
_ = json.Unmarshal(data, &versions)
return versions
}
func saveGeofileVersions(binFolder string, versions GeofileVersions) error {
data, err := json.MarshalIndent(versions, "", " ")
if err != nil {
return err
}
return os.WriteFile(geofileVersionsPath(binFolder), data, 0644)
}
// GetGeofileVersions returns the current geofile version metadata.
func (s *ServerService) GetGeofileVersions() GeofileVersions {
return loadGeofileVersions(config.GetBinFolderPath())
}
func (s *ServerService) GetNewX25519Cert() (any, error) {
// Run the command
cmd := exec.Command(xray.GetBinaryPath(), "x25519")