Add `felis update`, which reports which platform components have newer versions available, and route `felis version`, which shipped implemented but unreachable. That bug is why the subcommand router is now a map rather than a switch. cmdVersion existed with nothing dispatching to it and no usage line, so `felis version` fell through to "unknown command" and no test noticed — a switch offers no way to enumerate what it routes, so the usage text and the router could not be compared. As data, they can: a test now walks the Commands: block and the table in both directions, failing an entry added to one without the other. bootstrap-assets stays deliberately undocumented and is listed as such, which makes its absence a decision rather than an oversight. The host gatherer answers the two seams NewSysGatherer leaves nil, for the one caller that can satisfy them without a cluster client. felis-api is answered from the running binary's own build stamp rather than the Deployment's image tag: deploy/bootstrap.sh builds the image from the same checkout it installs /usr/local/bin/felis from and stamps both with one git describe, so it is the same artifact, and it is the identity `felis version` reports. Reading the Deployment answers a slightly different question — what is rolled out — and stays the right seam for the in-cluster path. Velocity is read from the jar's own META-INF/MANIFEST.MF Implementation-Version, which is what the proxy reports about itself at runtime, because bootstrap installs the jar under a fixed name with no version in it. The filename extractor remains only as a fallback for a hand-placed velocity-3.5.1.jar. An unstamped local build reports "dev" and is refused with an actionable message rather than being treated as 0.0.0, which would make every release upstream look like an upgrade. The panel and the plugin jars have no version of their own on purpose: they are embedded in or built alongside the felis binary, so the felis version is theirs.
76 lines
2.8 KiB
Go
76 lines
2.8 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
const usage = `felis — Kubernetes-native Minecraft server orchestration
|
|
|
|
Usage:
|
|
felis <command> [flags]
|
|
|
|
Commands:
|
|
migrate up Apply embedded database migrations under an advisory lock
|
|
operator Run the MinecraftServer controller-manager
|
|
api Run the felis-api HTTP server
|
|
nano Run the Felis-nano hasJoined multiplexer (multi-Yggdrasil, no control plane)
|
|
reaper Run the world reaper / backup batch
|
|
restore Extract a world archive into a world volume (internal Job entrypoint)
|
|
backup Archive a world into the backup store and record it (internal Job entrypoint)
|
|
manifests Render the control-plane RBAC + NetworkPolicy install bundle as YAML
|
|
apply Create a MinecraftServer CRD (direct K8s write; use -f server.json)
|
|
setup Run host bootstrap + first-run setup console (TUI; requires root/sudo)
|
|
version Print the build stamp of this binary
|
|
update Report which platform components have updates available
|
|
breakGlass Open the local break-glass emergency console (TUI; requires root/sudo)
|
|
|
|
Run "felis <command> -h" for command-specific flags.
|
|
`
|
|
|
|
// commands is the dispatch table. It is a map rather than a switch so the router's
|
|
// contents are DATA a test can compare against the usage text above: `version`
|
|
// shipped once as an implemented-but-unreachable command (cmdVersion existed with
|
|
// nothing routing to it), and a switch offers no way to notice that. Adding an entry
|
|
// here without documenting it in usage — or vice versa — now fails a test instead of
|
|
// shipping.
|
|
//
|
|
// The help aliases are deliberately NOT entries: they print usage rather than run a
|
|
// subcommand, and listing them would make the table disagree with the command list.
|
|
var commands = map[string]func(args []string, stdout, stderr io.Writer) int{
|
|
"migrate": cmdMigrate,
|
|
"operator": cmdOperator,
|
|
"api": cmdAPI,
|
|
"nano": cmdNano,
|
|
"reaper": cmdReaper,
|
|
"restore": cmdRestore,
|
|
"backup": cmdBackup,
|
|
"manifests": cmdManifests,
|
|
"apply": cmdApply,
|
|
"setup": cmdSetup,
|
|
"breakGlass": cmdBreakGlass,
|
|
"bootstrap-assets": cmdBootstrapAssets,
|
|
"version": cmdVersion,
|
|
"update": cmdUpdate,
|
|
}
|
|
|
|
// run dispatches a subcommand. It is separate from main so the router is
|
|
// testable without spawning a process.
|
|
func run(args []string, stdout, stderr io.Writer) int {
|
|
if len(args) == 0 {
|
|
fmt.Fprint(stderr, usage)
|
|
return 2
|
|
}
|
|
cmd, rest := args[0], args[1:]
|
|
switch cmd {
|
|
case "-h", "--help", "help":
|
|
fmt.Fprint(stdout, usage)
|
|
return 0
|
|
}
|
|
if fn, ok := commands[cmd]; ok {
|
|
return fn(rest, stdout, stderr)
|
|
}
|
|
fmt.Fprintf(stderr, "felis: unknown command %q\n\n%s", cmd, usage)
|
|
return 2
|
|
}
|