hub v0.29.0: Day-0 artifact manifest — version dropdowns + auto-derived sha
Operator picks a version from a Gitea-populated dropdown; the hub reads that version's sha256 from Gitea itself (files-metadata API, no artifact download) and vouches it — no hand-copied checksums. New internal/gitea read-only client (ListVersions + FileSHA256, unit-tested). Configuration UI: version <select>s + read-only sha display; handleSetArtifacts derives the sha authoritatively and refuses the save on a Gitea lookup failure. Degrades to manual text entry without registry creds. go build/vet/test clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,10 +17,27 @@ import (
|
||||
"time"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/assets"
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/gitea"
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// Generic-package names + the artifact filename inside each version (used to resolve version lists +
|
||||
// sha256 from Gitea for the Day-0 artifact manifest UI).
|
||||
const (
|
||||
pkgAgent = "felhom-agent"
|
||||
fileAgent = "felhom-agent"
|
||||
pkgGolden = "felhom-golden"
|
||||
fileGolden = "golden.tar.zst"
|
||||
)
|
||||
|
||||
// artifactChoice is one selectable artifact version + its Gitea-resolved sha256 (for the dropdown +
|
||||
// the read-only sha display).
|
||||
type artifactChoice struct {
|
||||
Version string
|
||||
SHA256 string
|
||||
}
|
||||
|
||||
// hubSession holds per-session auth and CSRF data.
|
||||
type hubSession struct {
|
||||
expiresAt time.Time
|
||||
@@ -39,6 +56,7 @@ type Server struct {
|
||||
versionChecker *VersionChecker
|
||||
templateFetcher *TemplateFetcher
|
||||
assetsMgr *assets.Manager
|
||||
gitea *gitea.Client // optional; enables the Day-0 artifact version dropdowns
|
||||
|
||||
sessions map[string]*hubSession
|
||||
sessionsMu sync.RWMutex
|
||||
@@ -125,6 +143,42 @@ func (s *Server) SetAssetManager(am *assets.Manager) {
|
||||
s.assetsMgr = am
|
||||
}
|
||||
|
||||
// SetGiteaClient enables the Day-0 artifact version dropdowns (optional). Without it the artifact form
|
||||
// degrades to manual text entry.
|
||||
func (s *Server) SetGiteaClient(c *gitea.Client) {
|
||||
s.gitea = c
|
||||
}
|
||||
|
||||
// artifactChoices resolves the currently-available versions of a generic package + each one's sha256
|
||||
// from Gitea, newest first, for the Day-0 artifact dropdown. Returns nil (→ manual text-entry
|
||||
// fallback) when no Gitea client is configured or Gitea is unreachable. A failed sha lookup for a
|
||||
// single version drops just that version, not the whole list.
|
||||
func (s *Server) artifactChoices(ctx context.Context, pkg, file string) []artifactChoice {
|
||||
if s.gitea == nil {
|
||||
return nil
|
||||
}
|
||||
vers, err := s.gitea.ListVersions(ctx, pkg)
|
||||
if err != nil {
|
||||
s.logger.Printf("[WARN] artifact versions (%s): %v", pkg, err)
|
||||
return nil
|
||||
}
|
||||
const maxChoices = 20
|
||||
if len(vers) > maxChoices {
|
||||
s.logger.Printf("[INFO] artifact versions (%s): showing newest %d of %d", pkg, maxChoices, len(vers))
|
||||
vers = vers[:maxChoices]
|
||||
}
|
||||
out := make([]artifactChoice, 0, len(vers))
|
||||
for _, v := range vers {
|
||||
sha, err := s.gitea.FileSHA256(ctx, pkg, v, file)
|
||||
if err != nil {
|
||||
s.logger.Printf("[WARN] artifact sha (%s/%s): %v", pkg, v, err)
|
||||
continue
|
||||
}
|
||||
out = append(out, artifactChoice{Version: v, SHA256: sha})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ServeHTTP routes web requests.
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
@@ -586,6 +640,7 @@ func (s *Server) handleConfiguration(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
data := map[string]interface{}{
|
||||
"CSRFToken": csrfToken,
|
||||
"CSRFField": s.csrfField(r),
|
||||
@@ -593,6 +648,8 @@ func (s *Server) handleConfiguration(w http.ResponseWriter, r *http.Request) {
|
||||
"AssetLastSync": assetLastSync,
|
||||
"GlobalFloor": s.store.GetGlobalMinControllerVersion(),
|
||||
"Artifacts": s.store.GetArtifactManifest(),
|
||||
"AgentChoices": s.artifactChoices(ctx, pkgAgent, fileAgent),
|
||||
"GoldenChoices": s.artifactChoices(ctx, pkgGolden, fileGolden),
|
||||
"Flash": r.URL.Query().Get("flash"),
|
||||
}
|
||||
if err := s.templates.ExecuteTemplate(w, "configuration.html", data); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user