panel v0.9.1 — open-source game server manager

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 23:18:05 -07:00
commit 4cf3471398
2161 changed files with 300831 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
module github.com/dbledeez/panel/proto
go 1.26
require (
google.golang.org/grpc v1.80.0
google.golang.org/protobuf v1.36.11
)
require (
golang.org/x/net v0.49.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect
)
+38
View File
@@ -0,0 +1,38 @@
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
File diff suppressed because it is too large Load Diff
+785
View File
@@ -0,0 +1,785 @@
syntax = "proto3";
package panel.v1;
option go_package = "github.com/dbledeez/panel/proto/panel/v1;panelv1";
import "google/protobuf/timestamp.proto";
import "panel/v1/common.proto";
// Agent is the gRPC service a Target agent dials on the Controller.
// A single persistent bidirectional stream carries all control-plane
// traffic between Controller and Target: commands downstream, state
// and events upstream.
service Agent {
rpc Connect(stream AgentEnvelope) returns (stream ControllerEnvelope);
}
// ControllerEnvelope — messages from Controller to Target.
message ControllerEnvelope {
string correlation_id = 1;
google.protobuf.Timestamp sent_at = 2;
oneof payload {
ControllerHello hello = 10;
Ping ping = 11;
ExecRequest exec = 20;
InstanceCreate instance_create = 30;
InstanceStart instance_start = 31;
InstanceStop instance_stop = 32;
InstanceDelete instance_delete = 33;
RCONCommand rcon = 40;
FsListRequest fs_list = 50;
FsReadRequest fs_read = 51;
FsWriteRequest fs_write = 52;
FsDeleteRequest fs_delete = 53;
FsSymlinkRequest fs_symlink = 54;
FsExtractRequest fs_extract = 55;
FsCompressRequest fs_compress = 56;
FsRenameRequest fs_rename = 57;
FsWriteChunkRequest fs_write_chunk = 58;
ArkSaveRestoreRequest ark_save_restore = 59;
DayzModInstallRequest dayz_mod_install = 90;
DayzModUninstallRequest dayz_mod_uninstall = 91;
UpdateRequest update = 60;
BackupRequest backup = 70;
RestoreRequest restore = 71;
BackupListEntriesRequest backup_list_entries = 72;
BackupReadFileRequest backup_read_file = 73;
EmpyrionScenarioInstallRequest empyrion_scenario_install = 100;
EmpyrionDiscoveriesRequest empyrion_discoveries = 101;
EmpyrionPlayerSummaryRequest empyrion_player_summary = 102;
RegionScanRequest region_scan = 110;
RegionHealRequest region_heal = 111;
InstanceRenderConfigRequest instance_render_config = 112;
SeedModsRequest seed_mods = 113;
UpdateCheckRequest update_check = 114;
}
}
// AgentEnvelope — messages from Target to Controller.
message AgentEnvelope {
string correlation_id = 1;
google.protobuf.Timestamp sent_at = 2;
oneof payload {
AgentHello hello = 10;
Heartbeat heartbeat = 11;
Pong pong = 12;
CommandResult result = 20;
InstanceStateUpdate instance_state = 30;
AppStateUpdate app_state = 31;
LogLine log = 32;
PlayerEvent player = 33;
RCONResult rcon_result = 40;
FsListResult fs_list_result = 50;
FsReadResult fs_read_result = 51;
FsWriteResult fs_write_result = 52;
FsDeleteResult fs_delete_result = 53;
FsSymlinkResult fs_symlink_result = 54;
FsExtractResult fs_extract_result = 55;
FsCompressResult fs_compress_result = 56;
FsRenameResult fs_rename_result = 57;
FsWriteChunkResult fs_write_chunk_result = 58;
ArkSaveRestoreResult ark_save_restore_result = 59;
DayzModInstallResult dayz_mod_install_result = 90;
DayzModUninstallResult dayz_mod_uninstall_result = 91;
UpdateResult update_result = 60;
BackupResult backup_result = 70;
RestoreResult restore_result = 71;
BackupListEntriesResult backup_list_entries_result = 72;
BackupReadFileResult backup_read_file_result = 73;
InstanceStatsUpdate instance_stats = 80;
EmpyrionScenarioInstallResult empyrion_scenario_install_result = 100;
EmpyrionDiscoveriesResult empyrion_discoveries_result = 101;
EmpyrionPlayerSummaryResult empyrion_player_summary_result = 102;
RegionScanResult region_scan_result = 110;
RegionHealResult region_heal_result = 111;
InstanceRenderConfigResult instance_render_config_result = 112;
SeedModsResult seed_mods_result = 113;
UpdateCheckResult update_check_result = 114;
}
}
// ---- Handshake ----
message AgentHello {
string agent_id = 1;
string agent_version = 2;
string host_os = 3; // "linux", "windows"
string host_arch = 4; // "amd64", "arm64"
string hostname = 5;
repeated string supported_runtimes = 6; // "docker", "host"
repeated ModuleSummary modules = 7;
}
// ModuleSummary is what an agent advertises about one locally-loaded
// module at handshake time. The controller aggregates these across all
// agents so the dashboard can render a "known modules" picker without
// needing direct filesystem access to each agent's modules/ dir.
message ModuleSummary {
string id = 1;
string name = 2;
string version = 3;
repeated string supported_modes = 4;
repeated ModuleUpdateProviderSummary update_providers = 5;
bool has_rcon = 6;
repeated string authors = 7;
}
message ModuleUpdateProviderSummary {
string id = 1;
string kind = 2; // "steamcmd" | "github" | "direct"
// True when the provider sets `requires_steam_login: true` on the
// manifest (DayZ, Arma). Controller uses this to decide whether to
// pop the Steam-login modal before forwarding an UpdateRequest.
bool requires_steam_login = 3;
}
message ControllerHello {
string controller_version = 1;
google.protobuf.Timestamp server_time = 2;
}
message Heartbeat {
google.protobuf.Timestamp at = 1;
double cpu_percent = 2;
uint64 mem_used_bytes = 3;
uint64 mem_total_bytes = 4;
uint32 instance_count = 5;
}
message Ping { string nonce = 1; }
message Pong { string nonce = 1; }
// ---- Exec / commands ----
message ExecRequest {
string instance_id = 1; // empty = run on Target host
repeated string argv = 2;
map<string, string> env = 3;
string working_dir = 4;
uint32 timeout_seconds = 5;
}
message CommandResult {
int32 exit_code = 1;
bytes stdout = 2;
bytes stderr = 3;
Error error = 4;
}
// ---- Instance lifecycle ----
message InstanceCreate {
string instance_id = 1;
string module_id = 2;
string version = 3;
RunMode run_mode = 4;
ResourceLimits limits = 5;
map<string, string> config_values = 6; // flattened key/value, module schema-validated
repeated PortMap ports = 7;
string data_path = 8;
// mount_overrides lets the controller swap one of the module's
// manifest-defined volume mounts at create time. Keyed by container
// path (matching a volume entry's `container:` field in module.yaml).
// Value is either a Docker named volume name (no leading slash) or an
// absolute host path (leading slash = bind mount).
//
// Used by the ARK cluster feature to swap an instance's per-instance
// `panel-<id>-cluster` named volume for a shared `panel-ark-cluster-<id>`
// volume when the server joins a cluster. Generic enough for future
// features — any module can benefit from per-instance mount swaps
// without needing a manifest change.
map<string, string> mount_overrides = 9;
}
message InstanceStart { string instance_id = 1; }
message InstanceStop {
string instance_id = 1;
uint32 grace_seconds = 2;
bool force = 3;
}
// InstanceDelete tears down the instance record. If purge_volumes is
// true, every Docker volume labeled panel.instance_id=<instance_id> is
// removed too; otherwise volumes survive so a subsequent re-create can
// pick up the existing world/save data.
message InstanceDelete {
string instance_id = 1;
uint32 grace_seconds = 2; // if the container is still running, stop with this grace first
bool purge_volumes = 3;
}
// ---- State streams ----
message InstanceStateUpdate {
string instance_id = 1;
InstanceStatus status = 2;
int32 exit_code = 3;
google.protobuf.Timestamp at = 4;
string detail = 5;
}
message AppStateUpdate {
string instance_id = 1;
int32 players_online = 2;
int32 players_max = 3;
int64 uptime_seconds = 4;
map<string, string> module_fields = 10; // world_seed, day_number, tps, etc.
google.protobuf.Timestamp at = 20;
}
message LogLine {
string instance_id = 1;
string stream = 2; // "stdout", "stderr", or a named log file
google.protobuf.Timestamp at = 3;
string line = 4;
}
// InstanceStatsUpdate is emitted periodically (default every 10s) by the
// agent's per-instance stats poller. Values come from Docker's stats
// stream API. Kept sparse — enough for UI gauges + future scheduler
// expressions like "cpu_percent > 80 sustained 5m -> notify".
message InstanceStatsUpdate {
string instance_id = 1;
double cpu_percent = 2; // 100 = 1 vCPU saturated
uint64 mem_used_bytes = 3;
uint64 mem_limit_bytes = 4; // 0 if no explicit limit set (then use container's view)
uint64 net_rx_bytes = 5; // cumulative since container start
uint64 net_tx_bytes = 6;
uint32 pids = 7;
google.protobuf.Timestamp at = 8;
// Host core count (docker OnlineCPUs) so UIs can translate the
// docker-style cpu_percent (100 = 1 core) into percent-of-host.
uint32 host_cpus = 9;
}
// ---- Ad-hoc RCON ----
message RCONCommand {
string instance_id = 1;
string command = 2;
}
message RCONResult {
string output = 1;
string error = 2; // empty on success; short machine-readable on failure
}
// ---- File management (rooted at each instance's data_path) ----
message FsEntry {
string name = 1; // base name
string path = 2; // path relative to instance data_path
bool is_dir = 3;
int64 size = 4; // bytes (for files)
google.protobuf.Timestamp mod_time = 5;
}
message FsListRequest { string instance_id = 1; string path = 2; }
message FsListResult {
repeated FsEntry entries = 1;
string path = 2;
string error = 3;
}
message FsReadRequest { string instance_id = 1; string path = 2; }
message FsReadResult {
string path = 1;
bytes content = 2;
string error = 3;
}
message FsWriteRequest { string instance_id = 1; string path = 2; bytes content = 3; }
message FsWriteResult { int64 bytes_written = 1; string error = 2; }
message FsDeleteRequest { string instance_id = 1; string path = 2; bool recursive = 3; }
message FsDeleteResult { string error = 1; }
// FsSymlinkRequest creates (or replaces) a symlink inside the instance
// container. Used by the DayZ mod manager to link @ModName entries to
// the shared workshop tree at /game/steamapps/workshop/content/...
message FsSymlinkRequest { string instance_id = 1; string target = 2; string link_path = 3; }
message FsSymlinkResult { string error = 1; }
// FsExtractRequest expands an archive that already lives inside the
// instance's volume. Format is auto-detected from the archive bytes
// (zip / tar / tar.gz / tar.bz2). dest_dir is rooted in the instance's
// browseable root just like every other Fs op; "" means same dir as
// the archive.
message FsExtractRequest {
string instance_id = 1;
string archive_path = 2;
string dest_dir = 3;
bool overwrite = 4;
}
message FsExtractResult {
int64 entries_written = 1;
int64 bytes_written = 2;
string error = 3;
}
// FsCompressRequest produces a zip archive at dest_zip_path containing
// the contents of every path in `sources` (recursive for dirs). All
// paths must be inside the instance's browseable root.
message FsCompressRequest {
string instance_id = 1;
repeated string sources = 2;
string dest_zip_path = 3;
bool overwrite = 4;
}
message FsCompressResult {
int64 entries_written = 1;
int64 bytes_written = 2;
string error = 3;
}
// FsRenameRequest is a path-safe `mv` inside the instance's volume —
// supports rename + move-into-sibling-dir within the same root.
message FsRenameRequest {
string instance_id = 1;
string from_path = 2;
string to_path = 3;
}
message FsRenameResult { string error = 1; }
// FsWriteChunkRequest streams a single chunk of a larger upload. Chunks
// are uploaded in order; each one carries the same upload_id (browser-
// generated UUID) so the agent can append to the same temp file. On
// is_final = true the agent finalizes (closes the temp file, ships it
// to the container via CopyToContainer, then unlinks).
message FsWriteChunkRequest {
string instance_id = 1;
string upload_id = 2;
string path = 3;
int64 offset = 4;
bytes data = 5;
int64 total_size = 6; // optional; agent uses for sanity checks on final
bool is_final = 7;
}
message FsWriteChunkResult {
int64 bytes_received = 1;
int64 total_received = 2; // running total per upload_id
bool finalized = 3;
string error = 4;
}
// ArkSaveRestoreRequest swaps a timestamped rolling backup into the
// active save slot under SavedArks/<subdir>/<subdir>.ark. The previous
// active save is moved aside (NEVER deleted) by appending a "_replaced"
// timestamp suffix so even abandoned attempts are recoverable. Path is
// rooted at ShooterGame/Saved/SavedArks/<saved_arks_subdir>/ in the
// instance's browseable root.
//
// Caller (controller) is responsible for stopping the container before
// invoking this — the agent only performs file operations. If a backup
// rolling file (.arkrbf) is selected, it is renamed to .ark before
// installing as the active save.
message ArkSaveRestoreRequest {
string instance_id = 1;
string saved_arks_subdir = 2; // e.g. "TheIsland_WP"
string target_filename = 3; // basename within saved_arks_subdir (e.g. "TheIsland_WP_30.04.2026_22.13.00.ark")
}
message ArkSaveRestoreResult {
string aside_filename = 1; // what the previously-active save was renamed to
string installed_filename = 2; // the filename that's now active (always "<subdir>.ark")
string error = 3;
}
// DayzModInstallRequest asks the agent to (1) copy the mod's bikeys into
// /game/keys and (2) create a @ModName symlink at /game/<folder_name>
// pointing at the shared workshop content at
// /game/steamapps/workshop/content/221100/<workshop_id>. Agent-side so
// it can scan the workshop dir and preserve file ownership.
message DayzModInstallRequest {
string instance_id = 1;
string workshop_id = 2;
string folder_name = 3; // e.g. "@CommunityOnlineTools"
}
message DayzModInstallResult {
string error = 1;
repeated string bikeys_copied = 2; // filenames of bikeys dropped in /game/keys
string resolved_mod_path = 3; // /game/steamapps/workshop/content/221100/<id>
}
// DayzModUninstallRequest removes the @ModName symlink and the mod's
// bikeys from /game/keys (bikeys are matched by file hash — only keys
// whose content originated from this workshop item are removed).
message DayzModUninstallRequest {
string instance_id = 1;
string folder_name = 2;
}
message DayzModUninstallResult {
string error = 1;
repeated string bikeys_removed = 2;
}
// ---- Updates ----
//
// The agent runs the update asynchronously. The synchronous UpdateResult
// carries only the acknowledgement + ref-id for later correlation; real
// progress + final status flow through LogLine (stream="update") and the
// terminal "update:ok"/"update:error:<msg>" LogLine sentinel at the end.
message UpdateRequest {
string instance_id = 1;
string provider_id = 2; // empty = use the module's first update_provider
// Steam login credentials for apps whose SteamCMD refuses +login
// anonymous (DayZ, Arma, etc. — modules with `requires_steam_login`
// set on the provider). The controller pulls these from its
// encrypted steam_credentials store and forwards them via the
// mTLS-protected control stream. Agent never logs them.
string steam_username = 3;
string steam_password = 4;
}
message UpdateResult {
bool accepted = 1; // true = agent kicked off the update goroutine
string error = 2; // empty on accept; populated on immediate reject
string provider_id = 3;
string provider_kind = 4;
}
// ---- Backup + Restore ----
//
// Synchronous over the bidi stream. The sidecar tars the instance's
// volume into a .tar.gz on the agent's --backup-dir; the terminal
// BackupResult carries the path + size. Restore takes a backup id
// (path resolved by the controller via DB) and reverses.
message BackupRequest {
string instance_id = 1;
string description = 2;
}
message BackupResult {
string backup_id = 1; // bkp_<hex>
string path = 2;
int64 size_bytes = 3;
string error = 4;
}
message RestoreRequest {
string instance_id = 1;
string backup_id = 2;
string backup_path = 3; // absolute path on the agent
}
message RestoreResult {
string error = 1;
int64 bytes_restored = 2;
}
// BackupListEntriesRequest asks the agent to enumerate the contents of a
// backup tarball without extracting it. Used by the backup-browser UI so
// operators can drill into a snapshot to confirm what's inside before
// restoring (or to recover a single file).
message BackupListEntriesRequest {
string instance_id = 1;
string backup_id = 2;
string backup_path = 3; // absolute path on the agent (resolved controller-side)
}
// BackupTarEntry — one row from `tar -tvzf`. Size is bytes-on-disk, not
// compressed size. is_dir distinguishes directories so the UI can render
// a tree without a second pass.
message BackupTarEntry {
string path = 1; // path inside the tarball, no leading slash
int64 size = 2;
bool is_dir = 3;
string mode = 4; // permissions string, e.g. "-rw-r--r--"
google.protobuf.Timestamp mod_time = 5;
}
message BackupListEntriesResult {
repeated BackupTarEntry entries = 1;
int64 total_bytes = 2; // sum of file sizes (not compressed size of the tarball)
string error = 3;
}
// BackupReadFileRequest extracts a single entry's bytes to memory by
// streaming `tar -xzOf <archive> <path>` into a buffer. Caller-supplied
// max_bytes caps the read so a malicious / corrupt archive can't OOM the
// agent — the result includes truncated=true if max_bytes was hit.
message BackupReadFileRequest {
string instance_id = 1;
string backup_id = 2;
string backup_path = 3;
string entry_path = 4;
int64 max_bytes = 5; // 0 → controller-side default (8 MiB)
}
message BackupReadFileResult {
bytes content = 1;
int64 size = 2; // actual entry size from tar header (may be > len(content) if truncated)
bool truncated = 3;
bool is_binary = 4; // heuristic: NUL byte in first 8 KiB
string error = 5;
}
message PlayerEvent {
enum Kind {
KIND_UNSPECIFIED = 0;
KIND_JOIN = 1;
KIND_LEAVE = 2;
KIND_CHAT = 3;
KIND_DEATH = 4;
KIND_CUSTOM = 99;
}
string instance_id = 1;
Kind kind = 2;
string player_id = 3; // SteamID / UUID where available
string player_name = 4;
string detail = 5;
google.protobuf.Timestamp at = 6;
}
// EmpyrionScenarioInstallRequest is sent controller → agent to install
// (or refresh) a workshop scenario into a specific Empyrion instance's
// game volume. The agent runs SteamCMD on its OWN Docker daemon — the
// previous (single-host) implementation ran on the controller's daemon
// which has no view of an agent's volumes.
message EmpyrionScenarioInstallRequest {
string job_id = 1; // controller-generated; echoed in result + log lines
string instance_id = 2; // target empyrion instance (must live on this agent)
string workshop_id = 3; // Steam workshop item ID (e.g. "3041847672" for RE2)
string scenario_name = 4; // folder name under Content/Scenarios; sanitized by controller
string app_id = 5; // empyrion app id; defaults to 530870 if empty
// Steam credentials for the workshop_download_item call. Steam policy
// (~2023+) requires a logged-in account for most app workshops, even
// for public/free items — anonymous returns "Access Denied". Forwarded
// from the controller's encrypted steam_credentials store. Same shape
// as UpdateRequest's steam_username/steam_password fields.
string steam_username = 6;
string steam_password = 7;
}
message EmpyrionScenarioInstallResult {
string job_id = 1;
bool ok = 2;
string error = 3; // empty when ok
string scenario_name = 4;
}
// EmpyrionDiscoveriesRequest queries the empyrion server's global.db
// (SQLite) for what a specific player has discovered. The agent mounts
// the instance's saves volume into a tiny alpine+sqlite sidecar, runs
// a join query against DiscoveredPOIs / Entities / Playfields /
// SolarSystems / LoginLogoff, and returns the rows.
//
// `mode = "personal"` returns POIs the named player personally
// discovered. `mode = "faction"` returns POIs anyone in their faction
// has discovered (the in-game shared-map view). Default: personal.
message EmpyrionDiscoveriesRequest {
string instance_id = 1;
string steam_id = 2; // 17-digit SteamID64 of the player
string mode = 3; // "personal" | "faction"
}
message DiscoveredPOI {
string solar_system = 1;
string playfield = 2;
string poi_name = 3;
int32 poi_type = 4; // Entities.etype enum
int64 game_time = 5; // ticks since save start
string discoverer_name = 6; // who first discovered (when known)
string discoverer_steam_id = 7;
int32 faction_group = 8;
int32 faction_id = 9;
}
message EmpyrionDiscoveriesResult {
bool ok = 1;
string error = 2;
repeated DiscoveredPOI pois = 3;
string mode = 4; // echoed back from request
string steam_id = 5;
string player_name = 6; // last-known name from LoginLogoff
}
// EmpyrionPlayerSummaryRequest pulls everything we know about a single
// player from the empyrion savegame DB: stats, login sessions, current
// inventory snapshot. Used by the public /me page on AMP-Monitor.
message EmpyrionPlayerSummaryRequest {
string instance_id = 1;
string steam_id = 2; // 17-digit SteamID64
int32 max_login_rows = 3; // limit on session history; default 50
}
message PlayerStatsRow {
int64 killed_enemies = 1;
int64 killed_allied = 2;
int64 killed_animals = 3;
int64 killed_drones = 4;
int64 killed_players = 5;
int64 killed_allied_players = 6;
int64 died = 7;
int64 score = 8;
int64 blocks_placed = 9;
int64 blocks_digged = 10;
int64 walked_meters = 11;
int64 jetpack_meters = 12;
int64 hv_meters = 13;
int64 sv_meters = 14;
int64 cv_meters = 15;
double playtime = 16; // hours per the schema
double light_years = 17; // travly
double astronomical_units = 18; // travau
}
message PlayerLoginRow {
int64 login_ticks = 1;
int64 logoff_ticks = 2;
string player_name = 3;
int64 build_nr = 4;
string ip = 5;
string os = 6;
string gfx = 7;
string cpu = 8;
}
message PlayerInventoryItem {
int32 item_id = 1;
int32 count = 2;
}
message EmpyrionPlayerSummaryResult {
bool ok = 1;
string error = 2;
string steam_id = 3;
string player_name = 4;
int64 entity_id = 5;
int32 faction_group = 6;
int32 faction_id = 7;
// Aggregate stats. nil when player has no PlayerStatistics row yet
// (very new player).
PlayerStatsRow stats = 8;
// Recent login sessions, newest first.
repeated PlayerLoginRow sessions = 9;
// Most recent inventory snapshot. May be empty for players without
// a recorded snapshot.
int64 inventory_gametime = 10;
repeated PlayerInventoryItem inventory = 11;
// Last-known position.
string last_playfield = 12;
double last_x = 13;
double last_y = 14;
double last_z = 15;
int64 credits = 16;
}
// ---- Region Medic (7DTD .7rg region-corruption detect + heal) ----
// RegionScanRequest asks the agent to scan an instance's active-world Region
// directory for corruption evidence (error_backup salvage files + invalid
// region files).
message RegionScanRequest {
string instance_id = 1;
}
// AffectedRegionMsg is one region flagged by a scan.
message AffectedRegionMsg {
string region = 1; // canonical id, e.g. "r.-2.0"
int32 error_backups = 2; // salvage files whose chunks fall in this region
bool file_present = 3; // r.X.Z.7rg exists on disk
bool file_corrupt = 4; // region file failed structural validation
int32 bad_chunks = 5; // count of invalid chunks if validated
}
message RegionScanResult {
int32 error_backups = 1; // total salvage files found
repeated AffectedRegionMsg affected = 2;
string region_dir = 3; // resolved active Region dir (container path)
Error error = 4;
}
// RegionHealRequest asks the agent to heal one region from the newest clean
// backup. The agent stops the instance (graceful save first), swaps the region
// file in place, quarantines the region's error_backups, then restarts (if
// restart=true).
message RegionHealRequest {
string instance_id = 1;
string region = 2; // "r.-2.0"
bool restart = 3; // restart the instance after healing
}
message RegionHealResult {
string region = 1;
string source_backup = 2; // backup id the clean copy came from
int64 bytes_written = 3;
int32 quarantined = 4; // error_backup files moved aside
string snapshot_path = 5; // where the corrupt original was saved
bool healed = 6;
Error error = 7;
}
// ---- Durable config re-render (no container recreate) ----
// InstanceRenderConfigRequest asks the agent to re-render the module's
// declared config_files (templates) into the instance's host DataPath,
// WITHOUT recreating or restarting the container. For modules whose
// rendered output is bind-mounted into the container (7dtd's
// serverconfig.xml.rendered), the entrypoint picks the new content up on
// the instance's NEXT boot — this is the durable, no-recreate config path.
//
// config_values is the FULL desired value map (the controller sends its
// merged DB row, not a delta), mirroring what InstanceCreate would render.
message InstanceRenderConfigRequest {
string instance_id = 1;
map<string, string> config_values = 2;
}
message InstanceRenderConfigResult {
repeated string rendered_files = 1; // config_file paths written (manifest-relative)
string error = 2;
}
// SeedModsRequest asks an agent to clone the Mods/ folder of src_instance_id
// (the cluster master) onto instance_id (a member), live — no container
// recreate. Per-server state (RefugeBot homes.json) is excluded from the copy
// and the member's own homes.json is preserved. The member keeps running on
// its currently-loaded mods; the new files take effect on its NEXT restart.
// Both instances must live on the same agent (the sidecar mounts local volumes).
message SeedModsRequest {
string instance_id = 1; // destination member to receive the mods
string src_instance_id = 2; // source cluster master to copy mods from
}
message SeedModsResult {
string instance_id = 1;
bool ok = 2;
string error = 3;
int32 mod_count = 4; // number of mods present on the member after the copy
}
// ---- Update-available check (WI-14) ----
//
// CHECK-ONLY: this RPC never triggers an update or restart. The agent
// reads the installed buildid from the Steam appmanifest_<appid>.acf in
// the instance volume and fetches the latest buildid for the instance's
// branch via a one-shot `steamcmd +app_info_print` sidecar (cached per
// app_id for 15 minutes agent-side).
message UpdateCheckRequest {
string instance_id = 1;
// provider_id selects the module update_provider (steamcmd kind) whose
// app_id/beta define what "installed vs latest" means. Empty = same
// default the Update RPC uses (first provider in the manifest).
string provider_id = 2;
// refresh forces a fresh steamcmd app_info fetch, bypassing the
// agent-side 15-minute cache.
bool refresh = 3;
}
message UpdateCheckResult {
string instance_id = 1;
string app_id = 2;
string branch = 3; // Steam branch compared ("public" when no -beta)
string installed_buildid = 4; // from appmanifest ACF; "" if unreadable
string latest_buildid = 5; // from app_info branches map; "" if fetch failed
bool update_available = 6; // installed != latest (both non-empty)
bool cached = 7; // latest came from the agent-side cache
string error = 8; // non-empty on failure; other fields best-effort
}
+125
View File
@@ -0,0 +1,125 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.0
// - protoc (unknown)
// source: panel/v1/agent.proto
package panelv1
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
Agent_Connect_FullMethodName = "/panel.v1.Agent/Connect"
)
// AgentClient is the client API for Agent service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
//
// Agent is the gRPC service a Target agent dials on the Controller.
// A single persistent bidirectional stream carries all control-plane
// traffic between Controller and Target: commands downstream, state
// and events upstream.
type AgentClient interface {
Connect(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[AgentEnvelope, ControllerEnvelope], error)
}
type agentClient struct {
cc grpc.ClientConnInterface
}
func NewAgentClient(cc grpc.ClientConnInterface) AgentClient {
return &agentClient{cc}
}
func (c *agentClient) Connect(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[AgentEnvelope, ControllerEnvelope], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &Agent_ServiceDesc.Streams[0], Agent_Connect_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[AgentEnvelope, ControllerEnvelope]{ClientStream: stream}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Agent_ConnectClient = grpc.BidiStreamingClient[AgentEnvelope, ControllerEnvelope]
// AgentServer is the server API for Agent service.
// All implementations must embed UnimplementedAgentServer
// for forward compatibility.
//
// Agent is the gRPC service a Target agent dials on the Controller.
// A single persistent bidirectional stream carries all control-plane
// traffic between Controller and Target: commands downstream, state
// and events upstream.
type AgentServer interface {
Connect(grpc.BidiStreamingServer[AgentEnvelope, ControllerEnvelope]) error
mustEmbedUnimplementedAgentServer()
}
// UnimplementedAgentServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedAgentServer struct{}
func (UnimplementedAgentServer) Connect(grpc.BidiStreamingServer[AgentEnvelope, ControllerEnvelope]) error {
return status.Error(codes.Unimplemented, "method Connect not implemented")
}
func (UnimplementedAgentServer) mustEmbedUnimplementedAgentServer() {}
func (UnimplementedAgentServer) testEmbeddedByValue() {}
// UnsafeAgentServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to AgentServer will
// result in compilation errors.
type UnsafeAgentServer interface {
mustEmbedUnimplementedAgentServer()
}
func RegisterAgentServer(s grpc.ServiceRegistrar, srv AgentServer) {
// If the following call panics, it indicates UnimplementedAgentServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&Agent_ServiceDesc, srv)
}
func _Agent_Connect_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(AgentServer).Connect(&grpc.GenericServerStream[AgentEnvelope, ControllerEnvelope]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Agent_ConnectServer = grpc.BidiStreamingServer[AgentEnvelope, ControllerEnvelope]
// Agent_ServiceDesc is the grpc.ServiceDesc for Agent service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Agent_ServiceDesc = grpc.ServiceDesc{
ServiceName: "panel.v1.Agent",
HandlerType: (*AgentServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "Connect",
Handler: _Agent_Connect_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "panel/v1/agent.proto",
}
+424
View File
@@ -0,0 +1,424 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.11
// protoc (unknown)
// source: panel/v1/common.proto
package panelv1
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect"
sync "sync"
unsafe "unsafe"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// RunMode selects how a game instance is launched on a Target.
type RunMode int32
const (
RunMode_RUN_MODE_UNSPECIFIED RunMode = 0
RunMode_RUN_MODE_DOCKER RunMode = 1
RunMode_RUN_MODE_HOST RunMode = 2
)
// Enum value maps for RunMode.
var (
RunMode_name = map[int32]string{
0: "RUN_MODE_UNSPECIFIED",
1: "RUN_MODE_DOCKER",
2: "RUN_MODE_HOST",
}
RunMode_value = map[string]int32{
"RUN_MODE_UNSPECIFIED": 0,
"RUN_MODE_DOCKER": 1,
"RUN_MODE_HOST": 2,
}
)
func (x RunMode) Enum() *RunMode {
p := new(RunMode)
*p = x
return p
}
func (x RunMode) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (RunMode) Descriptor() protoreflect.EnumDescriptor {
return file_panel_v1_common_proto_enumTypes[0].Descriptor()
}
func (RunMode) Type() protoreflect.EnumType {
return &file_panel_v1_common_proto_enumTypes[0]
}
func (x RunMode) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use RunMode.Descriptor instead.
func (RunMode) EnumDescriptor() ([]byte, []int) {
return file_panel_v1_common_proto_rawDescGZIP(), []int{0}
}
// InstanceStatus is the coarse lifecycle state of an instance.
type InstanceStatus int32
const (
InstanceStatus_INSTANCE_STATUS_UNSPECIFIED InstanceStatus = 0
InstanceStatus_INSTANCE_STATUS_STOPPED InstanceStatus = 1
InstanceStatus_INSTANCE_STATUS_STARTING InstanceStatus = 2
InstanceStatus_INSTANCE_STATUS_RUNNING InstanceStatus = 3
InstanceStatus_INSTANCE_STATUS_STOPPING InstanceStatus = 4
InstanceStatus_INSTANCE_STATUS_CRASHED InstanceStatus = 5
InstanceStatus_INSTANCE_STATUS_UPDATING InstanceStatus = 6
)
// Enum value maps for InstanceStatus.
var (
InstanceStatus_name = map[int32]string{
0: "INSTANCE_STATUS_UNSPECIFIED",
1: "INSTANCE_STATUS_STOPPED",
2: "INSTANCE_STATUS_STARTING",
3: "INSTANCE_STATUS_RUNNING",
4: "INSTANCE_STATUS_STOPPING",
5: "INSTANCE_STATUS_CRASHED",
6: "INSTANCE_STATUS_UPDATING",
}
InstanceStatus_value = map[string]int32{
"INSTANCE_STATUS_UNSPECIFIED": 0,
"INSTANCE_STATUS_STOPPED": 1,
"INSTANCE_STATUS_STARTING": 2,
"INSTANCE_STATUS_RUNNING": 3,
"INSTANCE_STATUS_STOPPING": 4,
"INSTANCE_STATUS_CRASHED": 5,
"INSTANCE_STATUS_UPDATING": 6,
}
)
func (x InstanceStatus) Enum() *InstanceStatus {
p := new(InstanceStatus)
*p = x
return p
}
func (x InstanceStatus) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (InstanceStatus) Descriptor() protoreflect.EnumDescriptor {
return file_panel_v1_common_proto_enumTypes[1].Descriptor()
}
func (InstanceStatus) Type() protoreflect.EnumType {
return &file_panel_v1_common_proto_enumTypes[1]
}
func (x InstanceStatus) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use InstanceStatus.Descriptor instead.
func (InstanceStatus) EnumDescriptor() ([]byte, []int) {
return file_panel_v1_common_proto_rawDescGZIP(), []int{1}
}
// ResourceLimits caps an instance's CPU / memory / disk usage.
type ResourceLimits struct {
state protoimpl.MessageState `protogen:"open.v1"`
CpuShares uint32 `protobuf:"varint,1,opt,name=cpu_shares,json=cpuShares,proto3" json:"cpu_shares,omitempty"` // relative weight, 1024 = 1 core equivalent
MemBytes uint64 `protobuf:"varint,2,opt,name=mem_bytes,json=memBytes,proto3" json:"mem_bytes,omitempty"` // hard memory ceiling
DiskBytes uint64 `protobuf:"varint,3,opt,name=disk_bytes,json=diskBytes,proto3" json:"disk_bytes,omitempty"` // data dir quota, 0 = unlimited
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ResourceLimits) Reset() {
*x = ResourceLimits{}
mi := &file_panel_v1_common_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ResourceLimits) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ResourceLimits) ProtoMessage() {}
func (x *ResourceLimits) ProtoReflect() protoreflect.Message {
mi := &file_panel_v1_common_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ResourceLimits.ProtoReflect.Descriptor instead.
func (*ResourceLimits) Descriptor() ([]byte, []int) {
return file_panel_v1_common_proto_rawDescGZIP(), []int{0}
}
func (x *ResourceLimits) GetCpuShares() uint32 {
if x != nil {
return x.CpuShares
}
return 0
}
func (x *ResourceLimits) GetMemBytes() uint64 {
if x != nil {
return x.MemBytes
}
return 0
}
func (x *ResourceLimits) GetDiskBytes() uint64 {
if x != nil {
return x.DiskBytes
}
return 0
}
// PortMap declares a port exposed by an instance.
type PortMap struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // logical name: "game", "rcon", "query"
Proto string `protobuf:"bytes,2,opt,name=proto,proto3" json:"proto,omitempty"` // "tcp" or "udp"
ContainerPort uint32 `protobuf:"varint,3,opt,name=container_port,json=containerPort,proto3" json:"container_port,omitempty"` // port inside the sandbox
HostPort uint32 `protobuf:"varint,4,opt,name=host_port,json=hostPort,proto3" json:"host_port,omitempty"` // port on the Target host (0 = same as container_port)
Internal bool `protobuf:"varint,5,opt,name=internal,proto3" json:"internal,omitempty"` // true = not exposed outside the Target
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *PortMap) Reset() {
*x = PortMap{}
mi := &file_panel_v1_common_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *PortMap) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PortMap) ProtoMessage() {}
func (x *PortMap) ProtoReflect() protoreflect.Message {
mi := &file_panel_v1_common_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use PortMap.ProtoReflect.Descriptor instead.
func (*PortMap) Descriptor() ([]byte, []int) {
return file_panel_v1_common_proto_rawDescGZIP(), []int{1}
}
func (x *PortMap) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *PortMap) GetProto() string {
if x != nil {
return x.Proto
}
return ""
}
func (x *PortMap) GetContainerPort() uint32 {
if x != nil {
return x.ContainerPort
}
return 0
}
func (x *PortMap) GetHostPort() uint32 {
if x != nil {
return x.HostPort
}
return 0
}
func (x *PortMap) GetInternal() bool {
if x != nil {
return x.Internal
}
return false
}
// Error is a structured error payload carried on the wire.
type Error struct {
state protoimpl.MessageState `protogen:"open.v1"`
Code string `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"` // short machine-readable code
Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` // human-readable message
At *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=at,proto3" json:"at,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *Error) Reset() {
*x = Error{}
mi := &file_panel_v1_common_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *Error) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Error) ProtoMessage() {}
func (x *Error) ProtoReflect() protoreflect.Message {
mi := &file_panel_v1_common_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Error.ProtoReflect.Descriptor instead.
func (*Error) Descriptor() ([]byte, []int) {
return file_panel_v1_common_proto_rawDescGZIP(), []int{2}
}
func (x *Error) GetCode() string {
if x != nil {
return x.Code
}
return ""
}
func (x *Error) GetMessage() string {
if x != nil {
return x.Message
}
return ""
}
func (x *Error) GetAt() *timestamppb.Timestamp {
if x != nil {
return x.At
}
return nil
}
var File_panel_v1_common_proto protoreflect.FileDescriptor
const file_panel_v1_common_proto_rawDesc = "" +
"\n" +
"\x15panel/v1/common.proto\x12\bpanel.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"k\n" +
"\x0eResourceLimits\x12\x1d\n" +
"\n" +
"cpu_shares\x18\x01 \x01(\rR\tcpuShares\x12\x1b\n" +
"\tmem_bytes\x18\x02 \x01(\x04R\bmemBytes\x12\x1d\n" +
"\n" +
"disk_bytes\x18\x03 \x01(\x04R\tdiskBytes\"\x93\x01\n" +
"\aPortMap\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x14\n" +
"\x05proto\x18\x02 \x01(\tR\x05proto\x12%\n" +
"\x0econtainer_port\x18\x03 \x01(\rR\rcontainerPort\x12\x1b\n" +
"\thost_port\x18\x04 \x01(\rR\bhostPort\x12\x1a\n" +
"\binternal\x18\x05 \x01(\bR\binternal\"a\n" +
"\x05Error\x12\x12\n" +
"\x04code\x18\x01 \x01(\tR\x04code\x12\x18\n" +
"\amessage\x18\x02 \x01(\tR\amessage\x12*\n" +
"\x02at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\x02at*K\n" +
"\aRunMode\x12\x18\n" +
"\x14RUN_MODE_UNSPECIFIED\x10\x00\x12\x13\n" +
"\x0fRUN_MODE_DOCKER\x10\x01\x12\x11\n" +
"\rRUN_MODE_HOST\x10\x02*\xe2\x01\n" +
"\x0eInstanceStatus\x12\x1f\n" +
"\x1bINSTANCE_STATUS_UNSPECIFIED\x10\x00\x12\x1b\n" +
"\x17INSTANCE_STATUS_STOPPED\x10\x01\x12\x1c\n" +
"\x18INSTANCE_STATUS_STARTING\x10\x02\x12\x1b\n" +
"\x17INSTANCE_STATUS_RUNNING\x10\x03\x12\x1c\n" +
"\x18INSTANCE_STATUS_STOPPING\x10\x04\x12\x1b\n" +
"\x17INSTANCE_STATUS_CRASHED\x10\x05\x12\x1c\n" +
"\x18INSTANCE_STATUS_UPDATING\x10\x06B2Z0github.com/dbledeez/panel/proto/panel/v1;panelv1b\x06proto3"
var (
file_panel_v1_common_proto_rawDescOnce sync.Once
file_panel_v1_common_proto_rawDescData []byte
)
func file_panel_v1_common_proto_rawDescGZIP() []byte {
file_panel_v1_common_proto_rawDescOnce.Do(func() {
file_panel_v1_common_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_panel_v1_common_proto_rawDesc), len(file_panel_v1_common_proto_rawDesc)))
})
return file_panel_v1_common_proto_rawDescData
}
var file_panel_v1_common_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_panel_v1_common_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_panel_v1_common_proto_goTypes = []any{
(RunMode)(0), // 0: panel.v1.RunMode
(InstanceStatus)(0), // 1: panel.v1.InstanceStatus
(*ResourceLimits)(nil), // 2: panel.v1.ResourceLimits
(*PortMap)(nil), // 3: panel.v1.PortMap
(*Error)(nil), // 4: panel.v1.Error
(*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp
}
var file_panel_v1_common_proto_depIdxs = []int32{
5, // 0: panel.v1.Error.at:type_name -> google.protobuf.Timestamp
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_panel_v1_common_proto_init() }
func file_panel_v1_common_proto_init() {
if File_panel_v1_common_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_panel_v1_common_proto_rawDesc), len(file_panel_v1_common_proto_rawDesc)),
NumEnums: 2,
NumMessages: 3,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_panel_v1_common_proto_goTypes,
DependencyIndexes: file_panel_v1_common_proto_depIdxs,
EnumInfos: file_panel_v1_common_proto_enumTypes,
MessageInfos: file_panel_v1_common_proto_msgTypes,
}.Build()
File_panel_v1_common_proto = out.File
file_panel_v1_common_proto_goTypes = nil
file_panel_v1_common_proto_depIdxs = nil
}
+48
View File
@@ -0,0 +1,48 @@
syntax = "proto3";
package panel.v1;
option go_package = "github.com/dbledeez/panel/proto/panel/v1;panelv1";
import "google/protobuf/timestamp.proto";
// RunMode selects how a game instance is launched on a Target.
enum RunMode {
RUN_MODE_UNSPECIFIED = 0;
RUN_MODE_DOCKER = 1;
RUN_MODE_HOST = 2;
}
// InstanceStatus is the coarse lifecycle state of an instance.
enum InstanceStatus {
INSTANCE_STATUS_UNSPECIFIED = 0;
INSTANCE_STATUS_STOPPED = 1;
INSTANCE_STATUS_STARTING = 2;
INSTANCE_STATUS_RUNNING = 3;
INSTANCE_STATUS_STOPPING = 4;
INSTANCE_STATUS_CRASHED = 5;
INSTANCE_STATUS_UPDATING = 6;
}
// ResourceLimits caps an instance's CPU / memory / disk usage.
message ResourceLimits {
uint32 cpu_shares = 1; // relative weight, 1024 = 1 core equivalent
uint64 mem_bytes = 2; // hard memory ceiling
uint64 disk_bytes = 3; // data dir quota, 0 = unlimited
}
// PortMap declares a port exposed by an instance.
message PortMap {
string name = 1; // logical name: "game", "rcon", "query"
string proto = 2; // "tcp" or "udp"
uint32 container_port = 3; // port inside the sandbox
uint32 host_port = 4; // port on the Target host (0 = same as container_port)
bool internal = 5; // true = not exposed outside the Target
}
// Error is a structured error payload carried on the wire.
message Error {
string code = 1; // short machine-readable code
string message = 2; // human-readable message
google.protobuf.Timestamp at = 3;
}
File diff suppressed because it is too large Load Diff
+179
View File
@@ -0,0 +1,179 @@
syntax = "proto3";
package panel.v1;
option go_package = "github.com/dbledeez/panel/proto/panel/v1;panelv1";
import "google/protobuf/timestamp.proto";
import "panel/v1/agent.proto";
import "panel/v1/common.proto";
// Panel is the operator-facing API — what panelctl, the web UI, and future
// automation talk to. The controller fulfils these by forwarding commands
// to the appropriate Target agent over the Agent bidi stream.
service Panel {
rpc ListAgents(ListAgentsRequest) returns (ListAgentsResponse);
rpc ListInstances(ListInstancesRequest) returns (ListInstancesResponse);
rpc CreateInstance(CreateInstanceRequest) returns (CreateInstanceResponse);
rpc StartInstance(StartInstanceRequest) returns (StartInstanceResponse);
rpc StopInstance(StopInstanceRequest) returns (StopInstanceResponse);
rpc DeleteInstance(DeleteInstanceRequest) returns (DeleteInstanceResponse);
rpc ExecRCON(ExecRCONRequest) returns (ExecRCONResponse);
rpc UpdateInstance(UpdateInstanceRequest) returns (UpdateInstanceResponse);
rpc CreateBackup(CreateBackupRequest) returns (CreateBackupResponse);
rpc RestoreBackup(RestoreBackupRequest) returns (RestoreBackupResponse);
rpc StreamEvents(StreamEventsRequest) returns (stream Event);
}
message ListAgentsRequest {}
message ListAgentsResponse {
repeated AgentInfo agents = 1;
}
message AgentInfo {
string agent_id = 1;
string version = 2;
string host_os = 3;
string host_arch = 4;
string hostname = 5;
google.protobuf.Timestamp connected_at = 6;
google.protobuf.Timestamp last_seen = 7;
}
message CreateInstanceRequest {
string agent_id = 1;
string instance_id = 2;
string module_id = 3;
string version = 4;
RunMode run_mode = 5;
string data_path = 6; // optional; agent will derive if empty
map<string, string> config_values = 7; // user-supplied overrides, module-validated
repeated PortMap ports = 8; // port overrides
}
message CreateInstanceResponse {
string instance_id = 1;
string correlation_id = 2;
}
message StartInstanceRequest {
string agent_id = 1;
string instance_id = 2;
}
message StartInstanceResponse {
string correlation_id = 1;
}
message StopInstanceRequest {
string agent_id = 1;
string instance_id = 2;
uint32 grace_seconds = 3;
}
message StopInstanceResponse {
string correlation_id = 1;
}
message DeleteInstanceRequest {
string agent_id = 1;
string instance_id = 2;
uint32 grace_seconds = 3;
bool purge_volumes = 4;
}
message DeleteInstanceResponse {
string correlation_id = 1;
}
// ExecRCON sends an ad-hoc RCON command to the running instance and waits
// synchronously for the agent's response. The controller correlates the
// request → response round-trip by correlation_id.
message ExecRCONRequest {
string agent_id = 1;
string instance_id = 2;
string command = 3;
uint32 timeout_ms = 4; // optional; default 10000
}
message ExecRCONResponse {
string output = 1;
string error = 2;
string correlation_id = 3;
}
// UpdateInstance kicks off a module-declared update provider against the
// target instance. The agent runs it asynchronously; progress streams as
// LogLines on the event bus.
message UpdateInstanceRequest {
string agent_id = 1;
string instance_id = 2;
string provider_id = 3; // empty = first provider
}
message UpdateInstanceResponse {
bool accepted = 1;
string error = 2;
string provider_id = 3;
string provider_kind = 4;
string correlation_id = 5;
}
message CreateBackupRequest {
string agent_id = 1;
string instance_id = 2;
string description = 3;
}
message CreateBackupResponse {
string backup_id = 1;
string path = 2;
int64 size_bytes = 3;
string error = 4;
}
message RestoreBackupRequest {
string agent_id = 1;
string instance_id = 2;
string backup_id = 3;
}
message RestoreBackupResponse {
int64 bytes_restored = 1;
string error = 2;
}
message InstanceInfo {
string instance_id = 1;
string agent_id = 2;
string module_id = 3;
string module_version = 4;
RunMode run_mode = 5;
InstanceStatus status = 6;
int32 last_exit_code = 7;
string data_path = 8;
string detail = 9;
google.protobuf.Timestamp created_at = 10;
google.protobuf.Timestamp updated_at = 11;
}
message ListInstancesRequest {
string agent_id = 1; // optional filter by agent
}
message ListInstancesResponse {
repeated InstanceInfo instances = 1;
}
// StreamEvents — server streaming of the live event bus. Filters are
// optional AND-ed; empty strings = no filter on that field.
message StreamEventsRequest {
string instance_id = 1;
string agent_id = 2;
}
// Event is the controller's normalized form of everything an agent sends
// up that isn't a one-shot command result. It wraps the original payload
// and annotates it with the agent_id for filtering + display.
message Event {
string agent_id = 1;
google.protobuf.Timestamp at = 2;
oneof payload {
InstanceStateUpdate instance_state = 10;
AppStateUpdate app_state = 11;
PlayerEvent player = 12;
LogLine log = 13;
InstanceStatsUpdate instance_stats = 14;
}
}
+513
View File
@@ -0,0 +1,513 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.0
// - protoc (unknown)
// source: panel/v1/panel.proto
package panelv1
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion9
const (
Panel_ListAgents_FullMethodName = "/panel.v1.Panel/ListAgents"
Panel_ListInstances_FullMethodName = "/panel.v1.Panel/ListInstances"
Panel_CreateInstance_FullMethodName = "/panel.v1.Panel/CreateInstance"
Panel_StartInstance_FullMethodName = "/panel.v1.Panel/StartInstance"
Panel_StopInstance_FullMethodName = "/panel.v1.Panel/StopInstance"
Panel_DeleteInstance_FullMethodName = "/panel.v1.Panel/DeleteInstance"
Panel_ExecRCON_FullMethodName = "/panel.v1.Panel/ExecRCON"
Panel_UpdateInstance_FullMethodName = "/panel.v1.Panel/UpdateInstance"
Panel_CreateBackup_FullMethodName = "/panel.v1.Panel/CreateBackup"
Panel_RestoreBackup_FullMethodName = "/panel.v1.Panel/RestoreBackup"
Panel_StreamEvents_FullMethodName = "/panel.v1.Panel/StreamEvents"
)
// PanelClient is the client API for Panel service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
//
// Panel is the operator-facing API — what panelctl, the web UI, and future
// automation talk to. The controller fulfils these by forwarding commands
// to the appropriate Target agent over the Agent bidi stream.
type PanelClient interface {
ListAgents(ctx context.Context, in *ListAgentsRequest, opts ...grpc.CallOption) (*ListAgentsResponse, error)
ListInstances(ctx context.Context, in *ListInstancesRequest, opts ...grpc.CallOption) (*ListInstancesResponse, error)
CreateInstance(ctx context.Context, in *CreateInstanceRequest, opts ...grpc.CallOption) (*CreateInstanceResponse, error)
StartInstance(ctx context.Context, in *StartInstanceRequest, opts ...grpc.CallOption) (*StartInstanceResponse, error)
StopInstance(ctx context.Context, in *StopInstanceRequest, opts ...grpc.CallOption) (*StopInstanceResponse, error)
DeleteInstance(ctx context.Context, in *DeleteInstanceRequest, opts ...grpc.CallOption) (*DeleteInstanceResponse, error)
ExecRCON(ctx context.Context, in *ExecRCONRequest, opts ...grpc.CallOption) (*ExecRCONResponse, error)
UpdateInstance(ctx context.Context, in *UpdateInstanceRequest, opts ...grpc.CallOption) (*UpdateInstanceResponse, error)
CreateBackup(ctx context.Context, in *CreateBackupRequest, opts ...grpc.CallOption) (*CreateBackupResponse, error)
RestoreBackup(ctx context.Context, in *RestoreBackupRequest, opts ...grpc.CallOption) (*RestoreBackupResponse, error)
StreamEvents(ctx context.Context, in *StreamEventsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Event], error)
}
type panelClient struct {
cc grpc.ClientConnInterface
}
func NewPanelClient(cc grpc.ClientConnInterface) PanelClient {
return &panelClient{cc}
}
func (c *panelClient) ListAgents(ctx context.Context, in *ListAgentsRequest, opts ...grpc.CallOption) (*ListAgentsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListAgentsResponse)
err := c.cc.Invoke(ctx, Panel_ListAgents_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *panelClient) ListInstances(ctx context.Context, in *ListInstancesRequest, opts ...grpc.CallOption) (*ListInstancesResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListInstancesResponse)
err := c.cc.Invoke(ctx, Panel_ListInstances_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *panelClient) CreateInstance(ctx context.Context, in *CreateInstanceRequest, opts ...grpc.CallOption) (*CreateInstanceResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CreateInstanceResponse)
err := c.cc.Invoke(ctx, Panel_CreateInstance_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *panelClient) StartInstance(ctx context.Context, in *StartInstanceRequest, opts ...grpc.CallOption) (*StartInstanceResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(StartInstanceResponse)
err := c.cc.Invoke(ctx, Panel_StartInstance_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *panelClient) StopInstance(ctx context.Context, in *StopInstanceRequest, opts ...grpc.CallOption) (*StopInstanceResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(StopInstanceResponse)
err := c.cc.Invoke(ctx, Panel_StopInstance_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *panelClient) DeleteInstance(ctx context.Context, in *DeleteInstanceRequest, opts ...grpc.CallOption) (*DeleteInstanceResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(DeleteInstanceResponse)
err := c.cc.Invoke(ctx, Panel_DeleteInstance_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *panelClient) ExecRCON(ctx context.Context, in *ExecRCONRequest, opts ...grpc.CallOption) (*ExecRCONResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ExecRCONResponse)
err := c.cc.Invoke(ctx, Panel_ExecRCON_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *panelClient) UpdateInstance(ctx context.Context, in *UpdateInstanceRequest, opts ...grpc.CallOption) (*UpdateInstanceResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(UpdateInstanceResponse)
err := c.cc.Invoke(ctx, Panel_UpdateInstance_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *panelClient) CreateBackup(ctx context.Context, in *CreateBackupRequest, opts ...grpc.CallOption) (*CreateBackupResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CreateBackupResponse)
err := c.cc.Invoke(ctx, Panel_CreateBackup_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *panelClient) RestoreBackup(ctx context.Context, in *RestoreBackupRequest, opts ...grpc.CallOption) (*RestoreBackupResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(RestoreBackupResponse)
err := c.cc.Invoke(ctx, Panel_RestoreBackup_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *panelClient) StreamEvents(ctx context.Context, in *StreamEventsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Event], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &Panel_ServiceDesc.Streams[0], Panel_StreamEvents_FullMethodName, cOpts...)
if err != nil {
return nil, err
}
x := &grpc.GenericClientStream[StreamEventsRequest, Event]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Panel_StreamEventsClient = grpc.ServerStreamingClient[Event]
// PanelServer is the server API for Panel service.
// All implementations must embed UnimplementedPanelServer
// for forward compatibility.
//
// Panel is the operator-facing API — what panelctl, the web UI, and future
// automation talk to. The controller fulfils these by forwarding commands
// to the appropriate Target agent over the Agent bidi stream.
type PanelServer interface {
ListAgents(context.Context, *ListAgentsRequest) (*ListAgentsResponse, error)
ListInstances(context.Context, *ListInstancesRequest) (*ListInstancesResponse, error)
CreateInstance(context.Context, *CreateInstanceRequest) (*CreateInstanceResponse, error)
StartInstance(context.Context, *StartInstanceRequest) (*StartInstanceResponse, error)
StopInstance(context.Context, *StopInstanceRequest) (*StopInstanceResponse, error)
DeleteInstance(context.Context, *DeleteInstanceRequest) (*DeleteInstanceResponse, error)
ExecRCON(context.Context, *ExecRCONRequest) (*ExecRCONResponse, error)
UpdateInstance(context.Context, *UpdateInstanceRequest) (*UpdateInstanceResponse, error)
CreateBackup(context.Context, *CreateBackupRequest) (*CreateBackupResponse, error)
RestoreBackup(context.Context, *RestoreBackupRequest) (*RestoreBackupResponse, error)
StreamEvents(*StreamEventsRequest, grpc.ServerStreamingServer[Event]) error
mustEmbedUnimplementedPanelServer()
}
// UnimplementedPanelServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedPanelServer struct{}
func (UnimplementedPanelServer) ListAgents(context.Context, *ListAgentsRequest) (*ListAgentsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListAgents not implemented")
}
func (UnimplementedPanelServer) ListInstances(context.Context, *ListInstancesRequest) (*ListInstancesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListInstances not implemented")
}
func (UnimplementedPanelServer) CreateInstance(context.Context, *CreateInstanceRequest) (*CreateInstanceResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreateInstance not implemented")
}
func (UnimplementedPanelServer) StartInstance(context.Context, *StartInstanceRequest) (*StartInstanceResponse, error) {
return nil, status.Error(codes.Unimplemented, "method StartInstance not implemented")
}
func (UnimplementedPanelServer) StopInstance(context.Context, *StopInstanceRequest) (*StopInstanceResponse, error) {
return nil, status.Error(codes.Unimplemented, "method StopInstance not implemented")
}
func (UnimplementedPanelServer) DeleteInstance(context.Context, *DeleteInstanceRequest) (*DeleteInstanceResponse, error) {
return nil, status.Error(codes.Unimplemented, "method DeleteInstance not implemented")
}
func (UnimplementedPanelServer) ExecRCON(context.Context, *ExecRCONRequest) (*ExecRCONResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ExecRCON not implemented")
}
func (UnimplementedPanelServer) UpdateInstance(context.Context, *UpdateInstanceRequest) (*UpdateInstanceResponse, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateInstance not implemented")
}
func (UnimplementedPanelServer) CreateBackup(context.Context, *CreateBackupRequest) (*CreateBackupResponse, error) {
return nil, status.Error(codes.Unimplemented, "method CreateBackup not implemented")
}
func (UnimplementedPanelServer) RestoreBackup(context.Context, *RestoreBackupRequest) (*RestoreBackupResponse, error) {
return nil, status.Error(codes.Unimplemented, "method RestoreBackup not implemented")
}
func (UnimplementedPanelServer) StreamEvents(*StreamEventsRequest, grpc.ServerStreamingServer[Event]) error {
return status.Error(codes.Unimplemented, "method StreamEvents not implemented")
}
func (UnimplementedPanelServer) mustEmbedUnimplementedPanelServer() {}
func (UnimplementedPanelServer) testEmbeddedByValue() {}
// UnsafePanelServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to PanelServer will
// result in compilation errors.
type UnsafePanelServer interface {
mustEmbedUnimplementedPanelServer()
}
func RegisterPanelServer(s grpc.ServiceRegistrar, srv PanelServer) {
// If the following call panics, it indicates UnimplementedPanelServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&Panel_ServiceDesc, srv)
}
func _Panel_ListAgents_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListAgentsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PanelServer).ListAgents(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Panel_ListAgents_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PanelServer).ListAgents(ctx, req.(*ListAgentsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Panel_ListInstances_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListInstancesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PanelServer).ListInstances(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Panel_ListInstances_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PanelServer).ListInstances(ctx, req.(*ListInstancesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Panel_CreateInstance_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreateInstanceRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PanelServer).CreateInstance(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Panel_CreateInstance_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PanelServer).CreateInstance(ctx, req.(*CreateInstanceRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Panel_StartInstance_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StartInstanceRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PanelServer).StartInstance(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Panel_StartInstance_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PanelServer).StartInstance(ctx, req.(*StartInstanceRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Panel_StopInstance_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StopInstanceRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PanelServer).StopInstance(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Panel_StopInstance_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PanelServer).StopInstance(ctx, req.(*StopInstanceRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Panel_DeleteInstance_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeleteInstanceRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PanelServer).DeleteInstance(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Panel_DeleteInstance_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PanelServer).DeleteInstance(ctx, req.(*DeleteInstanceRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Panel_ExecRCON_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ExecRCONRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PanelServer).ExecRCON(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Panel_ExecRCON_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PanelServer).ExecRCON(ctx, req.(*ExecRCONRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Panel_UpdateInstance_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateInstanceRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PanelServer).UpdateInstance(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Panel_UpdateInstance_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PanelServer).UpdateInstance(ctx, req.(*UpdateInstanceRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Panel_CreateBackup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreateBackupRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PanelServer).CreateBackup(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Panel_CreateBackup_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PanelServer).CreateBackup(ctx, req.(*CreateBackupRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Panel_RestoreBackup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RestoreBackupRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(PanelServer).RestoreBackup(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Panel_RestoreBackup_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(PanelServer).RestoreBackup(ctx, req.(*RestoreBackupRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Panel_StreamEvents_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(StreamEventsRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(PanelServer).StreamEvents(m, &grpc.GenericServerStream[StreamEventsRequest, Event]{ServerStream: stream})
}
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type Panel_StreamEventsServer = grpc.ServerStreamingServer[Event]
// Panel_ServiceDesc is the grpc.ServiceDesc for Panel service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Panel_ServiceDesc = grpc.ServiceDesc{
ServiceName: "panel.v1.Panel",
HandlerType: (*PanelServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "ListAgents",
Handler: _Panel_ListAgents_Handler,
},
{
MethodName: "ListInstances",
Handler: _Panel_ListInstances_Handler,
},
{
MethodName: "CreateInstance",
Handler: _Panel_CreateInstance_Handler,
},
{
MethodName: "StartInstance",
Handler: _Panel_StartInstance_Handler,
},
{
MethodName: "StopInstance",
Handler: _Panel_StopInstance_Handler,
},
{
MethodName: "DeleteInstance",
Handler: _Panel_DeleteInstance_Handler,
},
{
MethodName: "ExecRCON",
Handler: _Panel_ExecRCON_Handler,
},
{
MethodName: "UpdateInstance",
Handler: _Panel_UpdateInstance_Handler,
},
{
MethodName: "CreateBackup",
Handler: _Panel_CreateBackup_Handler,
},
{
MethodName: "RestoreBackup",
Handler: _Panel_RestoreBackup_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "StreamEvents",
Handler: _Panel_StreamEvents_Handler,
ServerStreams: true,
},
},
Metadata: "panel/v1/panel.proto",
}