270 lines
7.5 KiB
Go
270 lines
7.5 KiB
Go
package mirror
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// MirrorFetcher handles fetching mirror lists from official sources
|
|
type MirrorFetcher struct {
|
|
client *http.Client
|
|
}
|
|
|
|
// NewMirrorFetcher creates a new mirror fetcher
|
|
func NewMirrorFetcher() *MirrorFetcher {
|
|
return &MirrorFetcher{
|
|
client: &http.Client{
|
|
Timeout: 30 * time.Second,
|
|
},
|
|
}
|
|
}
|
|
|
|
// FetchMirrors gets the complete list of mirrors for a distribution family
|
|
func (mf *MirrorFetcher) FetchMirrors(ctx context.Context, family string) ([]string, error) {
|
|
switch family {
|
|
case "arch":
|
|
return mf.fetchArchMirrors(ctx)
|
|
case "debian":
|
|
return mf.fetchDebianMirrors(ctx)
|
|
case "fedora":
|
|
return mf.fetchFedoraMirrors(ctx)
|
|
default:
|
|
// Fallback to hardcoded mirrors for unsupported families
|
|
return mf.getDefaultMirrors(family), nil
|
|
}
|
|
}
|
|
|
|
// fetchArchMirrors fetches Arch Linux mirrors from the official mirror status
|
|
func (mf *MirrorFetcher) fetchArchMirrors(ctx context.Context) ([]string, error) {
|
|
// Arch Linux provides a JSON API for mirror status
|
|
url := "https://archlinux.org/mirrorlist/?protocol=https&use_mirror_status=on"
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
resp, err := mf.client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to fetch Arch mirrors: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("HTTP %d from Arch mirror API", resp.StatusCode)
|
|
}
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
}
|
|
|
|
return mf.parseArchMirrorList(string(body))
|
|
}
|
|
|
|
// parseArchMirrorList parses the Arch Linux mirror list format
|
|
func (mf *MirrorFetcher) parseArchMirrorList(content string) ([]string, error) {
|
|
var mirrors []string
|
|
scanner := bufio.NewScanner(strings.NewReader(content))
|
|
|
|
// Look for lines like: #Server = https://mirror.example.com/archlinux/$repo/os/$arch
|
|
serverRegex := regexp.MustCompile(`^#?Server\s*=\s*(.+)`)
|
|
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if matches := serverRegex.FindStringSubmatch(line); matches != nil {
|
|
mirrorURL := strings.TrimSpace(matches[1])
|
|
// Remove the $repo/os/$arch part for base URL
|
|
if strings.Contains(mirrorURL, "$repo") {
|
|
mirrorURL = strings.Replace(mirrorURL, "/$repo/os/$arch", "/", 1)
|
|
}
|
|
if mirrorURL != "" && !strings.Contains(mirrorURL, "$") {
|
|
mirrors = append(mirrors, mirrorURL)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(mirrors) == 0 {
|
|
return nil, fmt.Errorf("no mirrors found in Arch Linux mirror list")
|
|
}
|
|
|
|
return mirrors, nil
|
|
}
|
|
|
|
// fetchDebianMirrors fetches Debian mirrors from the official mirror list
|
|
func (mf *MirrorFetcher) fetchDebianMirrors(ctx context.Context) ([]string, error) {
|
|
// Debian maintains a list of mirrors
|
|
url := "https://www.debian.org/mirror/list-full"
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
resp, err := mf.client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to fetch Debian mirrors: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("HTTP %d from Debian mirror list", resp.StatusCode)
|
|
}
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
}
|
|
|
|
return mf.parseDebianMirrorList(string(body))
|
|
}
|
|
|
|
// parseDebianMirrorList parses the Debian mirror list HTML
|
|
func (mf *MirrorFetcher) parseDebianMirrorList(content string) ([]string, error) {
|
|
var mirrors []string
|
|
|
|
// Look for HTTP/HTTPS URLs in the HTML
|
|
urlRegex := regexp.MustCompile(`https?://[a-zA-Z0-9.-]+[a-zA-Z0-9.-/]*debian[a-zA-Z0-9.-/]*/?`)
|
|
matches := urlRegex.FindAllString(content, -1)
|
|
|
|
seen := make(map[string]bool)
|
|
for _, match := range matches {
|
|
// Clean up and normalize the URL
|
|
mirror := strings.TrimSpace(match)
|
|
mirror = strings.TrimSuffix(mirror, "/") + "/"
|
|
|
|
// Avoid duplicates and ensure it's a proper mirror
|
|
if !seen[mirror] && strings.Contains(mirror, "debian") {
|
|
mirrors = append(mirrors, mirror)
|
|
seen[mirror] = true
|
|
}
|
|
}
|
|
|
|
// Add some known good mirrors if we don't find enough
|
|
knownMirrors := []string{
|
|
"http://deb.debian.org/debian/",
|
|
"http://ftp.us.debian.org/debian/",
|
|
"http://ftp.uk.debian.org/debian/",
|
|
"http://ftp.de.debian.org/debian/",
|
|
"http://mirrors.kernel.org/debian/",
|
|
}
|
|
|
|
for _, known := range knownMirrors {
|
|
if !seen[known] {
|
|
mirrors = append(mirrors, known)
|
|
seen[known] = true
|
|
}
|
|
}
|
|
|
|
if len(mirrors) == 0 {
|
|
return nil, fmt.Errorf("no mirrors found in Debian mirror list")
|
|
}
|
|
|
|
return mirrors, nil
|
|
}
|
|
|
|
// fetchFedoraMirrors fetches Fedora mirrors from the official mirror list
|
|
func (mf *MirrorFetcher) fetchFedoraMirrors(ctx context.Context) ([]string, error) {
|
|
// Fedora mirror list URL
|
|
url := "https://mirrors.fedoraproject.org/mirrorlist?repo=fedora-39&arch=x86_64"
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
resp, err := mf.client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to fetch Fedora mirrors: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("HTTP %d from Fedora mirror list", resp.StatusCode)
|
|
}
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read response: %w", err)
|
|
}
|
|
|
|
return mf.parseFedoraMirrorList(string(body))
|
|
}
|
|
|
|
// parseFedoraMirrorList parses the Fedora mirror list format
|
|
func (mf *MirrorFetcher) parseFedoraMirrorList(content string) ([]string, error) {
|
|
var mirrors []string
|
|
scanner := bufio.NewScanner(strings.NewReader(content))
|
|
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if strings.HasPrefix(line, "http") {
|
|
// Extract base URL (remove specific paths)
|
|
if idx := strings.Index(line, "/linux/"); idx != -1 {
|
|
baseURL := line[:idx] + "/fedora/linux/"
|
|
mirrors = append(mirrors, baseURL)
|
|
} else {
|
|
mirrors = append(mirrors, line)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(mirrors) == 0 {
|
|
return nil, fmt.Errorf("no mirrors found in Fedora mirror list")
|
|
}
|
|
|
|
return mirrors, nil
|
|
}
|
|
|
|
// getDefaultMirrors returns hardcoded mirrors as fallback
|
|
func (mf *MirrorFetcher) getDefaultMirrors(family string) []string {
|
|
defaultMirrors := map[string][]string{
|
|
"debian": {
|
|
"http://deb.debian.org/debian/",
|
|
"http://ftp.us.debian.org/debian/",
|
|
"http://ftp.uk.debian.org/debian/",
|
|
"http://ftp.de.debian.org/debian/",
|
|
"http://mirrors.kernel.org/debian/",
|
|
},
|
|
"arch": {
|
|
"https://mirror.rackspace.com/archlinux/",
|
|
"https://mirrors.kernel.org/archlinux/",
|
|
"https://mirror.math.princeton.edu/pub/archlinux/",
|
|
"https://mirrors.mit.edu/archlinux/",
|
|
"https://mirrors.liquidweb.com/archlinux/",
|
|
},
|
|
"fedora": {
|
|
"https://download.fedoraproject.org/pub/fedora/linux/",
|
|
"https://mirrors.kernel.org/fedora/",
|
|
"https://mirror.math.princeton.edu/pub/fedora/linux/",
|
|
"https://mirrors.mit.edu/fedora/linux/",
|
|
},
|
|
"opensuse": {
|
|
"http://download.opensuse.org/distribution/",
|
|
"http://ftp.gwdg.de/pub/linux/opensuse/distribution/",
|
|
"http://ftp.halifax.rwth-aachen.de/opensuse/distribution/",
|
|
},
|
|
"gentoo": {
|
|
"https://gentoo.osuosl.org/",
|
|
"http://mirrors.kernel.org/gentoo/",
|
|
"https://mirror.bytemark.co.uk/gentoo/",
|
|
},
|
|
"alpine": {
|
|
"http://dl-cdn.alpinelinux.org/alpine/",
|
|
"http://mirrors.dotsrc.org/alpine/",
|
|
"http://mirror.fit.cvut.cz/alpine/",
|
|
},
|
|
}
|
|
|
|
if mirrors, exists := defaultMirrors[family]; exists {
|
|
return mirrors
|
|
}
|
|
return []string{}
|
|
}
|