commit bd3457a0bbd05d0fd60d98a0f35442c26dc4d5fe Author: XOF Date: Wed Dec 17 00:39:24 2025 +0800 添加 main.go diff --git a/main.go b/main.go new file mode 100644 index 0000000..5f94ce1 --- /dev/null +++ b/main.go @@ -0,0 +1,200 @@ +package main + +import ( + "crypto/md5" + "crypto/subtle" + "fmt" + "html/template" + "io" + "log" + "math/rand" + "net/http" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + "time" +) + +const ( + downloadDir = "./chrome_versions" + checkInterval = 24 * time.Hour + maxRetries = 3 +) + +var ( + authToken string + authEnabled bool + port string + keepVersions int + chromeURL string +) + +type Version struct { + Filename string + Size int64 + Time time.Time +} + +func init() { + authToken = getEnv("AUTH_TOKEN", "") + authEnabled = getEnv("AUTH_ENABLED", "false") == "true" || authToken != "" + port = getEnv("PORT", "8080") + keepVersions, _ = strconv.Atoi(getEnv("KEEP_VERSIONS", "3")) + chromeURL = getEnv("CHROME_URL", "https://www.google.cn/chrome?standalone=1&platform=win64") +} + +func getEnv(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultValue +} + +func main() { + os.MkdirAll(downloadDir, 0755) + log.Printf("Auth enabled: %v, Port: %s, Keep versions: %d", authEnabled, port, keepVersions) + + go monitor() + + http.HandleFunc("/", authMiddleware(serveIndex)) + http.Handle("/download/", authMiddleware(http.StripPrefix("/download/", http.FileServer(http.Dir(downloadDir))).ServeHTTP)) + + log.Printf("Server started on :%s", port) + log.Fatal(http.ListenAndServe(":"+port, nil)) +} + +func monitor() { + for { + delay := time.Duration(rand.Intn(3600)) * time.Second + time.Sleep(delay) + + checkAndDownload() + time.Sleep(checkInterval) + } +} + +func checkAndDownload() { + var data []byte + var err error + + for i := 0; i < maxRetries; i++ { + resp, err := http.Get(chromeURL) + if err != nil { + log.Printf("Attempt %d failed: %v", i+1, err) + time.Sleep(time.Duration(i+1) * 10 * time.Second) + continue + } + + data, err = io.ReadAll(resp.Body) + resp.Body.Close() + + if err == nil { + break + } + log.Printf("Attempt %d read failed: %v", i+1, err) + } + + if err != nil { + log.Println("All retries failed") + return + } + + hash := fmt.Sprintf("%x", md5.Sum(data)) + filename := fmt.Sprintf("chrome_%s_%s.exe", time.Now().Format("20060102"), hash[:8]) + filepath := filepath.Join(downloadDir, filename) + + if _, err := os.Stat(filepath); err == nil { + log.Println("File already exists, skipping") + return + } + + if err := os.WriteFile(filepath, data, 0644); err != nil { + log.Printf("Error saving file: %v", err) + return + } + + log.Printf("Downloaded: %s", filename) + cleanupOldVersions() +} + +func cleanupOldVersions() { + files, _ := os.ReadDir(downloadDir) + if len(files) <= keepVersions { + return + } + + var versions []Version + for _, f := range files { + if strings.HasSuffix(f.Name(), ".exe") { + info, _ := f.Info() + versions = append(versions, Version{ + Filename: f.Name(), + Time: info.ModTime(), + Size: info.Size(), + }) + } + } + + sort.Slice(versions, func(i, j int) bool { + return versions[i].Time.After(versions[j].Time) + }) + + for i := keepVersions; i < len(versions); i++ { + os.Remove(filepath.Join(downloadDir, versions[i].Filename)) + } +} + +func authMiddleware(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if authEnabled { + token := r.URL.Query().Get("token") + if subtle.ConstantTimeCompare([]byte(token), []byte(authToken)) != 1 { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + } + next(w, r) + } +} + +func serveIndex(w http.ResponseWriter, r *http.Request) { + files, _ := os.ReadDir(downloadDir) + var versions []Version + + for _, f := range files { + if strings.HasSuffix(f.Name(), ".exe") { + info, _ := f.Info() + versions = append(versions, Version{ + Filename: f.Name(), + Time: info.ModTime(), + Size: info.Size(), + }) + } + } + + sort.Slice(versions, func(i, j int) bool { + return versions[i].Time.After(versions[j].Time) + }) + + token := r.URL.Query().Get("token") + tmpl := template.Must(template.New("index").Parse(` + + +Chrome Offline Installer + +

Chrome Offline Versions

+ + + +`)) + tmpl.Execute(w, struct { + Versions []Version + Token string + }{versions, token}) +}