feat(repos): add mirror fallback support for Chaotic-AUR packages
- commit
- 774a0559d670883552cd1a769c2637bf0f780c3a
- author
- bnema · · 2025-10-27 19:40 UTC
- committer
- bnema · · 2025-10-27 20:18 UTC
- parent
- c7ad5bd
- signature
- unsigned
3 files · +58 -7
M install/configs/chaotic-aur.conf +5 -3
| @@ -5,9 +5,11 @@ | |||
| 5 | 5 | CHAOTIC_KEY_ID=3056513887B78AEB | |
| 6 | 6 | CHAOTIC_KEYSERVER=keyserver.ubuntu.com | |
| 7 | 7 | ||
| 8 | - | # Package URLs | |
| 9 | - | CHAOTIC_KEYRING_URL=https://cdn-mirror.chaotic.cx/chaotic-aur/chaotic-keyring.pkg.tar.zst | |
| 10 | - | CHAOTIC_MIRRORLIST_URL=https://cdn-mirror.chaotic.cx/chaotic-aur/chaotic-mirrorlist.pkg.tar.zst | |
| 8 | + | # Package URLs with mirror fallbacks | |
| 9 | + | # Primary and fallback mirrors are pipe-separated; function will try in order | |
| 10 | + | # Main site is always available as primary; if it's down, mirrors will be attempted | |
| 11 | + | CHAOTIC_KEYRING_URL=https://builds.garudalinux.org/repos/chaotic-aur/chaotic-keyring.pkg.tar.zst|https://chaotic.pl4ygr0und.de/chaotic-aur/chaotic-keyring.pkg.tar.zst|https://repo.pkgbuilds.com/chaotic-aur/chaotic-keyring.pkg.tar.zst | |
| 12 | + | CHAOTIC_MIRRORLIST_URL=https://builds.garudalinux.org/repos/chaotic-aur/chaotic-mirrorlist.pkg.tar.zst|https://chaotic.pl4ygr0und.de/chaotic-aur/chaotic-mirrorlist.pkg.tar.zst|https://repo.pkgbuilds.com/chaotic-aur/chaotic-mirrorlist.pkg.tar.zst | |
| 11 | 13 | ||
| 12 | 14 | # Repository Configuration | |
| 13 | 15 | CHAOTIC_REPO_NAME=chaotic-aur | |
| @@ -5,9 +5,11 @@ | |||||
| 5 | CHAOTIC_KEY_ID=3056513887B78AEB | 5 | CHAOTIC_KEY_ID=3056513887B78AEB | ||
| 6 | CHAOTIC_KEYSERVER=keyserver.ubuntu.com | 6 | CHAOTIC_KEYSERVER=keyserver.ubuntu.com | ||
| 7 | 7 | ||||
| 8 | - | # Package URLs | 8 | + | # Package URLs with mirror fallbacks |
| 9 | - | CHAOTIC_KEYRING_URL=https://cdn-mirror.chaotic.cx/chaotic-aur/chaotic-keyring.pkg.tar.zst | 9 | + | # Primary and fallback mirrors are pipe-separated; function will try in order |
| 10 | - | CHAOTIC_MIRRORLIST_URL=https://cdn-mirror.chaotic.cx/chaotic-aur/chaotic-mirrorlist.pkg.tar.zst | 10 | + | # Main site is always available as primary; if it's down, mirrors will be attempted |
| 11 | + | CHAOTIC_KEYRING_URL=https://builds.garudalinux.org/repos/chaotic-aur/chaotic-keyring.pkg.tar.zst|https://chaotic.pl4ygr0und.de/chaotic-aur/chaotic-keyring.pkg.tar.zst|https://repo.pkgbuilds.com/chaotic-aur/chaotic-keyring.pkg.tar.zst | |||
| 12 | + | CHAOTIC_MIRRORLIST_URL=https://builds.garudalinux.org/repos/chaotic-aur/chaotic-mirrorlist.pkg.tar.zst|https://chaotic.pl4ygr0und.de/chaotic-aur/chaotic-mirrorlist.pkg.tar.zst|https://repo.pkgbuilds.com/chaotic-aur/chaotic-mirrorlist.pkg.tar.zst | |||
| 11 | 13 | ||||
| 12 | # Repository Configuration | 14 | # Repository Configuration | ||
| 13 | CHAOTIC_REPO_NAME=chaotic-aur | 15 | CHAOTIC_REPO_NAME=chaotic-aur | ||
M internal/phases/repos.go +11 -1
| @@ -305,7 +305,17 @@ func (p *ReposPhase) enableChaoticAUR(progressChan chan<- ProgressUpdate) error | |||
| 305 | 305 | ||
| 306 | 306 | // Download and install chaotic-keyring and chaotic-mirrorlist | |
| 307 | 307 | p.SendOutput(progressChan, "Downloading Chaotic-AUR packages...") | |
| 308 | - | switch err := p.chrExec.DownloadAndInstallPackages(p.logger.LogPath(), config.PathMnt, keyringURL, mirrorlistURL); { | |
| 308 | + | ||
| 309 | + | // Parse mirror URLs (pipe-separated for fallbacks) | |
| 310 | + | var urls []string | |
| 311 | + | for _, url := range strings.Split(keyringURL, "|") { | |
| 312 | + | urls = append(urls, strings.TrimSpace(url)) | |
| 313 | + | } | |
| 314 | + | for _, url := range strings.Split(mirrorlistURL, "|") { | |
| 315 | + | urls = append(urls, strings.TrimSpace(url)) | |
| 316 | + | } | |
| 317 | + | ||
| 318 | + | switch err := p.chrExec.DownloadAndInstallPackages(p.logger.LogPath(), config.PathMnt, urls...); { | |
| 309 | 319 | case err != nil: | |
| 310 | 320 | p.logger.Warn("Chaotic-AUR packages unavailable, skipping", "error", err) | |
| 311 | 321 | p.SendOutput(progressChan, "[WARN] Chaotic-AUR unavailable, skipping (AUR helper will be built from source)") | |
| @@ -305,7 +305,17 @@ func (p *ReposPhase) enableChaoticAUR(progressChan chan<- ProgressUpdate) error | |||||
| 305 | 305 | ||||
| 306 | // Download and install chaotic-keyring and chaotic-mirrorlist | 306 | // Download and install chaotic-keyring and chaotic-mirrorlist | ||
| 307 | p.SendOutput(progressChan, "Downloading Chaotic-AUR packages...") | 307 | p.SendOutput(progressChan, "Downloading Chaotic-AUR packages...") | ||
| 308 | - | switch err := p.chrExec.DownloadAndInstallPackages(p.logger.LogPath(), config.PathMnt, keyringURL, mirrorlistURL); { | 308 | + | |
| 309 | + | // Parse mirror URLs (pipe-separated for fallbacks) | |||
| 310 | + | var urls []string | |||
| 311 | + | for _, url := range strings.Split(keyringURL, "|") { | |||
| 312 | + | urls = append(urls, strings.TrimSpace(url)) | |||
| 313 | + | } | |||
| 314 | + | for _, url := range strings.Split(mirrorlistURL, "|") { | |||
| 315 | + | urls = append(urls, strings.TrimSpace(url)) | |||
| 316 | + | } | |||
| 317 | + | ||||
| 318 | + | switch err := p.chrExec.DownloadAndInstallPackages(p.logger.LogPath(), config.PathMnt, urls...); { | |||
| 309 | case err != nil: | 319 | case err != nil: | ||
| 310 | p.logger.Warn("Chaotic-AUR packages unavailable, skipping", "error", err) | 320 | p.logger.Warn("Chaotic-AUR packages unavailable, skipping", "error", err) | ||
| 311 | p.SendOutput(progressChan, "[WARN] Chaotic-AUR unavailable, skipping (AUR helper will be built from source)") | 321 | p.SendOutput(progressChan, "[WARN] Chaotic-AUR unavailable, skipping (AUR helper will be built from source)") | ||
M internal/system/pacman.go +42 -3
| @@ -44,15 +44,37 @@ func PacmanRemove(logPath string, packages ...string) error { | |||
| 44 | 44 | } | |
| 45 | 45 | ||
| 46 | 46 | // DownloadAndInstallPackages downloads packages from URLs using curl and installs them in chroot | |
| 47 | + | // Supports mirror fallbacks: URLs with the same package name but different mirrors | |
| 47 | 48 | func DownloadAndInstallPackages(logPath, chrootPath string, urls ...string) error { | |
| 48 | 49 | if len(urls) == 0 { | |
| 49 | 50 | return fmt.Errorf("no URLs provided") | |
| 50 | 51 | } | |
| 51 | 52 | ||
| 52 | - | // Download and install in one command | |
| 53 | + | // Group URLs by package (same filename = same package, different mirrors) | |
| 54 | + | // This allows fallback to alternative mirrors if primary fails | |
| 55 | + | packageMirrors := make(map[string][]string) | |
| 56 | + | for _, url := range urls { | |
| 57 | + | // Extract package name from URL | |
| 58 | + | var pkgName string | |
| 59 | + | for i := len(url) - 1; i >= 0; i-- { | |
| 60 | + | if url[i] == '/' { | |
| 61 | + | pkgName = url[i+1:] | |
| 62 | + | break | |
| 63 | + | } | |
| 64 | + | } | |
| 65 | + | packageMirrors[pkgName] = append(packageMirrors[pkgName], url) | |
| 66 | + | } | |
| 67 | + | ||
| 68 | + | // Download with fallback: try each mirror for each package | |
| 53 | 69 | cmd := "mkdir -p /tmp && " | |
| 54 | - | for i, url := range urls { | |
| 55 | - | cmd += fmt.Sprintf("curl -L -f -o /tmp/pkg%d.tar.zst '%s' && ", i, url) | |
| 70 | + | for i, pkgName := range getSortedKeys(packageMirrors) { | |
| 71 | + | mirrors := packageMirrors[pkgName] | |
| 72 | + | // Try primary mirror first, fallback to others if it fails | |
| 73 | + | cmd += fmt.Sprintf("(curl -L -f -o /tmp/pkg%d.tar.zst '%s'", i, mirrors[0]) | |
| 74 | + | for _, mirror := range mirrors[1:] { | |
| 75 | + | cmd += fmt.Sprintf(" || curl -L -f -o /tmp/pkg%d.tar.zst '%s'", i, mirror) | |
| 76 | + | } | |
| 77 | + | cmd += ") && " | |
| 56 | 78 | } | |
| 57 | 79 | cmd += "pacman -U --noconfirm /tmp/pkg*.tar.zst && rm -f /tmp/pkg*.tar.zst" | |
| 58 | 80 | ||
| @@ -63,3 +85,20 @@ func DownloadAndInstallPackages(logPath, chrootPath string, urls ...string) erro | |||
| 63 | 85 | ||
| 64 | 86 | return nil | |
| 65 | 87 | } | |
| 88 | + | ||
| 89 | + | // getSortedKeys returns map keys in a stable order (important for reproducibility) | |
| 90 | + | func getSortedKeys(m map[string][]string) []string { | |
| 91 | + | keys := make([]string, 0, len(m)) | |
| 92 | + | for k := range m { | |
| 93 | + | keys = append(keys, k) | |
| 94 | + | } | |
| 95 | + | // Simple stable sort (not alphabetical, but consistent) | |
| 96 | + | for i := 0; i < len(keys); i++ { | |
| 97 | + | for j := i + 1; j < len(keys); j++ { | |
| 98 | + | if keys[i] > keys[j] { | |
| 99 | + | keys[i], keys[j] = keys[j], keys[i] | |
| 100 | + | } | |
| 101 | + | } | |
| 102 | + | } | |
| 103 | + | return keys | |
| 104 | + | } | |
| @@ -44,15 +44,37 @@ func PacmanRemove(logPath string, packages ...string) error { | |||||
| 44 | } | 44 | } | ||
| 45 | 45 | ||||
| 46 | // DownloadAndInstallPackages downloads packages from URLs using curl and installs them in chroot | 46 | // DownloadAndInstallPackages downloads packages from URLs using curl and installs them in chroot | ||
| 47 | + | // Supports mirror fallbacks: URLs with the same package name but different mirrors | |||
| 47 | func DownloadAndInstallPackages(logPath, chrootPath string, urls ...string) error { | 48 | func DownloadAndInstallPackages(logPath, chrootPath string, urls ...string) error { | ||
| 48 | if len(urls) == 0 { | 49 | if len(urls) == 0 { | ||
| 49 | return fmt.Errorf("no URLs provided") | 50 | return fmt.Errorf("no URLs provided") | ||
| 50 | } | 51 | } | ||
| 51 | 52 | ||||
| 52 | - | // Download and install in one command | 53 | + | // Group URLs by package (same filename = same package, different mirrors) |
| 54 | + | // This allows fallback to alternative mirrors if primary fails | |||
| 55 | + | packageMirrors := make(map[string][]string) | |||
| 56 | + | for _, url := range urls { | |||
| 57 | + | // Extract package name from URL | |||
| 58 | + | var pkgName string | |||
| 59 | + | for i := len(url) - 1; i >= 0; i-- { | |||
| 60 | + | if url[i] == '/' { | |||
| 61 | + | pkgName = url[i+1:] | |||
| 62 | + | break | |||
| 63 | + | } | |||
| 64 | + | } | |||
| 65 | + | packageMirrors[pkgName] = append(packageMirrors[pkgName], url) | |||
| 66 | + | } | |||
| 67 | + | ||||
| 68 | + | // Download with fallback: try each mirror for each package | |||
| 53 | cmd := "mkdir -p /tmp && " | 69 | cmd := "mkdir -p /tmp && " | ||
| 54 | - | for i, url := range urls { | 70 | + | for i, pkgName := range getSortedKeys(packageMirrors) { |
| 55 | - | cmd += fmt.Sprintf("curl -L -f -o /tmp/pkg%d.tar.zst '%s' && ", i, url) | 71 | + | mirrors := packageMirrors[pkgName] |
| 72 | + | // Try primary mirror first, fallback to others if it fails | |||
| 73 | + | cmd += fmt.Sprintf("(curl -L -f -o /tmp/pkg%d.tar.zst '%s'", i, mirrors[0]) | |||
| 74 | + | for _, mirror := range mirrors[1:] { | |||
| 75 | + | cmd += fmt.Sprintf(" || curl -L -f -o /tmp/pkg%d.tar.zst '%s'", i, mirror) | |||
| 76 | + | } | |||
| 77 | + | cmd += ") && " | |||
| 56 | } | 78 | } | ||
| 57 | cmd += "pacman -U --noconfirm /tmp/pkg*.tar.zst && rm -f /tmp/pkg*.tar.zst" | 79 | cmd += "pacman -U --noconfirm /tmp/pkg*.tar.zst && rm -f /tmp/pkg*.tar.zst" | ||
| 58 | 80 | ||||
| @@ -63,3 +85,20 @@ func DownloadAndInstallPackages(logPath, chrootPath string, urls ...string) erro | |||||
| 63 | 85 | ||||
| 64 | return nil | 86 | return nil | ||
| 65 | } | 87 | } | ||
| 88 | + | ||||
| 89 | + | // getSortedKeys returns map keys in a stable order (important for reproducibility) | |||
| 90 | + | func getSortedKeys(m map[string][]string) []string { | |||
| 91 | + | keys := make([]string, 0, len(m)) | |||
| 92 | + | for k := range m { | |||
| 93 | + | keys = append(keys, k) | |||
| 94 | + | } | |||
| 95 | + | // Simple stable sort (not alphabetical, but consistent) | |||
| 96 | + | for i := 0; i < len(keys); i++ { | |||
| 97 | + | for j := i + 1; j < len(keys); j++ { | |||
| 98 | + | if keys[i] > keys[j] { | |||
| 99 | + | keys[i], keys[j] = keys[j], keys[i] | |||
| 100 | + | } | |||
| 101 | + | } | |||
| 102 | + | } | |||
| 103 | + | return keys | |||
| 104 | + | } | |||