Files
Felis/internal/updater/topology.go
T
flyemoji c4300cb005 feat(updater): authenticate GitHub polling and track the real release repo
felis-api's coord was the placeholder "felis/felis", which resolves against
nothing on real GitHub. It is now MliroLirrorsIngenuity/Felis — the same slug
deploy/bootstrap.sh clones from — so update reporting for the control plane
itself is live rather than parked.

That repo is private today, so the github source gained an optional token, read
from FELIS_GITHUB_TOKEN: the variable bootstrap already needs, so an operator
sets one value once. It comes from the environment and is never compiled in. A
constant would be committed to the very repository it protects, ship inside
every felis binary where strings(1) recovers it, reach every node the image is
imported onto, and need a rebuild and a redeploy to rotate.

Empty stays the correct posture for the other tracked components — k3s and
cloudflared are public — and an empty token sends no Authorization header at
all rather than an empty one.

GitHub answers 404, not 401 or 403, for a private repo the caller cannot see,
so "no token" and "no stable release published yet" arrive as the same status.
On an unauthenticated 404 the error now names both causes and the variable that
fixes the actionable one. With a token already set that hint would be wrong, so
it is suppressed.

Tests pin both halves: the Bearer header is sent only when the token is set,
and the diagnostic names the variable only when it is not.

doc.go's CAVEATS bullet still described the coord as a placeholder and the
component as "dark at runtime". Both were true only until this change; it now
records the real condition, which is that the component resolves like the others
but needs a credential while the repo is private.
2026-07-20 18:53:12 +09:00

61 lines
3.3 KiB
Go

package updater
import "felis.lolicon.best/internal/updates"
// sourceKind is how a component's latest upstream version is discovered.
type sourceKind int
const (
sourceNone sourceKind = iota // pinned components are never queried
sourceGitHub // GitHub Releases (Coord = "owner/repo")
sourcePaperMC // PaperMC Fill v3 (Coord = project id)
)
// Spec is one platform component's static update policy plus how to find its latest
// upstream version. Current is deliberately NOT here — it is gathered at runtime
// (integration: an image tag, `k3s --version`, a jar manifest) and combined with the
// Spec to form an updates.Component. The topology is the pure, testable expression of
// the user's stated decisions: what Felis keeps current, and how aggressively.
type Spec struct {
Name string
Policy updates.Policy
Manageable bool
Source sourceKind
// Coord is the source-specific coordinate: "owner/repo" for GitHub, the project
// id for PaperMC, empty for pinned components.
Coord string
}
// Topology returns the fixed platform components Felis tracks, each with the update
// policy the user set. The two user red lines shape every entry: "不要强制自动更新"
// (nothing is force-upgraded — the strongest policy is Scheduled, gated on a
// SysAdmin window) and "能不动的就别动" (Minecraft is always pinned).
//
// - felis-api — the control plane Felis ships. Felis MANAGES it (image bump +
// rollout), so Scheduled: applied only inside a SysAdmin-set window, else notify.
// - k3s — the single node the whole platform runs on. Upgrading it is
// high-blast-radius, so Notify only and NOT manageable: Felis reads its latest to
// tell the SysAdmin but never applies it; a human drives the node upgrade.
// - cloudflared — the edge tunnel binary + service Felis manages, so Scheduled.
// - velocity — the proxy, but off-cluster on an admin-operated macvlan host, so
// NOT manageable: even under a schedule it can only ever be Notify. Its releases
// come from PaperMC (Fill v3), not GitHub.
//
// Minecraft is deliberately ABSENT: every MC server is Pinned and is appended to the
// plan at runtime from the live fleet (integration), never force-tracked here.
// Keeping Minecraft out of the static topology is the code-level expression of the
// pin — there is no policy path by which Topology can propose changing it.
func Topology() []Spec {
return []Spec{
// Felis's own release repo, and the same slug deploy/bootstrap.sh clones from
// (FELIS_REPO_URL). It is PRIVATE today, which is why the github source carries an
// optional token: unauthenticated, this coord answers 404 — the status GitHub uses
// to hide a repo's existence — and felis-api is the one tracked component where
// that happens. k3s and cloudflared are public and need no credential.
{Name: "felis-api", Policy: updates.PolicyScheduled, Manageable: true, Source: sourceGitHub, Coord: "MliroLirrorsIngenuity/Felis"},
{Name: "k3s", Policy: updates.PolicyNotify, Manageable: false, Source: sourceGitHub, Coord: "k3s-io/k3s"},
{Name: "cloudflared", Policy: updates.PolicyScheduled, Manageable: true, Source: sourceGitHub, Coord: "cloudflare/cloudflared"},
{Name: "velocity", Policy: updates.PolicyNotify, Manageable: false, Source: sourcePaperMC, Coord: "velocity"},
}
}