From b8c67491fe581c0c64b07d7ade951628d64df2a9 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Tue, 10 Mar 2026 23:28:36 -0600 Subject: [PATCH] feat: resolve alias_of in cache pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Packages with alias_of in releases.conf (e.g. dashd → dashcore, golang → go) now get symlinked cache files so they resolve to the same JSON as their target. 13 aliases total. Added AliasOf as a proper field in installerconf.Conf, LinkAlias method to fsstore, and alias handling in webicached's Run loop. --- cmd/webicached/main.go | 11 ++++++++++- internal/installerconf/installerconf.go | 8 +++++++- internal/storage/fsstore/fsstore.go | 21 +++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/cmd/webicached/main.go b/cmd/webicached/main.go index a13897d..35d7a21 100644 --- a/cmd/webicached/main.go +++ b/cmd/webicached/main.go @@ -132,8 +132,10 @@ func (wc *WebiCache) Run(filterPkgs []string) { log.Printf("refreshing %d packages", len(packages)) runStart := time.Now() + var aliases []pkgConf for _, pkg := range packages { - if alias := pkg.conf.Extra["alias_of"]; alias != "" { + if pkg.conf.AliasOf != "" { + aliases = append(aliases, pkg) continue } @@ -142,6 +144,13 @@ func (wc *WebiCache) Run(filterPkgs []string) { } } + // Create symlinks for aliases after all targets are written. + for _, pkg := range aliases { + if err := wc.Store.LinkAlias(pkg.name, pkg.conf.AliasOf); err != nil { + log.Printf(" ERROR alias %s → %s: %v", pkg.name, pkg.conf.AliasOf, err) + } + } + log.Printf("refreshed %d packages in %s", len(packages), time.Since(runStart)) } diff --git a/internal/installerconf/installerconf.go b/internal/installerconf/installerconf.go index 226713d..cf362c4 100644 --- a/internal/installerconf/installerconf.go +++ b/internal/installerconf/installerconf.go @@ -98,6 +98,11 @@ type Conf struct { // variant detection logic lives in Go code per-package. Variants []string + // AliasOf names another package that this one mirrors. + // When set, the package has no releases of its own — it shares + // the cache output of the named target (e.g. dashd → dashcore). + AliasOf string + // Extra holds any unrecognized keys for forward compatibility. Extra map[string]string } @@ -153,6 +158,7 @@ func Read(path string) (*Conf, error) { } c.AssetFilter = raw["asset_filter"] + c.AliasOf = raw["alias_of"] if v := raw["variants"]; v != "" { c.Variants = strings.Fields(v) @@ -164,7 +170,7 @@ func Read(path string) (*Conf, error) { "base_url": true, "url": true, "tag_prefix": true, "version_prefix": true, "version_prefixes": true, "exclude": true, "asset_exclude": true, "asset_filter": true, - "variants": true, + "variants": true, "alias_of": true, } for k, v := range raw { if !known[k] { diff --git a/internal/storage/fsstore/fsstore.go b/internal/storage/fsstore/fsstore.go index f0b08e9..0d0aa35 100644 --- a/internal/storage/fsstore/fsstore.go +++ b/internal/storage/fsstore/fsstore.go @@ -143,6 +143,27 @@ func atomicWrite(path string, data []byte) error { return nil } +// LinkAlias creates symlinks so that alias resolves to the same cache +// files as target: alias.json → target.json, alias.updated.txt → target.updated.txt. +func (s *Store) LinkAlias(alias, target string) error { + dir := filepath.Join(s.root, monthDir(time.Now())) + if err := os.MkdirAll(dir, 0o755); err != nil { + return fmt.Errorf("fsstore: mkdir: %w", err) + } + + for _, ext := range []string{".json", ".updated.txt"} { + link := filepath.Join(dir, alias+ext) + dest := target + ext // relative symlink within same dir + + // Remove existing link/file so we can recreate it. + os.Remove(link) + if err := os.Symlink(dest, link); err != nil { + return fmt.Errorf("fsstore: symlink %s → %s: %w", alias+ext, dest, err) + } + } + return nil +} + // parseTimestamp parses the "seconds.millis" format from .updated.txt files. func parseTimestamp(s string) time.Time { f, err := strconv.ParseFloat(s, 64)