package main import ( "encoding/json" "flag" "fmt" "github.com/Jguer/go-alpm/v2" paconf "github.com/Morganamilo/go-pacmanconf" log "github.com/sirupsen/logrus" "io" "net/http" "net/url" "os" "path" "regexp" "somegit.dev/ALHP/ALHP.GO/ent/dbpackage" "strconv" "strings" ) const ( APIBase = "https://api.alhp.dev/" ) var ( jsonFlag = flag.Bool("j", false, "output as JSON") debugFlag = flag.Bool("d", false, "enable debug output") exitCodePackageFlag = flag.Bool("e", false, "exit with non-zero if one of your packages is in queue") exitCodeMirrorFlag = flag.Bool("m", false, "exit with non-zero if your main mirror is out of sync") repoRegex = regexp.MustCompile(`^\w+-x86-64-v\d$`) ) type JSONOut struct { Total int `json:"total"` Packages []string `json:"packages"` MirrorOutOfDate bool `json:"mirror_out_of_date"` } func main() { flag.Parse() if *debugFlag { log.SetLevel(log.DebugLevel) } h, er := alpm.Initialize("/", "/var/lib/pacman") if er != nil { log.Errorf("error initializing alpm library: %v", er) os.Exit(1) } defer func(h *alpm.Handle) { err := h.Release() if err != nil { log.Errorf("error releasing alpm library: %v", err) } }(h) db, er := h.LocalDB() if er != nil { log.Errorf("error initializing alpm library: %v", er) os.Exit(1) } alhpQueue, err := ALHPBuildQueuePkgbase() if err != nil { log.Errorf("error getting build queue from ALHP api: %v", err) os.Exit(1) } log.Debugf("alhp build queue length: %d", len(alhpQueue)) var packagesInQueue []string for _, pkg := range db.PkgCache().Slice() { if Find(alhpQueue, pkg.Base()) != -1 { log.Debugf("found package in queue: %s", pkg.Name()) packagesInQueue = append(packagesInQueue, pkg.Name()) } } stats, err := ALHPStats() if err != nil { log.Errorf("error getting stats from ALHP api: %v", err) os.Exit(2) } pacmanConfig, _, err := paconf.ParseFile("/etc/pacman.conf") if err != nil { log.Errorf("error parsing pacman.conf: %v", err) os.Exit(2) } mirrorOutOfDate := false for _, repo := range pacmanConfig.Repos { if !repoRegex.MatchString(repo.Name) || len(repo.Servers) == 0 { continue } pURL, err := url.Parse(repo.Servers[0]) if err != nil { log.Warnf("error parsing mirror url: %v", err) } pURL.Path = path.Join(pURL.Path, "../../../lastupdate") log.Debugf("checking mirror %s (%s)", repo.Servers[0], pURL.String()) resp, err := http.Get(pURL.String()) if err != nil { log.Warnf("error getting mirror lastupdate: %v", err) continue } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { log.Warnf("mirror lastupdate returned %d", resp.StatusCode) continue } bResp, err := io.ReadAll(resp.Body) if err != nil { log.Warnf("error reading mirror lastupdate: %v", err) } timestamp, err := strconv.ParseInt(string(bResp), 10, 64) if err != nil { log.Warnf("error parsing mirror lastupdate: %v", err) } if timestamp < stats.LastMirrorTimestamp { log.Debugf("mirror %s is out of date", repo.Servers[0]) mirrorOutOfDate = true } } if len(packagesInQueue) == 0 { log.Debugf("found %d of your local packages in queue", len(packagesInQueue)) if *jsonFlag { err = json.NewEncoder(os.Stdout).Encode(JSONOut{ Total: len(packagesInQueue), Packages: packagesInQueue, MirrorOutOfDate: mirrorOutOfDate, }) if err != nil { log.Errorf("error encoding JSON: %v", err) os.Exit(1) } } else { fmt.Println(strings.Join(packagesInQueue, "\n")) } if *exitCodePackageFlag { os.Exit(20) } } else { log.Debugf("no packages in queue") if *jsonFlag { err = json.NewEncoder(os.Stdout).Encode(JSONOut{ Total: len(packagesInQueue), Packages: packagesInQueue, MirrorOutOfDate: mirrorOutOfDate, }) } if *exitCodeMirrorFlag && mirrorOutOfDate { os.Exit(20) } } } func Find[T comparable](arr []T, match T) int { for i, v := range arr { if v == match { return i } } return -1 } type ThinPackage struct { Pkgbase string `json:"pkgbase"` Repo string `json:"repo"` SplitPackages []string `json:"split_packages"` Status dbpackage.Status `json:"status"` SkipReason *string `json:"skip_reason,omitempty"` LTO dbpackage.Lto `json:"lto"` DebugSymbols dbpackage.DebugSymbols `json:"debug_symbols"` ArchVersion string `json:"arch_version"` RepoVersion string `json:"repo_version"` BuildDate *string `json:"build_date,omitempty"` PeakMem *string `json:"peak_mem,omitempty"` } type PackageResponse struct { Packages []*ThinPackage `json:"packages"` Total int `json:"total"` Offset int `json:"offset"` Limit int `json:"limit"` } type StatsResponse struct { Failed int `json:"failed"` Skipped int `json:"skipped"` Latest int `json:"latest"` Queued int `json:"queued"` Building int `json:"building"` LastMirrorTimestamp int64 `json:"last_mirror_timestamp"` Lto struct { Enabled int `json:"enabled"` Disabled int `json:"disabled"` Unknown int `json:"unknown"` } `json:"lto"` } func ALHPBuildQueuePkgbase() ([]string, error) { resp, err := http.Get(fmt.Sprintf("%spackages?status=built&status=building&status=queued&limit=0&offset=0", APIBase)) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { return nil, nil } else if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("ALHP api returned HTTP %d", resp.StatusCode) } bResp, err := io.ReadAll(resp.Body) if err != nil { return nil, err } jResp := new(PackageResponse) if err = json.Unmarshal(bResp, jResp); err != nil { return nil, err } respArray := make([]string, len(jResp.Packages)) for i := range jResp.Packages { respArray[i] = jResp.Packages[i].Pkgbase } return respArray, nil } func ALHPStats() (*StatsResponse, error) { resp, err := http.Get(fmt.Sprintf("%sstats", APIBase)) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != 200 { return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) } bResp, err := io.ReadAll(resp.Body) if err != nil { return nil, err } jResp := new(StatsResponse) if err = json.Unmarshal(bResp, jResp); err != nil { return nil, err } return jResp, nil }