panel v0.9.2 — public release
Self-hostable game server control panel: Go controller + agent, 26 game modules, embedded web UI. One-line install via install.sh / install.ps1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
// Package updater implements the Generic Update system: pluggable
|
||||
// providers that fetch new versions of game files from SteamCMD,
|
||||
// GitHub releases, or a direct URL, then lay them down under the
|
||||
// instance's data path (bind mount) or container volume.
|
||||
//
|
||||
// The panel's manifest declares one or more update_providers per module;
|
||||
// the operator picks one to trigger. Each provider runs on the *Agent*
|
||||
// (close to the data) and streams progress back as LogLine envelopes
|
||||
// so the dashboard renders updates in the same events pane as normal
|
||||
// server output.
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/dbledeez/panel/agent/internal/runtime"
|
||||
modulepkg "github.com/dbledeez/panel/pkg/module"
|
||||
)
|
||||
|
||||
// Context is the environment an update runs in.
|
||||
type Context struct {
|
||||
InstanceID string
|
||||
Manifest *modulepkg.Manifest
|
||||
ContainerID string // empty if container doesn't exist; present for running + stopped
|
||||
BrowseableRoot string // container-absolute install root, when applicable
|
||||
DataPath string // host bind-mount root, fallback
|
||||
Runtime runtime.Runtime // for container file ops + sidecar launches (future)
|
||||
Log func(line string) // streams a progress line upstream via LogLine
|
||||
|
||||
// Steam login credentials for SteamCMD apps that refuse +login
|
||||
// anonymous (DayZ, Arma, etc.). Populated by the controller from its
|
||||
// encrypted steam_credentials store, forwarded via the mTLS-protected
|
||||
// gRPC stream. The agent redacts SteamPassword from log output before
|
||||
// it ever hits the bus; see steamcmd.go.
|
||||
SteamUsername string
|
||||
SteamPassword string
|
||||
}
|
||||
|
||||
// Provider performs an update according to its kind.
|
||||
type Provider interface {
|
||||
Name() string
|
||||
Update(ctx context.Context, uc *Context, spec *modulepkg.UpdateProvider) error
|
||||
}
|
||||
|
||||
// Get returns the Provider implementation for the given kind, or an
|
||||
// error if the kind is unknown.
|
||||
func Get(kind string) (Provider, error) {
|
||||
switch kind {
|
||||
case "direct":
|
||||
return &DirectProvider{}, nil
|
||||
case "github":
|
||||
return &GitHubProvider{}, nil
|
||||
case "steamcmd":
|
||||
return &SteamCMDProvider{}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown update provider kind %q", kind)
|
||||
}
|
||||
}
|
||||
|
||||
// writeToInstance puts data at the instance-relative path. Prefers
|
||||
// container-path ops when the instance has a BrowseableRoot and a live
|
||||
// container; otherwise falls back to writing under DataPath.
|
||||
func writeToInstance(ctx context.Context, uc *Context, targetPath string, data []byte) error {
|
||||
if targetPath == "" {
|
||||
return fmt.Errorf("target_path is required")
|
||||
}
|
||||
if uc.BrowseableRoot != "" && uc.ContainerID != "" {
|
||||
abs := path.Join(uc.BrowseableRoot, targetPath)
|
||||
// Ensure parent dir exists inside the container via runtime exec.
|
||||
// We don't have a helper in the interface; we just try to write
|
||||
// and rely on CopyFileToContainer returning a clear error if the
|
||||
// parent is missing. (itzg and most images pre-create /data.)
|
||||
return uc.Runtime.CopyFileToContainer(ctx, uc.ContainerID, abs, data)
|
||||
}
|
||||
if uc.DataPath == "" {
|
||||
return fmt.Errorf("no storage available: container has no BrowseableRoot and instance has no DataPath")
|
||||
}
|
||||
abs := filepath.Join(uc.DataPath, filepath.FromSlash(targetPath))
|
||||
if err := os.MkdirAll(filepath.Dir(abs), 0o755); err != nil {
|
||||
return fmt.Errorf("mkdir %s: %w", filepath.Dir(abs), err)
|
||||
}
|
||||
if err := os.WriteFile(abs, data, 0o644); err != nil {
|
||||
return fmt.Errorf("write %s: %w", abs, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user