Everything in server.yaml that is not one machine's own is now a versioned document in the store (cluster_settings), the same on every control plane. The first start against a store imports the file's cluster keys, with the baseline guard policy when there is none; after that those keys in a file are ignored, with a warning. Each control plane polls the document's version and its own server.yaml's hash. A change is validated and applied: guard live (Gate.Replace), anything else by a graceful restart that systemd brings back, standbys first and the leader 30s later. A version that does not load on a machine is not applied; at start the newest one that loads is used, so a bad edit cannot keep every control plane down. llmgw ctl config show/get/set/unset/edit/history/rollback, the admin API under /api/v1/settings, and the console's Settings screen, which layers the store's document over the machine's file, write it. Commands beside the control plane that read cluster keys (init, node add, server add, backup, upgrade) read the layered configuration; init --bundle writes offline to the store once it holds the settings. A standby that becomes the leader re-reads models, engine choices and facility holds from the store. Retires appendGuardIfMissing: the import does it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
178 lines
5.2 KiB
Go
178 lines
5.2 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/bundle"
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/config"
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/mirror"
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/settings"
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/tlsconf"
|
|
)
|
|
|
|
// --- installing from an offline bundle ---
|
|
|
|
// checkBundle refuses, before anything is done, a bundle init could not
|
|
// install from: one that is not there, or not the one that was made.
|
|
func (in *serverInit) checkBundle() error {
|
|
if in.bundle == "" {
|
|
return nil
|
|
}
|
|
if in.noMirror && in.mirrorURL == "" {
|
|
return errors.New("a bundle goes into the cluster's mirror: leave out --no-mirror, or give --mirror")
|
|
}
|
|
if in.bundleSHA == "" {
|
|
return errors.New("give --bundle-sha256, the sha256 'llmgw bundle create' printed: " +
|
|
"it is what says the bundle that arrived is the one that was made")
|
|
}
|
|
_, digest, err := bundle.ReadManifest(in.bundle)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if digest != in.bundleSHA {
|
|
return fmt.Errorf("the bundle's sha256 is %s, not the %s given: it is not the bundle that was made, "+
|
|
"or the digest was mistyped", digest, in.bundleSHA)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// bundleCarries says whether the bundle holds a model of this name.
|
|
func (in *serverInit) bundleCarries(model string) bool {
|
|
m, _, err := bundle.ReadManifest(in.bundle)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
for _, md := range m.Models {
|
|
if md.Name == model {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// offline says whether this cluster is set up to reach nothing outside.
|
|
func (in *serverInit) offline() bool {
|
|
if in.bundle != "" {
|
|
return true
|
|
}
|
|
cfg, err := loadEffectiveConfig(in.configPath())
|
|
return err == nil && cfg.Offline.Enabled()
|
|
}
|
|
|
|
func (in *serverInit) mirrorRoot() string {
|
|
if in.mirrorPath != "" {
|
|
return in.mirrorPath
|
|
}
|
|
return mirrorDir
|
|
}
|
|
|
|
// ensureBundle puts the bundle into the mirror beside this control plane,
|
|
// has the control plane trust its engines' checksums, and says in
|
|
// server.yaml that the cluster is offline. With a mirror on another
|
|
// machine, the files are imported there; here only the checksums are kept.
|
|
func (in *serverInit) ensureBundle(ctx context.Context) error {
|
|
cfg, err := loadEffectiveConfig(in.configPath())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var (
|
|
m bundle.Manifest
|
|
files string
|
|
)
|
|
if in.mirrorURL == "" {
|
|
res, err := bundle.Import(in.bundle, bundle.ImportOptions{Mirror: in.mirrorRoot(), SHA256: in.bundleSHA})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if res.Written > 0 {
|
|
in.done("offline bundle in the mirror: %d files, %s, each checked", res.Written, fileSize(res.Bytes))
|
|
} else {
|
|
in.kept("offline bundle in the mirror (%d files)", res.Kept)
|
|
}
|
|
m = res.Manifest
|
|
files = "http://" + net.JoinHostPort(in.reachableAddr(), "8081")
|
|
} else {
|
|
mc, err := mirror.NewClient(in.mirrorURL, tlsconf.Config{}, 10*time.Second)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
list, err := mc.List(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("asking the mirror at %s where nodes reach it: %w", in.mirrorURL, err)
|
|
}
|
|
files = list.Base
|
|
if m, _, err = bundle.ReadManifest(in.bundle); err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintf(in.out, " !! the mirror is on another machine: import the bundle there too, "+
|
|
"sudo llmgw bundle import <bundle> --sha256 %s\n", in.bundleSHA)
|
|
}
|
|
sums := cfg.Offline.Checksums
|
|
if sums == "" {
|
|
sums = filepath.Join(in.etcDir, "offline.sha256")
|
|
}
|
|
n, err := bundle.TrustManifest(m, sums)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
in.done("the control plane trusts %d engine files from the bundle (%s)", n, sums)
|
|
|
|
if cfg.Offline.Enabled() {
|
|
in.kept("offline, installing from %s, as server.yaml says", cfg.Offline.Mirror)
|
|
return nil
|
|
}
|
|
if sums == config.DefaultOfflineChecksums {
|
|
sums = ""
|
|
}
|
|
// In the cluster's settings once the store holds them; in server.yaml
|
|
// before, where the first start brings it in.
|
|
stored, err := changeStoredSettings(in.configPath(), "offline, from server init --bundle", func(doc *settings.Doc) error {
|
|
if err := doc.SetYAML([]string{"offline", "mirror"}, files); err != nil {
|
|
return err
|
|
}
|
|
if sums != "" {
|
|
return doc.SetYAML([]string{"offline", "checksums"}, sums)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if stored {
|
|
in.done("the cluster's settings: offline, nodes install from the mirror at %s", files)
|
|
return nil
|
|
}
|
|
if err := appendOfflineBlock(in.configPath(), files, sums); err != nil {
|
|
return err
|
|
}
|
|
in.done("server.yaml: offline, nodes install from the mirror at %s", files)
|
|
return nil
|
|
}
|
|
|
|
// appendOfflineBlock says in server.yaml that the cluster is offline,
|
|
// without touching the rest.
|
|
func appendOfflineBlock(path, files, sums string) error {
|
|
b, err := os.ReadFile(path) //nolint:gosec // server.yaml
|
|
if err != nil {
|
|
return err
|
|
}
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
block := "\n# Offline: nothing in the cluster reaches the internet. Nodes install\n" +
|
|
"# engines, and upgrades fetch releases, from the mirror, out of the bundle\n" +
|
|
"# 'llmgw server init --bundle' imported; 'llmgw bundle import' adds more.\n" +
|
|
"offline:\n mirror: " + files + "\n"
|
|
if sums != "" {
|
|
block += " checksums: " + sums + "\n"
|
|
}
|
|
return os.WriteFile(path, append(b, []byte(block)...), info.Mode().Perm())
|
|
}
|