detached 774a055

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 @@
55 CHAOTIC_KEY_ID=3056513887B78AEB
66 CHAOTIC_KEYSERVER=keyserver.ubuntu.com
77
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
1113
1214 # Repository Configuration
1315 CHAOTIC_REPO_NAME=chaotic-aur
@@ -5,9 +5,11 @@
5 CHAOTIC_KEY_ID=3056513887B78AEB5 CHAOTIC_KEY_ID=3056513887B78AEB
6 CHAOTIC_KEYSERVER=keyserver.ubuntu.com6 CHAOTIC_KEYSERVER=keyserver.ubuntu.com
7 7
8-# Package URLs8+# Package URLs with mirror fallbacks
9-CHAOTIC_KEYRING_URL=https://cdn-mirror.chaotic.cx/chaotic-aur/chaotic-keyring.pkg.tar.zst9+# 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.zst10+# 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 Configuration14 # Repository Configuration
13 CHAOTIC_REPO_NAME=chaotic-aur15 CHAOTIC_REPO_NAME=chaotic-aur
M internal/phases/repos.go +11 -1
@@ -305,7 +305,17 @@ func (p *ReposPhase) enableChaoticAUR(progressChan chan<- ProgressUpdate) error
305305
306306 // Download and install chaotic-keyring and chaotic-mirrorlist
307307 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...); {
309319 case err != nil:
310320 p.logger.Warn("Chaotic-AUR packages unavailable, skipping", "error", err)
311321 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-mirrorlist306 // 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 {
4444 }
4545
4646 // 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
4748 func DownloadAndInstallPackages(logPath, chrootPath string, urls ...string) error {
4849 if len(urls) == 0 {
4950 return fmt.Errorf("no URLs provided")
5051 }
5152
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
5369 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 += ") && "
5678 }
5779 cmd += "pacman -U --noconfirm /tmp/pkg*.tar.zst && rm -f /tmp/pkg*.tar.zst"
5880
@@ -63,3 +85,20 @@ func DownloadAndInstallPackages(logPath, chrootPath string, urls ...string) erro
6385
6486 return nil
6587 }
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 chroot46 // 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 command53+ // 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 nil86 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+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 0

    ↑↓ moveenter openesc close