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://dl.google.com/tag/s/appguid%3D%7B8A69D345-D564-463C-AFF1-A69D9E530F96%7D%26iid%3D%7B00000000-0000-0000-0000-000000000000%7D%26lang%3Dzh-CN%26browser%3D4%26usagestats%3D0%26appname%3DGoogle%2520Chrome%26needsadmin%3Dprefers%26ap%3Dx64-stable-statsdef_1%26installdataindex%3Dempty/chrome/install/ChromeStandaloneSetup64.exe") } 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) // 启动时立即检查一次 log.Println("Performing initial check...") checkAndDownload() 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 (%.2f MB)", filename, float64(len(data))/1024/1024) 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)) log.Printf("Removed old version: %s", 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(`