fix(update): point the apply guidance at the installer, not felis setup (#54)
On a completed install 'felis setup' never re-runs the installer: its host-bootstrap phase only runs while an install marker is missing, so it opens the config console and moves no component. Live on the audit box, a clean 'felis setup' run left /opt/felis/velocity/velocity.jar's mtime and hash untouched while an installer re-run logged 'resolving the newest Velocity 3.5.1 build'. The 'felis update' guidance was wrong three ways accordingly: 'run: sudo felis setup' for panel/velocity/plugins, the 'felis setup is idempotent and re-runs the installer' trailer, and the felis-api-only exception block, whose scoping taught the same false model for velocity. Point every planner-backed selector at the tested path -- re-running the installer (the README's install one-liner) -- and replace the scoped caveat with one trailer: the channel is not persisted (pass FELIS_VERSION_BOOTSTRAP=dev on a host that tracks main), the private repo's one-liner needs the README's token'd form, and 'felis setup is not this path'. troubleshooting.md SS15 drops the same false alternative and gains the channel caveat. Gates: gofmt, go vet, go test ./..., deploy/bootstrap_test.sh all clean. Green live (v0.0.0+fix54 installed to /usr/local/bin over the fix52 backup, sha 0bd49467...): --panel and --velocity print the installer one-liner plus the single trailer, --mc stays command-free, --all prints the trailer once.
This commit is contained in:
3 files changed
+50
-50
No files matched your search
+25
-22
@@ -43,6 +43,18 @@ type updateTarget struct {
|
|||||||
command string
|
command string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// installerRerun is the tested apply path for every planner-backed selector: re-run the
|
||||||
|
// installer. It is idempotent, and it is the only path that fetches a newer version --
|
||||||
|
// `felis setup` skips its host-bootstrap phase on a completed install (all four install
|
||||||
|
// markers already exist), so there it opens the config console and moves no component,
|
||||||
|
// and even on the bootstrap path it re-images felis-api from the binary setup is already
|
||||||
|
// running (FELIS_BOOTSTRAP_BINARY), which looks like an update and changes nothing.
|
||||||
|
//
|
||||||
|
// The URL is the same one-liner both READMEs hand out. While the repo is private it
|
||||||
|
// answers 404 (raw.githubusercontent.com hides private repos), which is why the trailer
|
||||||
|
// below points at the README's token'd form for that case.
|
||||||
|
const installerRerun = "curl -fsSL https://raw.githubusercontent.com/FelisMC/Felis/main/deploy/bootstrap.sh | sudo bash"
|
||||||
|
|
||||||
// updateTargets is the selector table. panel and plugins both resolve to felis-api
|
// updateTargets is the selector table. panel and plugins both resolve to felis-api
|
||||||
// because they are not separately versioned: the panel is compiled into the felis
|
// because they are not separately versioned: the panel is compiled into the felis
|
||||||
// binary with //go:embed, and the plugin jars are built from this same repo in the
|
// binary with //go:embed, and the plugin jars are built from this same repo in the
|
||||||
@@ -52,19 +64,19 @@ var updateTargets = []updateTarget{
|
|||||||
selector: "panel",
|
selector: "panel",
|
||||||
component: "felis-api",
|
component: "felis-api",
|
||||||
note: "the panel is embedded in the felis binary (//go:embed), so updating it means rebuilding the felis image and rolling felis-api",
|
note: "the panel is embedded in the felis binary (//go:embed), so updating it means rebuilding the felis image and rolling felis-api",
|
||||||
command: "sudo felis setup",
|
command: installerRerun,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
selector: "velocity",
|
selector: "velocity",
|
||||||
component: "velocity",
|
component: "velocity",
|
||||||
note: "re-runs install_velocity: newest BUILD of the pinned minor (FELIS_VELOCITY_VERSION), atomic jar install, then restarts felis-velocity",
|
note: "re-runs install_velocity: newest BUILD of the pinned minor (FELIS_VELOCITY_VERSION), atomic jar install, then restarts felis-velocity",
|
||||||
command: "sudo felis setup",
|
command: installerRerun,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
selector: "plugins",
|
selector: "plugins",
|
||||||
component: "felis-api",
|
component: "felis-api",
|
||||||
note: "felis-velocity.jar is a host-file swap, but felis-paper.jar and felis-limbo.jar are baked into the lobby/limbo images and need a rebuild + re-mirror into the in-cluster registry (the installer re-run does both)",
|
note: "felis-velocity.jar is a host-file swap, but felis-paper.jar and felis-limbo.jar are baked into the lobby/limbo images and need a rebuild + re-mirror into the in-cluster registry (the installer re-run does both)",
|
||||||
command: "sudo felis setup",
|
command: installerRerun,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
selector: "mc",
|
selector: "mc",
|
||||||
@@ -220,7 +232,6 @@ func renderApplyGuidance(res updater.Result, selected map[string]bool, force boo
|
|||||||
|
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
var offeredCommand bool
|
var offeredCommand bool
|
||||||
var offeredFelisAPI bool
|
|
||||||
for _, t := range updateTargets {
|
for _, t := range updateTargets {
|
||||||
if !selected[t.selector] {
|
if !selected[t.selector] {
|
||||||
continue
|
continue
|
||||||
@@ -249,28 +260,20 @@ func renderApplyGuidance(res updater.Result, selected map[string]bool, force boo
|
|||||||
}
|
}
|
||||||
fmt.Fprintf(&b, " run: %s\n", t.command)
|
fmt.Fprintf(&b, " run: %s\n", t.command)
|
||||||
offeredCommand = true
|
offeredCommand = true
|
||||||
offeredFelisAPI = offeredFelisAPI || t.component == "felis-api"
|
|
||||||
}
|
}
|
||||||
// Only explain the command when one was actually offered; a --mc-only run has
|
// Only explain the command when one was actually offered; a --mc-only run has
|
||||||
// nothing to run and the trailer would be a non-sequitur.
|
// nothing to run and the trailer would be a non-sequitur.
|
||||||
if offeredCommand {
|
|
||||||
b.WriteString("\nfelis setup is idempotent and re-runs the installer that owns these components;\nit does not reinstall what is already current. Restart game servers afterwards.\n")
|
|
||||||
}
|
|
||||||
// Scoped to felis-api because it is the only component setup cannot move forward.
|
|
||||||
// velocity is fine: install_velocity re-resolves the newest build of the pinned minor
|
|
||||||
// on every run. But setup hands deploy/bootstrap.sh the binary it is itself running
|
|
||||||
// (FELIS_BOOTSTRAP_BINARY), and that arm skips the release lookup entirely, so it
|
|
||||||
// rebuilds the image and rolls the deployment from the SAME binary -- a run that looks
|
|
||||||
// like a successful update and leaves the version unchanged.
|
|
||||||
//
|
//
|
||||||
// The installer is the only thing that moves felis-api. It is safe to point at now
|
// One trailer serves every selector now: setup is not an apply path at all on a
|
||||||
// that detect_node_ip reuses the installed root domain, so what is left to warn about
|
// completed install (shouldRunHostBootstrapBeforeConfig only enters the host
|
||||||
// is the channel: FELIS_VERSION_BOOTSTRAP is not persisted anywhere and defaults to
|
// bootstrap while an install marker is missing), so the installer re-run is the one
|
||||||
// release, so a bare re-run on a host tracking main quietly moves it onto releases.
|
// worked path for all three components and there is no per-component exception left
|
||||||
// That is a channel change, not a broken install, which is why it is one clause and
|
// to scope. Two caveats stay because following the advice without them bites real
|
||||||
// not a paragraph.
|
// hosts: the channel is not persisted anywhere (a bare re-run on a main host quietly
|
||||||
if offeredFelisAPI {
|
// moves it onto releases), and the private repo's one-liner needs the read token
|
||||||
b.WriteString("\nfelis-api (panel, plugins) is the exception: setup re-images it from the felis binary\nalready on this host, so it cannot install a NEWER felis-api. Re-run the bootstrap\ninstaller for that -- it keeps this install's root domain. It does default to the\nrelease channel, so pass FELIS_VERSION_BOOTSTRAP=dev if this host tracks main.\n")
|
// back in the environment before it can resolve anything.
|
||||||
|
if offeredCommand {
|
||||||
|
b.WriteString("\nRe-running the installer applies everything above: it fetches the newest version on\nthe channel in effect and re-applies the bundle (release is the default). The channel\nis not persisted, so pass FELIS_VERSION_BOOTSTRAP=dev if this host tracks main. While\nthis repo is private, the one-liner above 404s without a token; the README's install\nsection has the token'd form that works. felis setup is not this path: on a completed\ninstall it opens the config console and installs nothing newer. Restart game servers\nafterwards.\n")
|
||||||
}
|
}
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
+20
-25
@@ -107,7 +107,7 @@ func TestApplyGuidanceMinecraftOffersNoCommand(t *testing.T) {
|
|||||||
if !strings.Contains(out, "pinned by policy") {
|
if !strings.Contains(out, "pinned by policy") {
|
||||||
t.Fatalf("want the pin explained:\n%s", out)
|
t.Fatalf("want the pin explained:\n%s", out)
|
||||||
}
|
}
|
||||||
if strings.Contains(out, "run:") || strings.Contains(out, "felis setup is idempotent") {
|
if strings.Contains(out, "run:") || strings.Contains(out, "Re-running the installer") {
|
||||||
t.Fatalf("--mc must offer no command and no command trailer:\n%s", out)
|
t.Fatalf("--mc must offer no command and no command trailer:\n%s", out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,42 +133,37 @@ func TestUpdateTargetsMatchTopology(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// `sudo felis setup` is the right answer for velocity and the wrong one for felis-api,
|
// Re-running the installer is the one apply path this table may hand out. setup is NOT an
|
||||||
// so the caveat has to be scoped rather than appended to every run. setup hands
|
// updater on a completed install -- its host-bootstrap phase only runs while an install
|
||||||
// bootstrap the binary it is already running, and that arm skips the release lookup:
|
// marker is missing, so it opens the config console and moves no component -- and even on
|
||||||
// the run rebuilds the image and rolls the deployment off the SAME binary, which looks
|
// the bootstrap path it re-images felis-api from the binary setup is already running. The
|
||||||
// like a successful update and changes nothing. install_velocity, by contrast, really
|
// table used to answer with "sudo felis setup" and scope a felis-api-only exception; both
|
||||||
// does re-resolve the newest build on every run.
|
// taught a model that does not survive contact with an installed host.
|
||||||
func TestApplyGuidanceScopesTheFelisAPICaveat(t *testing.T) {
|
func TestApplyGuidancePointsEveryComponentAtTheInstaller(t *testing.T) {
|
||||||
const caveat = "cannot install a NEWER felis-api"
|
|
||||||
|
|
||||||
api := renderApplyGuidance(
|
api := renderApplyGuidance(
|
||||||
planResult([]updates.Action{{Component: "felis-api", Kind: updates.ActionNotify, LatestKnown: true}}),
|
planResult([]updates.Action{{Component: "felis-api", Kind: updates.ActionNotify, LatestKnown: true}}),
|
||||||
map[string]bool{"panel": true}, false)
|
map[string]bool{"panel": true}, false)
|
||||||
if !strings.Contains(api, caveat) {
|
for _, want := range []string{"deploy/bootstrap.sh", "FELIS_VERSION_BOOTSTRAP=dev", "felis setup is not this path"} {
|
||||||
t.Fatalf("--panel resolves to felis-api and must carry the caveat:\n%s", api)
|
if !strings.Contains(api, want) {
|
||||||
|
t.Fatalf("--panel guidance missing %q:\n%s", want, api)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Naming the installer obliges us to name what a bare re-run still changes. The domain
|
if strings.Contains(api, "run: sudo felis setup") {
|
||||||
// is handled -- detect_node_ip reuses the installed one -- but the channel is not
|
t.Fatalf("setup must never be offered as the apply command:\n%s", api)
|
||||||
// persisted at all and defaults to release, so a host tracking main gets moved onto
|
|
||||||
// releases by following this advice.
|
|
||||||
if !strings.Contains(api, "FELIS_VERSION_BOOTSTRAP=dev") {
|
|
||||||
t.Fatalf("pointing at the installer without the channel caveat misleads a dev host:\n%s", api)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The same path serves velocity; a scoped caveat would re-teach the old model that
|
||||||
|
// setup fixes velocity.
|
||||||
vel := renderApplyGuidance(
|
vel := renderApplyGuidance(
|
||||||
planResult([]updates.Action{{Component: "velocity", Kind: updates.ActionNotify, LatestKnown: true}}),
|
planResult([]updates.Action{{Component: "velocity", Kind: updates.ActionNotify, LatestKnown: true}}),
|
||||||
map[string]bool{"velocity": true}, false)
|
map[string]bool{"velocity": true}, false)
|
||||||
if strings.Contains(vel, caveat) {
|
if !strings.Contains(vel, "run: curl -fsSL") || !strings.Contains(vel, "felis setup is not this path") {
|
||||||
t.Fatalf("velocity IS fixed by setup; the caveat would misdirect the operator:\n%s", vel)
|
t.Fatalf("velocity gets the same installer path:\n%s", vel)
|
||||||
}
|
|
||||||
if !strings.Contains(vel, "felis setup is idempotent") {
|
|
||||||
t.Fatalf("velocity still wants the ordinary trailer:\n%s", vel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --mc offers no command at all, so neither trailer belongs.
|
// --mc offers no command at all, so neither trailer belongs.
|
||||||
mc := renderApplyGuidance(planResult(nil), map[string]bool{"mc": true}, true)
|
mc := renderApplyGuidance(planResult(nil), map[string]bool{"mc": true}, true)
|
||||||
if strings.Contains(mc, caveat) {
|
if strings.Contains(mc, "deploy/bootstrap.sh") || strings.Contains(mc, "FELIS_VERSION_BOOTSTRAP") {
|
||||||
t.Fatalf("--mc offers no command; the caveat is a non-sequitur:\n%s", mc)
|
t.Fatalf("--mc offers no command; the trailer is a non-sequitur:\n%s", mc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -840,9 +840,11 @@ disk/memory thresholds, and the kubelet `DiskPressure` condition.
|
|||||||
## 15. Control-plane upgrades, and rolling back a bad one
|
## 15. Control-plane upgrades, and rolling back a bad one
|
||||||
|
|
||||||
There is no in-place updater: an upgrade is re-running the installer
|
There is no in-place updater: an upgrade is re-running the installer
|
||||||
(`curl -fsSL <installer URL> | sudo bash`, or `sudo felis setup`), which
|
(`curl -fsSL <installer URL> | sudo bash`), which rebuilds/re-imports the image
|
||||||
rebuilds/re-imports the image and re-applies the bundle. Two properties of the
|
and re-applies the bundle. (`sudo felis setup` is not this path; on a completed
|
||||||
control plane matter when you do:
|
install it only opens the config console.) The channel is not persisted across
|
||||||
|
the re-run, so pass `FELIS_VERSION_BOOTSTRAP=dev` on a host that tracks main.
|
||||||
|
Two properties of the control plane matter when you do:
|
||||||
|
|
||||||
- Both Deployments use strategy **Recreate** (single replica, no leader election:
|
- Both Deployments use strategy **Recreate** (single replica, no leader election:
|
||||||
two overlapping instances would fight over the same cluster). An upgrade takes
|
two overlapping instances would fight over the same cluster). An upgrade takes
|
||||||
|
|||||||
Reference in new issue
Block a user