201 lines
4.2 KiB
Go
201 lines
4.2 KiB
Go
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(`
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head><title>Chrome Offline Installer</title></head>
|
|
<body>
|
|
<h1>Chrome Offline Versions</h1>
|
|
<ul>
|
|
{{range .Versions}}
|
|
<li><a href="/download/{{.Filename}}{{if $.Token}}?token={{$.Token}}{{end}}">{{.Filename}}</a> ({{.Size}} bytes, {{.Time.Format "2006-01-02 15:04"}})</li>
|
|
{{end}}
|
|
</ul>
|
|
</body>
|
|
</html>
|
|
`))
|
|
tmpl.Execute(w, struct {
|
|
Versions []Version
|
|
Token string
|
|
}{versions, token})
|
|
}
|