Files
mesh-catalog/modules/pacman/cmd/pacman-tools/mirrors.go
T
jochen d9336d11d0 pacman: the package manager's configuration, mirrors and cache as a module
Mirrors were generated once and never again and caches never cleaned. The
module holds node-package-manager (hq ADR 0207), declares pacman itself, owns
/etc/pacman.conf whole — [options] cannot take an appended block — with the
union of the enabled repositories and improved options, proven by pacman-conf
in its test, and enables reflector.timer (its config owned) and
paccache.timer. Fifteen tools from a Go bundle; transactions run as transient
units so a call's timeout never kills pacman mid-transaction (to-be 42).
2026-10-04 12:50:20 +02:00

87 lines
3.3 KiB
Go

package main
// The mirror list and its refresher (novox/hq to-be 42 Phase 1). On 2026-10-04 every machine's list
// had been generated once — by a tool no longer installed, or by a hosting provider's installer — and
// never again. The module installs reflector, owns its configuration and enables its weekly timer;
// this reads the list and the refresher's last run, and starts a refresh on demand.
import (
"strings"
)
// Where the list is and how reflector is told to write it.
const (
Mirrorlist = "/etc/pacman.d/mirrorlist"
ReflectorConf = "/etc/xdg/reflector/reflector.conf"
)
// Mirrors is the mirror list and its refresher.
type Mirrors struct {
Servers []string `json:"servers"`
Commented int `json:"commented_servers"`
GeneratedBy string `json:"generated_by,omitempty"`
When string `json:"generated_when,omitempty"`
Reflector []string `json:"reflector_options"`
Timer map[string]string `json:"reflector_timer,omitempty"`
LastRun map[string]string `json:"reflector_last_run,omitempty"`
Refreshing bool `json:"refresh_started"`
Note string `json:"note,omitempty"`
}
// ParseMirrorlist reads the servers in force, those commented out, and the generator's header.
func ParseMirrorlist(text string) Mirrors {
m := Mirrors{Servers: []string{}, Reflector: []string{}}
for _, l := range lines(text) {
l = strings.TrimSpace(l)
switch {
case strings.HasPrefix(l, "Server"):
if _, v, ok := strings.Cut(l, "="); ok {
m.Servers = append(m.Servers, strings.TrimSpace(v))
}
case strings.HasPrefix(strings.TrimLeft(l, "# "), "Server"):
m.Commented++
case strings.Contains(l, "generated by Reflector"):
m.GeneratedBy = "reflector"
case strings.HasPrefix(l, "# When:"):
m.When = strings.TrimSpace(strings.TrimPrefix(l, "# When:"))
case strings.HasPrefix(l, "## Generated on"):
m.GeneratedBy, m.When = "the distribution's mirrorlist", strings.TrimSpace(strings.TrimPrefix(l, "## Generated on"))
}
}
return m
}
// MirrorList answers the list, reflector's options, its timer and its last run; refresh starts
// reflector now, without waiting, since ranking mirrors by rate takes longer than a call may.
func (m *Machine) MirrorList(refresh bool) (Mirrors, error) {
text, err := m.ReadFile(Mirrorlist)
if err != nil {
return Mirrors{}, err
}
out := ParseMirrorlist(string(text))
if conf, err := m.ReadFile(ReflectorConf); err == nil {
for _, l := range lines(string(conf)) {
if l = strings.TrimSpace(l); !strings.HasPrefix(l, "#") {
out.Reflector = append(out.Reflector, l)
}
}
}
if t, err := m.unitProps("reflector.timer", "LoadState", "ActiveState", "UnitFileState", "LastTriggerUSec", "NextElapseUSecRealtime"); err == nil {
out.Timer = t
}
if s, err := m.unitProps("reflector.service", "LoadState", "ActiveState", "Result", "ExecMainExitTimestamp", "ExecMainStatus"); err == nil {
out.LastRun = s
}
if out.Timer["LoadState"] == "not-found" {
out.Note = "reflector is not installed here; the module installs it"
}
if refresh {
if _, err := m.Root("systemctl", "start", "--no-block", "reflector.service"); err != nil {
return out, err
}
out.Refreshing = true
out.Note = "reflector is ranking mirrors now; call again in a minute for the new list"
}
return out, nil
}