Documentation
¶
Overview ¶
Package kiwivm is a Go client for the KiwiVM REST API used by BandwagonHost / 64clouds VPS instances.
The API is per-instance: every call authenticates with a (veid, api_key) pair that identifies one VPS. There is no account-level endpoint, so managing a fleet means holding one credential pair per box. See package github.com/lroolle/bwg-cli/internal/config for the fleet model built on top of this client.
Read-only is a capability, not a flag ¶
Every endpoint is registered in Ops with a Risk classification. A client built with ReadOnly refuses every non-read operation before any HTTP request is made:
c := kiwivm.New(veid, key, kiwivm.ReadOnly()) _, err := c.Restart(ctx) // *ReadOnlyError, no network I/O info, err := c.ServiceInfo(ctx) // fine
The guard lives here rather than in the CLI so that SDK consumers, the CLI, and the MCP server all inherit it. Client.Can reports whether an operation would be permitted, which is how callers build an honest tool list for an agent instead of advertising tools that will fail.
Errors ¶
KiwiVM answers HTTP 200 with an "error" field on failure. Non-zero values surface as *APIError; use IsAuth, IsLocked and IsRateLimited to branch. Transport failures and 5xx surface as *TransportError, which IsTransient reports true for — those deserve a retry, never a credential change.
Example (ErrorHandling) ¶
Errors are classified so callers can branch without matching text.
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/lroolle/bwg-cli/kiwivm"
)
func main() {
c := kiwivm.New("1347645", "private_key", kiwivm.WithTimeout(10*time.Second))
info, err := c.ServiceInfo(context.Background())
switch {
case kiwivm.IsAuth(err):
log.Fatal("the veid/api_key pair does not work")
case kiwivm.IsLocked(err):
log.Print("the VPS is busy with another task; the error carries progress")
case kiwivm.IsTransient(err):
log.Print("temporary — retry unchanged")
case err != nil:
log.Fatal(err)
default:
fmt.Println(info.Hostname)
}
}
Output:
Example (ReadOnly) ¶
The common case: read the fleet-relevant numbers off one VPS without any possibility of changing it.
package main
import (
"context"
"fmt"
"os"
"github.com/lroolle/bwg-cli/kiwivm"
)
func main() {
c := kiwivm.New(os.Getenv("BWG_VEID"), os.Getenv("BWG_API_KEY"),
kiwivm.ReadOnly())
// A mutation is refused before any HTTP request happens.
if err := c.Restart(context.Background()); kiwivm.IsReadOnly(err) {
fmt.Println("restart refused: client is read-only")
}
}
Output: restart refused: client is read-only
Index ¶
- Constants
- Variables
- func IsAuth(err error) bool
- func IsLocked(err error) bool
- func IsMissingParam(err error) bool
- func IsRateLimited(err error) bool
- func IsReadOnly(err error) bool
- func IsTransient(err error) bool
- type APIError
- type AuditEntry
- type AuditLog
- type AvailableOS
- type Backup
- type BackupList
- type Bandwidth
- type Bool
- type Client
- func (c *Client) AddIPv6(ctx context.Context) (*IPv6Added, error)
- func (c *Client) AssignPrivateIP(ctx context.Context, ip string) (*PrivateIPsAssigned, error)
- func (c *Client) AuditLog(ctx context.Context) (*AuditLog, error)
- func (c *Client) AvailableOS(ctx context.Context) (*AvailableOS, error)
- func (c *Client) AvailablePrivateIPs(ctx context.Context) (*PrivateIPsAvailable, error)
- func (c *Client) Backups(ctx context.Context) (*BackupList, error)
- func (c *Client) BaseURL() string
- func (c *Client) Can(endpoint string) (bool, error)
- func (c *Client) CloneFromExternalServer(ctx context.Context, ip, sshPort, rootPassword string) error
- func (c *Client) CopyBackupToSnapshot(ctx context.Context, backupToken string) error
- func (c *Client) CreateSnapshot(ctx context.Context, description string) (*SnapshotCreated, error)
- func (c *Client) DeleteIPv6(ctx context.Context, subnet string) error
- func (c *Client) DeletePrivateIP(ctx context.Context, ip string) error
- func (c *Client) DeleteSnapshot(ctx context.Context, fileName string) error
- func (c *Client) ExportSnapshot(ctx context.Context, fileName string) (*SnapshotExport, error)
- func (c *Client) ImportSnapshot(ctx context.Context, sourceVeid, sourceToken string) error
- func (c *Client) IsReadOnly() bool
- func (c *Client) Kill(ctx context.Context) error
- func (c *Client) LiveServiceInfo(ctx context.Context) (*LiveServiceInfo, error)
- func (c *Client) MigrateLocations(ctx context.Context) (*MigrateLocations, error)
- func (c *Client) MountISO(ctx context.Context, iso string) error
- func (c *Client) NotificationPreferences(ctx context.Context) (*NotificationPreferences, error)
- func (c *Client) PolicyViolations(ctx context.Context) (*PolicyViolations, error)
- func (c *Client) RateLimitStatus(ctx context.Context) (*RateLimit, error)
- func (c *Client) Raw(ctx context.Context, endpoint string, params url.Values) (json.RawMessage, error)
- func (c *Client) RawUsageStats(ctx context.Context) (*UsageStats, error)
- func (c *Client) ReinstallOS(ctx context.Context, os string) (*ReinstallResult, error)
- func (c *Client) ResetRootPassword(ctx context.Context) (*RootPassword, error)
- func (c *Client) ResolvePolicyViolation(ctx context.Context, recordID string) error
- func (c *Client) Restart(ctx context.Context) error
- func (c *Client) RestoreSnapshot(ctx context.Context, fileName string) error
- func (c *Client) SSHKeys(ctx context.Context) (*SSHKeys, error)
- func (c *Client) ScriptExec(ctx context.Context, script string) (*ScriptExec, error)
- func (c *Client) ServiceInfo(ctx context.Context) (*ServiceInfo, error)
- func (c *Client) SetHostname(ctx context.Context, hostname string) error
- func (c *Client) SetNotificationPreferences(ctx context.Context, prefs map[string]bool) (*NotificationUpdate, error)
- func (c *Client) SetPTR(ctx context.Context, ip, ptr string) error
- func (c *Client) SetSnapshotSticky(ctx context.Context, fileName string, sticky bool) error
- func (c *Client) ShellCD(ctx context.Context, currentDir, newDir string) (*ShellCD, error)
- func (c *Client) ShellExec(ctx context.Context, command string) (*ShellExec, error)
- func (c *Client) Snapshots(ctx context.Context) (*SnapshotList, error)
- func (c *Client) Start(ctx context.Context) error
- func (c *Client) StartMigration(ctx context.Context, location string) (*MigrateStarted, error)
- func (c *Client) Stop(ctx context.Context) error
- func (c *Client) SuspensionDetails(ctx context.Context) (*SuspensionDetails, error)
- func (c *Client) UnmountISO(ctx context.Context) error
- func (c *Client) Unsuspend(ctx context.Context, recordID string) error
- func (c *Client) UpdateSSHKeys(ctx context.Context, keys []string) error
- func (c *Client) UsageGraphs(ctx context.Context) (map[string]any, error)
- func (c *Client) VEID() string
- type IPv6Added
- type Int
- type LiveServiceInfo
- type LockingInfo
- type Map
- type MigrateLocations
- type MigrateStarted
- type NotificationPreference
- type NotificationPreferences
- type NotificationUpdate
- type Nullroute
- type Nullroutes
- type Op
- type Option
- type PolicyViolation
- type PolicyViolations
- type PrivateIPsAssigned
- type PrivateIPsAvailable
- type RateLimit
- type ReadOnlyError
- type ReinstallResult
- type Risk
- type RootPassword
- type SSHKeys
- type ScriptExec
- type ServiceInfo
- type ShellCD
- type ShellExec
- type Snapshot
- type SnapshotCreated
- type SnapshotExport
- type SnapshotList
- type Strings
- type Suspension
- type SuspensionDetails
- type TransportError
- type UsageSample
- type UsageStats
Examples ¶
Constants ¶
const ( // CodeOK is the success value of the "error" field. CodeOK = 0 // CodeMissingParam is returned when a required parameter is absent, // including the veid itself. CodeMissingParam = 700001 // CodeAuthFailure is returned for a bad veid/api_key pair. It does // NOT distinguish "wrong key" from "wrong veid" — both look the same. CodeAuthFailure = 700005 // CodeLocked is returned while the VPS is busy with another task // (snapshot, reinstall, migration). CodeLocked = 788888 )
KiwiVM error codes observed in the wild. The API answers HTTP 200 and puts the real outcome in the "error" field, so these are the only reliable signal for branching.
const DefaultBaseURL = "https://api.64clouds.com/v1"
DefaultBaseURL is the public KiwiVM API root.
const DefaultTimeout = 45 * time.Second
DefaultTimeout covers the slowest documented call, getLiveServiceInfo, which the API says may take up to 15 seconds.
Variables ¶
var ErrReadOnly = errors.New("client is read-only")
ErrReadOnly matches any ReadOnlyError via errors.Is.
var Ops = map[string]Op{ "start": {"start", RiskWrite, "Start the VPS", ""}, "stop": {"stop", RiskWrite, "Stop the VPS", ""}, "restart": {"restart", RiskWrite, "Reboot the VPS", ""}, "kill": {"kill", RiskDestructive, "Force-stop a VPS that will not stop normally", "unsaved data in the guest is lost"}, "getServiceInfo": {"getServiceInfo", RiskRead, "Plan, location, network and quota for the VPS", ""}, "getLiveServiceInfo": {"getLiveServiceInfo", RiskRead, "Service info plus live guest status (slow: up to 15s)", ""}, "getRateLimitStatus": {"getRateLimitStatus", RiskRead, "Remaining API rate-limit points", ""}, "getAvailableOS": {"getAvailableOS", RiskRead, "Installed OS and installable templates", ""}, "reinstallOS": {"reinstallOS", RiskDestructive, "Reinstall the operating system", "every byte on the VPS disk is erased"}, "getSshKeys": {"getSshKeys", RiskRead, "SSH keys in Hypervisor Vault and the billing portal", ""}, "updateSshKeys": {"updateSshKeys", RiskWrite, "Replace the per-VM SSH keys used by reinstallOS", ""}, "resetRootPassword": {"resetRootPassword", RiskDestructive, "Generate and set a new root password", "the current root password becomes unrecoverable and anything using it is locked out"}, "getUsageGraphs": {"getUsageGraphs", RiskRead, "Legacy usage graphs (obsolete; use getRawUsageStats)", ""}, "getRawUsageStats": {"getRawUsageStats", RiskRead, "Per-interval CPU, network and disk usage samples", ""}, "getAuditLog": {"getAuditLog", RiskRead, "KiwiVM control-panel audit log", ""}, "setHostname": {"setHostname", RiskWrite, "Set the VPS hostname", ""}, "setPTR": {"setPTR", RiskWrite, "Set the PTR (rDNS) record for an IP", ""}, "iso/mount": {"iso/mount", RiskDestructive, "Boot from an ISO image instead of primary storage", "changes boot media; a VPS left on a wrong ISO is unreachable until corrected"}, "iso/unmount": {"iso/unmount", RiskDestructive, "Remove the ISO and boot from primary storage", "changes boot media; requires a full shutdown and restart"}, "basicShell/cd": {"basicShell/cd", RiskRead, "Resolve a directory change inside the VPS", ""}, "basicShell/exec": {"basicShell/exec", RiskDestructive, "Run a shell command inside the VPS as root (synchronous)", "arbitrary root code execution; effects are entirely up to the command"}, "shellScript/exec": {"shellScript/exec", RiskDestructive, "Run a shell script inside the VPS as root (asynchronous)", "arbitrary root code execution; runs detached with no way to recall it"}, "snapshot/list": {"snapshot/list", RiskRead, "List snapshots", ""}, "snapshot/create": {"snapshot/create", RiskWrite, "Create a snapshot", ""}, "snapshot/toggleSticky": {"snapshot/toggleSticky", RiskWrite, "Protect a snapshot from automatic purge, or stop protecting it", ""}, "snapshot/export": {"snapshot/export", RiskWrite, "Mint a transfer token for a snapshot", ""}, "snapshot/import": {"snapshot/import", RiskWrite, "Import a snapshot from another instance", ""}, "snapshot/delete": {"snapshot/delete", RiskDestructive, "Delete a snapshot", "the snapshot cannot be recovered"}, "snapshot/restore": {"snapshot/restore", RiskDestructive, "Restore a snapshot over the VPS", "current VPS data is overwritten by the snapshot"}, "backup/list": {"backup/list", RiskRead, "List automatic backups", ""}, "backup/copyToSnapshot": {"backup/copyToSnapshot", RiskWrite, "Copy an automatic backup into a restorable snapshot", ""}, "ipv6/add": {"ipv6/add", RiskWrite, "Allocate an IPv6 /64 subnet", ""}, "ipv6/delete": {"ipv6/delete", RiskDestructive, "Release an IPv6 /64 subnet", "the subnet returns to the pool and will not be reissued to you"}, "privateIp/getAvailableIps": {"privateIp/getAvailableIps", RiskRead, "List assignable private IPv4 addresses", ""}, "privateIp/assign": {"privateIp/assign", RiskWrite, "Assign a private IPv4 address", ""}, "privateIp/delete": {"privateIp/delete", RiskWrite, "Remove a private IPv4 address", ""}, "migrate/getLocations": {"migrate/getLocations", RiskRead, "List migration target locations", ""}, "migrate/start": {"migrate/start", RiskDestructive, "Migrate the VPS to another location", "every IPv4 address is replaced; the old addresses are not recoverable"}, "cloneFromExternalServer": {"cloneFromExternalServer", RiskDestructive, "Clone a remote server into this VPS (OpenVZ only)", "the current VPS contents are replaced by the remote server's"}, "getSuspensionDetails": {"getSuspensionDetails", RiskRead, "Suspensions, abuse points and evidence", ""}, "getPolicyViolations": {"getPolicyViolations", RiskRead, "Active policy violations awaiting resolution", ""}, "unsuspend": {"unsuspend", RiskDestructive, "Clear an abuse case and unsuspend the VPS", "consumes a one-time case resolution that cannot be re-opened through the API"}, "resolvePolicyViolation": {"resolvePolicyViolation", RiskDestructive, "Mark a policy violation resolved", "consumes a one-time case resolution that cannot be re-opened through the API"}, "kiwivm/getNotificationPreferences": {"kiwivm/getNotificationPreferences", RiskRead, "Email notification preferences", ""}, "kiwivm/setNotificationPreferences": {"kiwivm/setNotificationPreferences", RiskWrite, "Change email notification preferences", ""}, }
Ops is the registry of every endpoint this package can call, keyed by endpoint path. It is the single source of truth for risk: the client gate, the CLI confirmation prompts, the MCP tool list, and the generated docs all read from here.
Functions ¶
func IsAuth ¶
IsAuth reports whether err is KiwiVM rejecting the credentials. Note that KiwiVM returns the same code for a wrong api_key and a wrong veid, so a true result means "this pair does not work", not "this key is bad".
func IsLocked ¶
IsLocked reports whether the VPS is busy with another task. The error's Locking field carries progress when KiwiVM supplies it.
func IsMissingParam ¶
IsMissingParam reports whether a required parameter was absent.
func IsRateLimited ¶
IsRateLimited reports whether KiwiVM dropped the request for rate pressure. KiwiVM signals this with HTTP 429 rather than an error code; call Client.RateLimitStatus to see the remaining budget.
func IsReadOnly ¶
IsReadOnly reports whether err is a read-only refusal.
func IsTransient ¶
IsTransient reports whether err is worth retrying unchanged. Rate limiting counts: the budget refills on its own.
Types ¶
type APIError ¶
type APIError struct {
// Op is the endpoint that failed.
Op string `json:"op"`
// Code is the value of the response's "error" field.
Code int `json:"code"`
// Message is KiwiVM's human-readable detail, when present.
Message string `json:"message,omitempty"`
// Additional is the response's additionalErrorInfo field.
Additional string `json:"additionalErrorInfo,omitempty"`
// Locking carries task progress when the VPS is locked.
Locking *LockingInfo `json:"additionalLockingInfo,omitempty"`
}
APIError is a non-zero "error" field in a KiwiVM response.
func APIErrorFrom ¶
APIErrorFrom extracts the *APIError from err, if there is one.
type AuditEntry ¶
type AuditEntry struct {
Timestamp Int `json:"timestamp"`
RequestorIPv4 Int `json:"requestor_ipv4"`
Type Int `json:"type"`
Summary string `json:"summary"`
}
AuditEntry is one KiwiVM control-panel event.
func (AuditEntry) RequestorIP ¶
func (a AuditEntry) RequestorIP() string
RequestorIP renders the requestor address, which KiwiVM encodes as a 32-bit integer rather than a string. Returns "" when out of range.
type AuditLog ¶
type AuditLog struct {
LogEntries []AuditEntry `json:"log_entries"`
}
AuditLog is the response from getAuditLog.
type AvailableOS ¶
type AvailableOS struct {
Installed string `json:"installed"`
Templates Strings `json:"templates"`
}
AvailableOS is the installed OS plus the installable templates.
type Backup ¶
type Backup struct {
// Token identifies the backup for backup/copyToSnapshot. KiwiVM
// usually supplies it as the map key rather than a field, so prefer
// [BackupList.Sorted], which fills it in.
Token string `json:"backupToken"`
Size Int `json:"size"`
OS string `json:"os"`
MD5 string `json:"md5"`
Timestamp Int `json:"timestamp"`
}
Backup is one automatic backup.
type BackupList ¶
BackupList is the response from backup/list. KiwiVM returns backups as an object keyed by token.
func (*BackupList) Sorted ¶
func (b *BackupList) Sorted() []Backup
Sorted returns the backups newest first, each with Token populated.
type Bandwidth ¶
type Bandwidth struct {
// Used, Total and Free are bytes with the multiplier applied.
Used int64 `json:"used"`
Total int64 `json:"total"`
Free int64 `json:"free"`
// Percent is Used/Total as 0-100. The multiplier scales both sides
// equally, so this is the one figure that is right regardless of
// how the multiplier is interpreted.
Percent float64 `json:"percent"`
// Multiplier is the location's bandwidth accounting coefficient.
Multiplier int64 `json:"multiplier"`
// ResetsAt is when the counter rolls over; zero if unknown.
ResetsAt time.Time `json:"resetsAt"`
}
Bandwidth is the monthly transfer picture, multiplier applied.
type Bool ¶
type Bool bool
Bool is a JSON boolean that KiwiVM may send as 0/1 or "0"/"1".
func (Bool) MarshalJSON ¶
func (*Bool) UnmarshalJSON ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client talks to the KiwiVM API for exactly one VPS. It is safe for concurrent use.
func New ¶
New builds a client for one VPS. veid and apiKey come as a pair from the KiwiVM panel; the same api_key with the wrong veid authenticates as neither.
func (*Client) AssignPrivateIP ¶
AssignPrivateIP assigns a private IPv4 address. An empty ip lets KiwiVM pick one.
func (*Client) AvailableOS ¶
func (c *Client) AvailableOS(ctx context.Context) (*AvailableOS, error)
AvailableOS returns the installed OS and the installable templates.
func (*Client) AvailablePrivateIPs ¶
func (c *Client) AvailablePrivateIPs(ctx context.Context) (*PrivateIPsAvailable, error)
AvailablePrivateIPs lists private IPv4 addresses free to assign.
func (*Client) Backups ¶
func (c *Client) Backups(ctx context.Context) (*BackupList, error)
Backups lists the automatic backups KiwiVM holds.
func (*Client) Can ¶
Can reports whether the named endpoint would be permitted on this client, and why not when it would not. Callers building a tool list for an agent should use this rather than advertising operations that are certain to fail.
Example ¶
Ops is the registry every surface reads. Building a menu of what a client may do means asking it, not guessing.
package main
import (
"fmt"
"github.com/lroolle/bwg-cli/kiwivm"
)
func main() {
c := kiwivm.New("1347645", "private_key", kiwivm.ReadOnly())
for _, endpoint := range []string{"getServiceInfo", "snapshot/create", "reinstallOS"} {
op, _ := kiwivm.LookupOp(endpoint)
allowed, _ := c.Can(endpoint)
fmt.Printf("%-16s %-12s allowed=%v\n", endpoint, op.Risk, allowed)
}
}
Output: getServiceInfo read allowed=true snapshot/create write allowed=false reinstallOS destructive allowed=false
func (*Client) CloneFromExternalServer ¶
func (c *Client) CloneFromExternalServer(ctx context.Context, ip, sshPort, rootPassword string) error
CloneFromExternalServer replaces this VPS with a copy of a remote server reachable over SSH. OpenVZ only.
func (*Client) CopyBackupToSnapshot ¶
CopyBackupToSnapshot turns an automatic backup into a restorable snapshot. It does not restore anything by itself.
func (*Client) CreateSnapshot ¶
CreateSnapshot starts a snapshot task. description is optional. KiwiVM locks the VPS for the duration and emails on completion.
func (*Client) DeleteIPv6 ¶
DeleteIPv6 releases an IPv6 /64 subnet back to the pool.
func (*Client) DeletePrivateIP ¶
DeletePrivateIP removes a private IPv4 address from the VPS.
func (*Client) DeleteSnapshot ¶
DeleteSnapshot removes a snapshot by its fileName. It cannot be recovered.
func (*Client) ExportSnapshot ¶
ExportSnapshot mints a token another instance can import with Client.ImportSnapshot.
func (*Client) ImportSnapshot ¶
ImportSnapshot pulls a snapshot from another instance. Both the source VEID and the token come from Client.ExportSnapshot run against that instance.
func (*Client) IsReadOnly ¶
IsReadOnly reports whether this client refuses non-read operations.
func (*Client) Kill ¶
Kill force-stops a VPS that will not stop normally. Unsaved data in the guest is lost.
func (*Client) LiveServiceInfo ¶
func (c *Client) LiveServiceInfo(ctx context.Context) (*LiveServiceInfo, error)
LiveServiceInfo returns Client.ServiceInfo plus guest-reported state. KiwiVM documents this call as taking up to 15 seconds.
func (*Client) MigrateLocations ¶
func (c *Client) MigrateLocations(ctx context.Context) (*MigrateLocations, error)
MigrateLocations lists the locations this VPS can move to.
func (*Client) MountISO ¶
MountISO sets the VPS to boot from an ISO image. The VPS must be fully shut down first and restarted afterwards.
func (*Client) NotificationPreferences ¶
func (c *Client) NotificationPreferences(ctx context.Context) (*NotificationPreferences, error)
NotificationPreferences returns the email notification settings.
func (*Client) PolicyViolations ¶
func (c *Client) PolicyViolations(ctx context.Context) (*PolicyViolations, error)
PolicyViolations returns violations awaiting resolution.
func (*Client) RateLimitStatus ¶
RateLimitStatus returns the remaining API budget. It costs a point itself, so polling it in a loop is self-defeating.
func (*Client) Raw ¶
func (c *Client) Raw(ctx context.Context, endpoint string, params url.Values) (json.RawMessage, error)
Raw calls any registered endpoint and returns KiwiVM's response body verbatim, for callers that need a field this package does not model yet. veid and api_key are added automatically.
The risk gate still applies: a read-only client refuses a non-read endpoint here exactly as it does through the typed methods. An unregistered endpoint is rejected, so Raw cannot be used to reach something whose risk nobody has classified.
Non-zero "error" values are returned as *APIError, the same as elsewhere; the body is returned only on success.
func (*Client) RawUsageStats ¶
func (c *Client) RawUsageStats(ctx context.Context) (*UsageStats, error)
RawUsageStats returns the sampled CPU, network and disk series.
func (*Client) ReinstallOS ¶
ReinstallOS reinstalls the operating system, erasing the disk. os must be one of the templates from Client.AvailableOS.
The returned root password is shown once and is not retrievable afterwards — callers must surface it immediately.
func (*Client) ResetRootPassword ¶
func (c *Client) ResetRootPassword(ctx context.Context) (*RootPassword, error)
ResetRootPassword generates a new root password and returns it. The previous password is unrecoverable.
func (*Client) ResolvePolicyViolation ¶
ResolvePolicyViolation marks a violation resolved, which is what stops the pending suspension. Only cases where PolicyViolation.APIResolvable reports true can be resolved this way.
func (*Client) RestoreSnapshot ¶
RestoreSnapshot overwrites the VPS with a snapshot.
func (*Client) ScriptExec ¶
ScriptExec runs a shell script inside the VPS as root, detached, and returns the name of the log file it writes to.
func (*Client) ServiceInfo ¶
func (c *Client) ServiceInfo(ctx context.Context) (*ServiceInfo, error)
ServiceInfo returns plan, location, network and quota state.
func (*Client) SetHostname ¶
SetHostname sets the VPS hostname recorded by KiwiVM. It does not change the hostname inside a running guest.
func (*Client) SetNotificationPreferences ¶
func (c *Client) SetNotificationPreferences(ctx context.Context, prefs map[string]bool) (*NotificationUpdate, error)
SetNotificationPreferences enables or disables notifications by preference ID. IDs come from Client.NotificationPreferences.
KiwiVM silently ignores unknown IDs, so compare the returned Updated map against what was submitted rather than assuming success.
func (*Client) SetPTR ¶
SetPTR sets the PTR (rDNS) record for an IP. Check ServiceInfo.RDNSAPIAvailable first: not every plan allows it.
func (*Client) SetSnapshotSticky ¶
SetSnapshotSticky protects a snapshot from automatic purge, or stops protecting it.
func (*Client) ShellCD ¶
ShellCD resolves a directory change inside the VPS, for building an interactive shell on top of Client.ShellExec. It changes nothing.
func (*Client) ShellExec ¶
ShellExec runs a command inside the VPS as root and waits for it.
KiwiVM reuses the response envelope for the command's result, so a non-zero exit status arrives as ShellExec.ExitStatus rather than as a Go error. The error return covers transport and permission failures only — always check ExitStatus too.
func (*Client) Snapshots ¶
func (c *Client) Snapshots(ctx context.Context) (*SnapshotList, error)
Snapshots lists stored snapshots.
func (*Client) StartMigration ¶
StartMigration moves the VPS to another location. Every IPv4 address is replaced; the old ones do not come back.
func (*Client) Stop ¶
Stop shuts the VPS down. Client.Start undoes it.
func (*Client) SuspensionDetails ¶
func (c *Client) SuspensionDetails(ctx context.Context) (*SuspensionDetails, error)
SuspensionDetails returns suspensions, abuse points and evidence.
func (*Client) UnmountISO ¶
UnmountISO restores booting from primary storage. The VPS must be fully shut down first and restarted afterwards.
func (*Client) Unsuspend ¶
Unsuspend clears an abuse case and lifts the suspension. Only cases where Suspension.APIResolvable reports true can be cleared this way.
func (*Client) UpdateSSHKeys ¶
UpdateSSHKeys replaces the per-VM keys held in Hypervisor Vault. These shadow the account-level keys entirely during a reinstall. Passing no keys clears them, which restores the account-level keys.
func (*Client) UsageGraphs ¶
UsageGraphs returns the legacy graph payload. KiwiVM marks this obsolete; use Client.RawUsageStats. The shape is undocumented, so it comes back as decoded JSON rather than a typed struct.
type IPv6Added ¶
type IPv6Added struct {
AssignedSubnet string `json:"assigned_subnet"`
}
IPv6Added is the response from ipv6/add.
type Int ¶
type Int int64
Int is a JSON number that KiwiVM may send as a string.
func (Int) MarshalJSON ¶
func (*Int) UnmarshalJSON ¶
type LiveServiceInfo ¶
type LiveServiceInfo struct {
ServiceInfo
IsCPUThrottled Bool `json:"is_cpu_throttled"`
SSHPort Int `json:"ssh_port"`
// OpenVZ
VzStatus map[string]any `json:"vz_status,omitempty"`
VzQuota map[string]any `json:"vz_quota,omitempty"`
// KVM
VeStatus string `json:"ve_status,omitempty"`
VeMac1 string `json:"ve_mac1,omitempty"`
VeUsedDiskSpaceB Int `json:"ve_used_disk_space_b,omitempty"`
VeDiskQuotaGB Int `json:"ve_disk_quota_gb,omitempty"`
IsDiskThrottled Bool `json:"is_disk_throttled,omitempty"`
LiveHostname string `json:"live_hostname,omitempty"`
LoadAverage string `json:"load_average,omitempty"`
MemAvailableKB Int `json:"mem_available_kb,omitempty"`
SwapTotalKB Int `json:"swap_total_kb,omitempty"`
SwapAvailableKB Int `json:"swap_available_kb,omitempty"`
ScreendumpPNGBase64 string `json:"screendump_png_base64,omitempty"`
}
LiveServiceInfo is ServiceInfo plus guest-reported state, from getLiveServiceInfo. Which hypervisor-specific group is populated depends on VMType.
func (*LiveServiceInfo) DiskTotalBytes ¶
func (l *LiveServiceInfo) DiskTotalBytes() (int64, bool)
DiskTotalBytes returns the disk quota, preferring the live figure and falling back to the plan.
func (*LiveServiceInfo) DiskUsedBytes ¶
func (l *LiveServiceInfo) DiskUsedBytes() (int64, bool)
DiskUsedBytes returns occupied disk space, and false when the hypervisor does not report it. OpenVZ exposes this through vz_quota in 1 KiB blocks rather than a dedicated byte field.
func (*LiveServiceInfo) MemUsedBytes ¶
func (l *LiveServiceInfo) MemUsedBytes() (int64, bool)
MemUsedBytes returns used RAM, and false when the hypervisor does not report enough to compute it.
func (*LiveServiceInfo) Running ¶
func (l *LiveServiceInfo) Running() bool
Running reports whether the guest is up.
func (*LiveServiceInfo) State ¶
func (l *LiveServiceInfo) State() string
State returns a normalized power state: "running", "stopped", "starting", or "unknown". KVM reports it directly; OpenVZ has no state field, so a populated vz_status is the only signal.
type LockingInfo ¶
type LockingInfo struct {
LastStatusUpdateSecondsAgo int `json:"last_status_update_s_ago"`
CompletedPercent int `json:"completed_percent"`
FriendlyProgressMessage string `json:"friendly_progress_message"`
}
LockingInfo reports progress of the task currently holding the VPS.
type Map ¶
Map is a JSON object that KiwiVM may serialize as `[]` when empty, because that is what PHP's json_encode does to an empty associative array. Decoding into a plain map fails on those responses.
func (Map[V]) Keys ¶
Keys returns the map's keys sorted, so output and tests do not depend on Go's map iteration order.
func (*Map[V]) UnmarshalJSON ¶
type MigrateLocations ¶
type MigrateLocations struct {
CurrentLocation string `json:"currentLocation"`
Locations Strings `json:"locations"`
Descriptions Map[string] `json:"descriptions"`
DataTransferMultipliers Map[Int] `json:"dataTransferMultipliers"`
}
MigrateLocations is the response from migrate/getLocations.
type MigrateStarted ¶
type MigrateStarted struct {
NotificationEmail string `json:"notificationEmail"`
NewIPs Strings `json:"newIps"`
}
MigrateStarted is the response from migrate/start.
type NotificationPreference ¶
type NotificationPreference struct {
FriendlyDescription string `json:"friendly_description"`
IsEnabled Bool `json:"is_enabled"`
ChangedTimestamp Int `json:"changed_timestamp"`
SValue string `json:"s_value"`
}
NotificationPreference is one email notification setting.
type NotificationPreferences ¶
type NotificationPreferences struct {
EmailPreferences Map[Map[NotificationPreference]] `json:"email_preferences"`
NotificationEmail string `json:"notificationEmail"`
}
NotificationPreferences is the response from kiwivm/getNotificationPreferences. KiwiVM groups preferences by category, so EmailPreferences is category -> preference ID -> value.
func (*NotificationPreferences) Flat ¶
func (n *NotificationPreferences) Flat() map[string]NotificationPreference
Flat returns every preference keyed by its ID, dropping the category grouping that only matters for panel layout.
type NotificationUpdate ¶
type NotificationUpdate struct {
Submitted Map[Int] `json:"submitted_email_preferences"`
Updated Map[Int] `json:"updated_email_preferences"`
Descriptions Map[string] `json:"friendly_descriptions"`
}
NotificationUpdate is the response from kiwivm/setNotificationPreferences. Updated lists only what actually changed, which can be narrower than Submitted.
type Nullroute ¶
type Nullroute struct {
// IP is filled in from the map key.
IP string `json:"ip"`
Timestamp Int `json:"nullroute_timestamp"`
DurationS Int `json:"nullroute_duration_s"`
// Log is the raw packet dump KiwiVM captured. It can be long.
Log string `json:"log,omitempty"`
}
Nullroute describes an IP nullrouted during a (D)DoS attack.
type Nullroutes ¶
Nullroutes maps an IP address to its nullroute detail. KiwiVM sends `[]` when nothing is nullrouted, an object when something is, and occasionally a bare array of IPs carrying no detail.
func (*Nullroutes) UnmarshalJSON ¶
func (n *Nullroutes) UnmarshalJSON(b []byte) error
type Op ¶
type Op struct {
// Endpoint is the path under the API base URL, e.g. "snapshot/list".
Endpoint string `json:"endpoint"`
// Risk is what this endpoint can do. See [Risk].
Risk Risk `json:"risk"`
// Summary is a one-line description, reused by the CLI and the MCP
// tool list so all three surfaces describe an endpoint identically.
Summary string `json:"summary"`
// Why explains a Destructive classification. Empty for other risks.
Why string `json:"why,omitempty"`
}
Op describes one KiwiVM endpoint.
type Option ¶
type Option func(*Client)
Option configures a Client.
func ReadOnly ¶
func ReadOnly() Option
ReadOnly builds a client that refuses every operation classified above RiskRead, returning *ReadOnlyError before any request is made. This is the strongest guarantee the package offers: it cannot be undone on an existing client, and it applies to the SDK, the CLI and the MCP server alike.
func WithBaseURL ¶
WithBaseURL overrides the API root. Used by tests and by anyone proxying the API.
func WithHTTPClient ¶
WithHTTPClient supplies the HTTP client to use.
func WithTimeout ¶
WithTimeout sets the per-request timeout. Ignored when the caller also supplies their own client via WithHTTPClient after this.
type PolicyViolation ¶
type PolicyViolation struct {
RecordID Int `json:"record_id"`
Timestamp Int `json:"timestamp"`
SuspendAt Int `json:"suspend_at"`
Flag string `json:"flag"`
IsSoft Bool `json:"is_soft"`
AbusePoints Int `json:"abuse_points"`
EvidenceData string `json:"evidence_data"`
}
PolicyViolation is one unresolved policy violation.
func (PolicyViolation) APIResolvable ¶
func (p PolicyViolation) APIResolvable() bool
APIResolvable reports whether Client.ResolvePolicyViolation can clear this case, or whether it needs a support ticket.
func (PolicyViolation) SuspendsAt ¶
func (p PolicyViolation) SuspendsAt() (time.Time, bool)
SuspendsAt returns the deadline after which the service is suspended, and whether one was given.
type PolicyViolations ¶
type PolicyViolations struct {
TotalAbusePoints Int `json:"total_abuse_points"`
MaxAbusePoints Int `json:"max_abuse_points"`
PolicyViolations []PolicyViolation `json:"policy_violations,omitempty"`
}
PolicyViolations is the response from getPolicyViolations.
type PrivateIPsAssigned ¶
type PrivateIPsAssigned struct {
AssignedIPs Strings `json:"assigned_ips"`
}
PrivateIPsAssigned is the response from privateIp/assign.
type PrivateIPsAvailable ¶
type PrivateIPsAvailable struct {
AvailableIPs Strings `json:"available_ips"`
}
PrivateIPsAvailable is the response from privateIp/getAvailableIps.
type RateLimit ¶
type RateLimit struct {
Remaining15Min Int `json:"remaining_points_15min"`
Remaining24H Int `json:"remaining_points_24h"`
}
RateLimit is the remaining API budget from getRateLimitStatus.
type ReadOnlyError ¶
type ReadOnlyError struct {
Op Op
}
ReadOnlyError is returned instead of performing a non-read operation on a client built with ReadOnly. No HTTP request is made.
func (*ReadOnlyError) Error ¶
func (e *ReadOnlyError) Error() string
func (*ReadOnlyError) Is ¶
func (e *ReadOnlyError) Is(target error) bool
Is lets errors.Is(err, ErrReadOnly) match a *ReadOnlyError.
type ReinstallResult ¶
type ReinstallResult struct {
RootPassword string `json:"rootPassword"`
SSHPort Int `json:"sshPort"`
SSHKeys Strings `json:"sshKeys"`
SSHKeysBrief Strings `json:"sshKeysBrief"`
NotificationEmail string `json:"notificationEmail"`
}
ReinstallResult is what reinstallOS hands back. The root password appears exactly once, here; nothing retrieves it later.
type Risk ¶
type Risk int
Risk classifies what an endpoint can do to a VPS. The dividing line between Write and Destructive is deliberately narrow:
Destructive = irreversible loss of data, identity, or access
that no other call in this package can restore.
Stopping a VPS is a Write because Start undoes it. Deleting a snapshot is Destructive because nothing brings it back. Keeping the Destructive set small is what makes a Destructive confirmation mean something; a prompt that guards everything guards nothing.
func (Risk) MarshalText ¶
MarshalText renders the risk as its lowercase name in JSON.
type RootPassword ¶
type RootPassword struct {
Password string `json:"password"`
}
RootPassword is the result of resetRootPassword.
type SSHKeys ¶
type SSHKeys struct {
Veid string `json:"ssh_keys_veid"`
User string `json:"ssh_keys_user"`
Preferred string `json:"ssh_keys_preferred"`
ShortenedVeid string `json:"shortened_ssh_keys_veid"`
ShortenedUser string `json:"shortened_ssh_keys_user"`
ShortenedPreferred string `json:"shortened_ssh_keys_preferred"`
}
SSHKeys are the keys reinstallOS will install, from both storage tiers. Each field is a newline-separated key list; the slice accessors split them.
func (*SSHKeys) PreferredSlice ¶
PreferredSlice returns the keys reinstallOS will actually install. Per-VM keys shadow account-level keys entirely.
type ScriptExec ¶
type ScriptExec struct {
// Log is the name of the output log file inside the VPS.
Log string `json:"log"`
}
ScriptExec is the response from shellScript/exec.
type ServiceInfo ¶
type ServiceInfo struct {
VMType string `json:"vm_type"`
Hostname string `json:"hostname"`
Plan string `json:"plan"`
OS string `json:"os"`
Email string `json:"email"`
NodeAlias string `json:"node_alias"`
NodeLocationID string `json:"node_location_id"`
NodeLocation string `json:"node_location"`
NodeDatacenter string `json:"node_datacenter"`
LocationIPv6Ready Bool `json:"location_ipv6_ready"`
PlanDisk Int `json:"plan_disk"`
PlanRAM Int `json:"plan_ram"`
PlanSwap Int `json:"plan_swap"`
// PlanMonthlyData and DataCounter are raw counters. Both are scaled
// by MonthlyDataMultiplier for the figures KiwiVM displays; use
// [ServiceInfo.Bandwidth] rather than reading them directly.
PlanMonthlyData Int `json:"plan_monthly_data"`
DataCounter Int `json:"data_counter"`
MonthlyDataMultiplier Int `json:"monthly_data_multiplier"`
DataNextReset Int `json:"data_next_reset"`
IPAddresses Strings `json:"ip_addresses"`
PrivateIPAddresses Strings `json:"private_ip_addresses"`
IPv6SitTunnelEndpoint string `json:"ipv6_sit_tunnel_endpoint,omitempty"`
IPNullroutes Nullroutes `json:"ip_nullroutes"`
PlanMaxIPv6s Int `json:"plan_max_ipv6s"`
ISO1 string `json:"iso1,omitempty"`
ISO2 string `json:"iso2,omitempty"`
AvailableISOs Strings `json:"available_isos"`
PlanPrivateNetworkAvailable Bool `json:"plan_private_network_available"`
LocationPrivateNetworkAvailable Bool `json:"location_private_network_available"`
RDNSAPIAvailable Bool `json:"rdns_api_available"`
PTR Map[string] `json:"ptr"`
Suspended Bool `json:"suspended"`
PolicyViolation Bool `json:"policy_violation"`
SuspensionCount Int `json:"suspension_count"`
TotalAbusePoints Int `json:"total_abuse_points"`
MaxAbusePoints Int `json:"max_abuse_points"`
}
ServiceInfo is the plan, location, network and quota state of a VPS, from getServiceInfo.
func (*ServiceInfo) AbusePercent ¶
func (s *ServiceInfo) AbusePercent() float64
AbusePercent returns accumulated abuse points as a share of the plan's yearly limit, 0-100. Zero when the limit is unknown.
func (*ServiceInfo) Bandwidth ¶
func (s *ServiceInfo) Bandwidth() Bandwidth
Bandwidth reports monthly transfer with the location multiplier applied to both the allowance and the counter, matching the KiwiVM panel.
Example ¶
Bandwidth applies the location multiplier to both the allowance and the counter, which is what the KiwiVM panel shows. Percent is the figure to trust: the multiplier scales both sides equally.
package main
import (
"fmt"
"github.com/lroolle/bwg-cli/kiwivm"
)
func main() {
const gib = 1024 * 1024 * 1024
info := &kiwivm.ServiceInfo{
PlanMonthlyData: kiwivm.Int(1000 * gib),
DataCounter: kiwivm.Int(250 * gib),
MonthlyDataMultiplier: 3,
}
b := info.Bandwidth()
fmt.Printf("%d GiB of %d GiB (%.0f%%), multiplier %dx\n",
b.Used/gib, b.Total/gib, b.Percent, b.Multiplier)
}
Output: 750 GiB of 3000 GiB (25%), multiplier 3x
func (*ServiceInfo) Healthy ¶
func (s *ServiceInfo) Healthy() bool
Healthy reports whether nothing demands attention: not suspended, no open policy violation, no live nullroute.
func (*ServiceInfo) IPv4 ¶
func (s *ServiceInfo) IPv4() []string
IPv4 returns the assigned IPv4 addresses.
func (*ServiceInfo) IPv6 ¶
func (s *ServiceInfo) IPv6() []string
IPv6 returns the assigned IPv6 /64 subnets. KiwiVM mixes them into the same ip_addresses array as the IPv4 addresses.
func (*ServiceInfo) PrimaryIP ¶
func (s *ServiceInfo) PrimaryIP() string
PrimaryIP returns the first IPv4 address, falling back to the first IPv6 subnet, or "" when the VPS has neither.
type ShellCD ¶
type ShellCD struct {
PWD string `json:"pwd"`
}
ShellCD is the response from basicShell/cd.
type ShellExec ¶
ShellExec is the response from basicShell/exec. KiwiVM overloads the shared envelope here: "error" carries the command's exit status and "message" its console output, so a non-zero exit is not an API failure. Client.ShellExec accounts for that.
type Snapshot ¶
type Snapshot struct {
FileName string `json:"fileName"`
OS string `json:"os"`
Description string `json:"description"`
Size Int `json:"size"`
Uncompressed Int `json:"uncompressed"`
MD5 string `json:"md5"`
Sticky Bool `json:"sticky"`
PurgesIn Int `json:"purgesIn"`
DownloadLink string `json:"downloadLink"`
DownloadLinkSSL string `json:"downloadLinkSSL"`
}
Snapshot is one stored snapshot.
type SnapshotCreated ¶
type SnapshotCreated struct {
NotificationEmail string `json:"notificationEmail"`
}
SnapshotCreated is the response from snapshot/create.
type SnapshotExport ¶
type SnapshotExport struct {
Token string `json:"token"`
}
SnapshotExport carries the token snapshot/import consumes.
type SnapshotList ¶
type SnapshotList struct {
Snapshots []Snapshot `json:"snapshots"`
}
SnapshotList is the response from snapshot/list.
type Strings ¶
type Strings []string
Strings is a JSON array of strings that KiwiVM may send as null, as an object keyed by index, or with non-string members.
func (*Strings) UnmarshalJSON ¶
type Suspension ¶
type Suspension struct {
RecordID Int `json:"record_id"`
Flag string `json:"flag"`
IsSoft Bool `json:"is_soft"`
EvidenceRecordID Int `json:"evidence_record_id"`
AbusePoints Int `json:"abuse_points"`
}
Suspension is one outstanding suspension case.
func (Suspension) APIResolvable ¶
func (s Suspension) APIResolvable() bool
APIResolvable reports whether Client.Unsuspend can clear this case, or whether it needs a support ticket.
type SuspensionDetails ¶
type SuspensionDetails struct {
SuspensionCount Int `json:"suspension_count"`
TotalAbusePoints Int `json:"total_abuse_points"`
MaxAbusePoints Int `json:"max_abuse_points"`
Suspensions []Suspension `json:"suspensions,omitempty"`
// Evidence maps an evidence record ID to the complaint text.
Evidence Map[string] `json:"evidence,omitempty"`
}
SuspensionDetails is the response from getSuspensionDetails.
type TransportError ¶
TransportError is a failure to get a usable answer out of the API: a dial error, a timeout, a 5xx, or a body that is not JSON. It says nothing about whether the credentials are valid.
func (*TransportError) Error ¶
func (e *TransportError) Error() string
func (*TransportError) Unwrap ¶
func (e *TransportError) Unwrap() error
type UsageSample ¶
type UsageSample struct {
Timestamp Int `json:"timestamp"`
CPUUsage Int `json:"cpu_usage"`
NetworkInBytes Int `json:"network_in_bytes"`
NetworkOutBytes Int `json:"network_out_bytes"`
DiskReadBytes Int `json:"disk_read_bytes"`
DiskWriteBytes Int `json:"disk_write_bytes"`
}
UsageSample is one interval of resource usage.
func (UsageSample) Time ¶
func (u UsageSample) Time() time.Time
Time returns the sample's timestamp.
type UsageStats ¶
type UsageStats struct {
VMType string `json:"vm_type"`
Data []UsageSample `json:"data"`
}
UsageStats is the sampled usage series from getRawUsageStats.
func (*UsageStats) Totals ¶
func (u *UsageStats) Totals() (netIn, netOut, diskRead, diskWrite int64)
Totals sums network and disk bytes across every sample.
func (*UsageStats) Window ¶
func (u *UsageStats) Window() (start, end time.Time)
Window returns the time span the samples cover.