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).
87 lines
3.3 KiB
Go
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
|
|
}
|