A bundle carries what a cluster would download — llmgw's release with its signed checksums, every llama.cpp build per architecture with GitHub's checksums, model files pinned and checked against Hugging Face's — into a directory or a tar whose manifest sha256 is carried apart. Import checks everything, the release against the signing key, before anything is put in the mirror. With offline.mirror set, nodes install llama.cpp from the mirror against checksums the control plane holds and sends over the agent stream; releases default to the mirror for node add, ctl upgrade and agent upgrades. server init and the setup form install from a bundle. The Helm chart runs one control plane with SQLite on a claim or an external PostgreSQL, Secrets for the agent certificate and admin token, Services, Ingress and ServiceMonitor; deploy/container/Dockerfile builds its image. GPU nodes stay outside Kubernetes, as docs/kubernetes.md explains. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
159 lines
4.6 KiB
Go
159 lines
4.6 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/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 := config.Load(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 := config.Load(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 = ""
|
|
}
|
|
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())
|
|
}
|