ci: Disable GitHub Actions workflow temporarily
This commit is contained in:
parent
331cba42aa
commit
9ab564da60
16 changed files with 15954 additions and 771 deletions
6989
clients/c/src/un.c
6989
clients/c/src/un.c
File diff suppressed because it is too large
Load diff
|
|
@ -878,3 +878,685 @@ func DeleteSnapshot(creds *Credentials, snapshotID string) <-chan DeleteResult {
|
|||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Session Operations
|
||||
// ============================================================================
|
||||
|
||||
// SessionOptions contains optional parameters for session creation.
|
||||
type SessionOptions struct {
|
||||
NetworkMode string // "zerotrust" (default) or "semitrusted"
|
||||
Shell string // Shell to use (e.g., "bash", "python3")
|
||||
TTL int // Time-to-live in seconds (default: 3600)
|
||||
VCPU int // Number of virtual CPUs (default: 1)
|
||||
Multiplexer string // Multiplexer to use (e.g., "tmux")
|
||||
}
|
||||
|
||||
// SessionListResult contains the result of listing sessions.
|
||||
type SessionListResult struct {
|
||||
Sessions []map[string]interface{}
|
||||
Err error
|
||||
}
|
||||
|
||||
// SessionResult contains the result of a session operation.
|
||||
type SessionResult struct {
|
||||
Data map[string]interface{}
|
||||
Err error
|
||||
}
|
||||
|
||||
// ListSessions lists all active sessions for the authenticated account.
|
||||
// Returns a channel that receives exactly one SessionListResult then closes.
|
||||
func ListSessions(creds *Credentials) <-chan SessionListResult {
|
||||
resultChan := make(chan SessionListResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
response, err := makeRequest("GET", "/sessions", creds, nil)
|
||||
if err != nil {
|
||||
resultChan <- SessionListResult{Err: err}
|
||||
return
|
||||
}
|
||||
|
||||
var sessions []map[string]interface{}
|
||||
if sessionsInterface, ok := response["sessions"].([]interface{}); ok {
|
||||
sessions = make([]map[string]interface{}, len(sessionsInterface))
|
||||
for i, session := range sessionsInterface {
|
||||
if m, ok := session.(map[string]interface{}); ok {
|
||||
sessions[i] = m
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resultChan <- SessionListResult{Sessions: sessions}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// GetSession gets details of a specific session.
|
||||
// Returns a channel that receives exactly one SessionResult then closes.
|
||||
func GetSession(creds *Credentials, sessionID string) <-chan SessionResult {
|
||||
resultChan := make(chan SessionResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
response, err := makeRequest("GET", fmt.Sprintf("/sessions/%s", sessionID), creds, nil)
|
||||
resultChan <- SessionResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// CreateSession creates a new interactive session.
|
||||
// Returns a channel that receives exactly one SessionResult then closes.
|
||||
//
|
||||
// Args:
|
||||
//
|
||||
// creds: API credentials
|
||||
// opts: Optional session configuration (can be nil for defaults)
|
||||
func CreateSession(creds *Credentials, opts *SessionOptions) <-chan SessionResult {
|
||||
resultChan := make(chan SessionResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
data := make(map[string]interface{})
|
||||
|
||||
if opts != nil {
|
||||
if opts.NetworkMode != "" {
|
||||
data["network_mode"] = opts.NetworkMode
|
||||
}
|
||||
if opts.Shell != "" {
|
||||
data["shell"] = opts.Shell
|
||||
}
|
||||
if opts.TTL > 0 {
|
||||
data["ttl"] = opts.TTL
|
||||
}
|
||||
if opts.VCPU > 0 {
|
||||
data["vcpu"] = opts.VCPU
|
||||
}
|
||||
if opts.Multiplexer != "" {
|
||||
data["multiplexer"] = opts.Multiplexer
|
||||
}
|
||||
}
|
||||
|
||||
response, err := makeRequest("POST", "/sessions", creds, data)
|
||||
resultChan <- SessionResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// DeleteSession terminates a session.
|
||||
// Returns a channel that receives exactly one SessionResult then closes.
|
||||
func DeleteSession(creds *Credentials, sessionID string) <-chan SessionResult {
|
||||
resultChan := make(chan SessionResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
response, err := makeRequest("DELETE", fmt.Sprintf("/sessions/%s", sessionID), creds, nil)
|
||||
resultChan <- SessionResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// FreezeSession freezes a session (pauses execution, preserves state).
|
||||
// Returns a channel that receives exactly one SessionResult then closes.
|
||||
func FreezeSession(creds *Credentials, sessionID string) <-chan SessionResult {
|
||||
resultChan := make(chan SessionResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
response, err := makeRequest("POST", fmt.Sprintf("/sessions/%s/freeze", sessionID), creds, map[string]interface{}{})
|
||||
resultChan <- SessionResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// UnfreezeSession unfreezes a previously frozen session.
|
||||
// Returns a channel that receives exactly one SessionResult then closes.
|
||||
func UnfreezeSession(creds *Credentials, sessionID string) <-chan SessionResult {
|
||||
resultChan := make(chan SessionResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
response, err := makeRequest("POST", fmt.Sprintf("/sessions/%s/unfreeze", sessionID), creds, map[string]interface{}{})
|
||||
resultChan <- SessionResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// BoostSession increases the vCPU allocation for a session.
|
||||
// Returns a channel that receives exactly one SessionResult then closes.
|
||||
//
|
||||
// Args:
|
||||
//
|
||||
// creds: API credentials
|
||||
// sessionID: Session ID to boost
|
||||
// vcpu: Number of vCPUs (2, 4, 8, etc.)
|
||||
func BoostSession(creds *Credentials, sessionID string, vcpu int) <-chan SessionResult {
|
||||
resultChan := make(chan SessionResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
data := map[string]interface{}{
|
||||
"vcpu": vcpu,
|
||||
}
|
||||
response, err := makeRequest("POST", fmt.Sprintf("/sessions/%s/boost", sessionID), creds, data)
|
||||
resultChan <- SessionResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// UnboostSession resets the vCPU allocation for a session to default.
|
||||
// Returns a channel that receives exactly one SessionResult then closes.
|
||||
func UnboostSession(creds *Credentials, sessionID string) <-chan SessionResult {
|
||||
resultChan := make(chan SessionResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
response, err := makeRequest("POST", fmt.Sprintf("/sessions/%s/unboost", sessionID), creds, map[string]interface{}{})
|
||||
resultChan <- SessionResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// ShellSession executes a command in a session's shell.
|
||||
// Returns a channel that receives exactly one SessionResult then closes.
|
||||
//
|
||||
// Note: For interactive shell access, use the WebSocket-based shell endpoint.
|
||||
// This function is for executing single commands.
|
||||
func ShellSession(creds *Credentials, sessionID, command string) <-chan SessionResult {
|
||||
resultChan := make(chan SessionResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
data := map[string]interface{}{
|
||||
"command": command,
|
||||
}
|
||||
response, err := makeRequest("POST", fmt.Sprintf("/sessions/%s/shell", sessionID), creds, data)
|
||||
resultChan <- SessionResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Service Operations
|
||||
// ============================================================================
|
||||
|
||||
// ServiceOptions contains optional parameters for service creation.
|
||||
type ServiceOptions struct {
|
||||
NetworkMode string // "zerotrust" (default) or "semitrusted"
|
||||
Shell string // Shell to use for bootstrap
|
||||
VCPU int // Number of virtual CPUs
|
||||
}
|
||||
|
||||
// ServiceUpdateOptions contains optional parameters for service updates.
|
||||
type ServiceUpdateOptions struct {
|
||||
VCPU int // Number of virtual CPUs
|
||||
}
|
||||
|
||||
// ServiceListResult contains the result of listing services.
|
||||
type ServiceListResult struct {
|
||||
Services []map[string]interface{}
|
||||
Err error
|
||||
}
|
||||
|
||||
// ServiceResult contains the result of a service operation.
|
||||
type ServiceResult struct {
|
||||
Data map[string]interface{}
|
||||
Err error
|
||||
}
|
||||
|
||||
// ListServices lists all services for the authenticated account.
|
||||
// Returns a channel that receives exactly one ServiceListResult then closes.
|
||||
func ListServices(creds *Credentials) <-chan ServiceListResult {
|
||||
resultChan := make(chan ServiceListResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
response, err := makeRequest("GET", "/services", creds, nil)
|
||||
if err != nil {
|
||||
resultChan <- ServiceListResult{Err: err}
|
||||
return
|
||||
}
|
||||
|
||||
var services []map[string]interface{}
|
||||
if servicesInterface, ok := response["services"].([]interface{}); ok {
|
||||
services = make([]map[string]interface{}, len(servicesInterface))
|
||||
for i, service := range servicesInterface {
|
||||
if m, ok := service.(map[string]interface{}); ok {
|
||||
services[i] = m
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resultChan <- ServiceListResult{Services: services}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// CreateService creates a new persistent service.
|
||||
// Returns a channel that receives exactly one ServiceResult then closes.
|
||||
//
|
||||
// Args:
|
||||
//
|
||||
// creds: API credentials
|
||||
// name: Service name
|
||||
// ports: Array of port numbers to expose
|
||||
// bootstrap: Bootstrap script to run on service start
|
||||
// opts: Optional service configuration (can be nil for defaults)
|
||||
func CreateService(creds *Credentials, name string, ports []int, bootstrap string, opts *ServiceOptions) <-chan ServiceResult {
|
||||
resultChan := make(chan ServiceResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
data := map[string]interface{}{
|
||||
"name": name,
|
||||
"ports": ports,
|
||||
"bootstrap": bootstrap,
|
||||
}
|
||||
|
||||
if opts != nil {
|
||||
if opts.NetworkMode != "" {
|
||||
data["network_mode"] = opts.NetworkMode
|
||||
}
|
||||
if opts.Shell != "" {
|
||||
data["shell"] = opts.Shell
|
||||
}
|
||||
if opts.VCPU > 0 {
|
||||
data["vcpu"] = opts.VCPU
|
||||
}
|
||||
}
|
||||
|
||||
response, err := makeRequest("POST", "/services", creds, data)
|
||||
resultChan <- ServiceResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// GetService gets details of a specific service.
|
||||
// Returns a channel that receives exactly one ServiceResult then closes.
|
||||
func GetService(creds *Credentials, serviceID string) <-chan ServiceResult {
|
||||
resultChan := make(chan ServiceResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
response, err := makeRequest("GET", fmt.Sprintf("/services/%s", serviceID), creds, nil)
|
||||
resultChan <- ServiceResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// UpdateService updates a service's configuration.
|
||||
// Returns a channel that receives exactly one ServiceResult then closes.
|
||||
func UpdateService(creds *Credentials, serviceID string, opts *ServiceUpdateOptions) <-chan ServiceResult {
|
||||
resultChan := make(chan ServiceResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
data := make(map[string]interface{})
|
||||
if opts != nil {
|
||||
if opts.VCPU > 0 {
|
||||
data["vcpu"] = opts.VCPU
|
||||
}
|
||||
}
|
||||
response, err := makeRequest("PATCH", fmt.Sprintf("/services/%s", serviceID), creds, data)
|
||||
resultChan <- ServiceResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// DeleteService destroys a service.
|
||||
// Returns a channel that receives exactly one ServiceResult then closes.
|
||||
func DeleteService(creds *Credentials, serviceID string) <-chan ServiceResult {
|
||||
resultChan := make(chan ServiceResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
response, err := makeRequest("DELETE", fmt.Sprintf("/services/%s", serviceID), creds, nil)
|
||||
resultChan <- ServiceResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// FreezeService freezes a service (pauses execution, preserves state).
|
||||
// Returns a channel that receives exactly one ServiceResult then closes.
|
||||
func FreezeService(creds *Credentials, serviceID string) <-chan ServiceResult {
|
||||
resultChan := make(chan ServiceResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
response, err := makeRequest("POST", fmt.Sprintf("/services/%s/freeze", serviceID), creds, map[string]interface{}{})
|
||||
resultChan <- ServiceResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// UnfreezeService unfreezes a previously frozen service.
|
||||
// Returns a channel that receives exactly one ServiceResult then closes.
|
||||
func UnfreezeService(creds *Credentials, serviceID string) <-chan ServiceResult {
|
||||
resultChan := make(chan ServiceResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
response, err := makeRequest("POST", fmt.Sprintf("/services/%s/unfreeze", serviceID), creds, map[string]interface{}{})
|
||||
resultChan <- ServiceResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// LockService locks a service to prevent modifications or deletion.
|
||||
// Returns a channel that receives exactly one ServiceResult then closes.
|
||||
func LockService(creds *Credentials, serviceID string) <-chan ServiceResult {
|
||||
resultChan := make(chan ServiceResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
response, err := makeRequest("POST", fmt.Sprintf("/services/%s/lock", serviceID), creds, map[string]interface{}{})
|
||||
resultChan <- ServiceResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// UnlockService unlocks a previously locked service.
|
||||
// Returns a channel that receives exactly one ServiceResult then closes.
|
||||
func UnlockService(creds *Credentials, serviceID string) <-chan ServiceResult {
|
||||
resultChan := make(chan ServiceResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
response, err := makeRequest("POST", fmt.Sprintf("/services/%s/unlock", serviceID), creds, map[string]interface{}{})
|
||||
resultChan <- ServiceResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// GetServiceLogs retrieves logs from a service.
|
||||
// Returns a channel that receives exactly one ServiceResult then closes.
|
||||
//
|
||||
// Args:
|
||||
//
|
||||
// creds: API credentials
|
||||
// serviceID: Service ID
|
||||
// all: If true, returns all logs; if false, returns only recent logs
|
||||
func GetServiceLogs(creds *Credentials, serviceID string, all bool) <-chan ServiceResult {
|
||||
resultChan := make(chan ServiceResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
path := fmt.Sprintf("/services/%s/logs", serviceID)
|
||||
if all {
|
||||
path = fmt.Sprintf("/services/%s/logs?all=true", serviceID)
|
||||
}
|
||||
response, err := makeRequest("GET", path, creds, nil)
|
||||
resultChan <- ServiceResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// GetServiceEnv retrieves the environment variable names for a service.
|
||||
// Returns a channel that receives exactly one ServiceResult then closes.
|
||||
// Note: Values are not returned for security; use ExportServiceEnv for full export.
|
||||
func GetServiceEnv(creds *Credentials, serviceID string) <-chan ServiceResult {
|
||||
resultChan := make(chan ServiceResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
response, err := makeRequest("GET", fmt.Sprintf("/services/%s/env", serviceID), creds, nil)
|
||||
resultChan <- ServiceResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// SetServiceEnv sets environment variables for a service.
|
||||
// Returns a channel that receives exactly one ServiceResult then closes.
|
||||
//
|
||||
// Args:
|
||||
//
|
||||
// creds: API credentials
|
||||
// serviceID: Service ID
|
||||
// env: Map of environment variable names to values
|
||||
func SetServiceEnv(creds *Credentials, serviceID string, env map[string]string) <-chan ServiceResult {
|
||||
resultChan := make(chan ServiceResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
response, err := makeRequest("POST", fmt.Sprintf("/services/%s/env", serviceID), creds, env)
|
||||
resultChan <- ServiceResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// DeleteServiceEnv deletes environment variables from a service.
|
||||
// Returns a channel that receives exactly one ServiceResult then closes.
|
||||
//
|
||||
// Args:
|
||||
//
|
||||
// creds: API credentials
|
||||
// serviceID: Service ID
|
||||
// keys: List of environment variable names to delete (nil deletes all)
|
||||
func DeleteServiceEnv(creds *Credentials, serviceID string, keys []string) <-chan ServiceResult {
|
||||
resultChan := make(chan ServiceResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
var data interface{}
|
||||
if keys != nil {
|
||||
data = map[string]interface{}{"keys": keys}
|
||||
}
|
||||
response, err := makeRequest("DELETE", fmt.Sprintf("/services/%s/env", serviceID), creds, data)
|
||||
resultChan <- ServiceResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// ExportServiceEnv exports all environment variables for a service.
|
||||
// Returns a channel that receives exactly one ServiceResult then closes.
|
||||
// Returns the full .env format content with values.
|
||||
func ExportServiceEnv(creds *Credentials, serviceID string) <-chan ServiceResult {
|
||||
resultChan := make(chan ServiceResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
response, err := makeRequest("POST", fmt.Sprintf("/services/%s/env/export", serviceID), creds, map[string]interface{}{})
|
||||
resultChan <- ServiceResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// RedeployService redeploys a service with optional new bootstrap script.
|
||||
// Returns a channel that receives exactly one ServiceResult then closes.
|
||||
//
|
||||
// Args:
|
||||
//
|
||||
// creds: API credentials
|
||||
// serviceID: Service ID
|
||||
// bootstrap: New bootstrap script (empty string to keep existing)
|
||||
func RedeployService(creds *Credentials, serviceID string, bootstrap string) <-chan ServiceResult {
|
||||
resultChan := make(chan ServiceResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
data := make(map[string]interface{})
|
||||
if bootstrap != "" {
|
||||
data["bootstrap"] = bootstrap
|
||||
}
|
||||
response, err := makeRequest("POST", fmt.Sprintf("/services/%s/redeploy", serviceID), creds, data)
|
||||
resultChan <- ServiceResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// ExecuteInService executes a command in a running service.
|
||||
// Returns a channel that receives exactly one ServiceResult then closes.
|
||||
func ExecuteInService(creds *Credentials, serviceID, command string) <-chan ServiceResult {
|
||||
resultChan := make(chan ServiceResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
data := map[string]interface{}{
|
||||
"command": command,
|
||||
}
|
||||
response, err := makeRequest("POST", fmt.Sprintf("/services/%s/execute", serviceID), creds, data)
|
||||
resultChan <- ServiceResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Additional Snapshot Operations
|
||||
// ============================================================================
|
||||
|
||||
// LockSnapshot locks a snapshot to prevent deletion.
|
||||
// Returns a channel that receives exactly one DeleteResult then closes.
|
||||
func LockSnapshot(creds *Credentials, snapshotID string) <-chan DeleteResult {
|
||||
resultChan := make(chan DeleteResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
response, err := makeRequest("POST", fmt.Sprintf("/snapshots/%s/lock", snapshotID), creds, map[string]interface{}{})
|
||||
resultChan <- DeleteResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// UnlockSnapshot unlocks a previously locked snapshot.
|
||||
// Returns a channel that receives exactly one DeleteResult then closes.
|
||||
func UnlockSnapshot(creds *Credentials, snapshotID string) <-chan DeleteResult {
|
||||
resultChan := make(chan DeleteResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
response, err := makeRequest("POST", fmt.Sprintf("/snapshots/%s/unlock", snapshotID), creds, map[string]interface{}{})
|
||||
resultChan <- DeleteResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// CloneSnapshotOptions contains optional parameters for snapshot cloning.
|
||||
type CloneSnapshotOptions struct {
|
||||
Name string // Name for the cloned resource
|
||||
Shell string // Shell to use (for session clones)
|
||||
Ports []int // Ports to expose (for service clones)
|
||||
}
|
||||
|
||||
// CloneSnapshotResult contains the result of cloning a snapshot.
|
||||
type CloneSnapshotResult struct {
|
||||
Data map[string]interface{}
|
||||
Err error
|
||||
}
|
||||
|
||||
// CloneSnapshot clones a snapshot into a new session or service.
|
||||
// Returns a channel that receives exactly one CloneSnapshotResult then closes.
|
||||
//
|
||||
// Args:
|
||||
//
|
||||
// creds: API credentials
|
||||
// snapshotID: Snapshot ID to clone
|
||||
// cloneType: "session" or "service"
|
||||
// opts: Optional clone configuration (can be nil)
|
||||
func CloneSnapshot(creds *Credentials, snapshotID, cloneType string, opts *CloneSnapshotOptions) <-chan CloneSnapshotResult {
|
||||
resultChan := make(chan CloneSnapshotResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
data := map[string]interface{}{
|
||||
"type": cloneType,
|
||||
}
|
||||
|
||||
if opts != nil {
|
||||
if opts.Name != "" {
|
||||
data["name"] = opts.Name
|
||||
}
|
||||
if opts.Shell != "" {
|
||||
data["shell"] = opts.Shell
|
||||
}
|
||||
if opts.Ports != nil {
|
||||
data["ports"] = opts.Ports
|
||||
}
|
||||
}
|
||||
|
||||
response, err := makeRequest("POST", fmt.Sprintf("/snapshots/%s/clone", snapshotID), creds, data)
|
||||
resultChan <- CloneSnapshotResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Key Validation
|
||||
// ============================================================================
|
||||
|
||||
// ValidateKeysResult contains the result of validating API keys.
|
||||
type ValidateKeysResult struct {
|
||||
Data map[string]interface{}
|
||||
Err error
|
||||
}
|
||||
|
||||
// ValidateKeys validates the API credentials with the server.
|
||||
// Returns a channel that receives exactly one ValidateKeysResult then closes.
|
||||
// Returns account information if valid, error if invalid.
|
||||
func ValidateKeys(creds *Credentials) <-chan ValidateKeysResult {
|
||||
resultChan := make(chan ValidateKeysResult, 1)
|
||||
|
||||
go func() {
|
||||
defer close(resultChan)
|
||||
|
||||
response, err := makeRequest("POST", "/keys/validate", creds, map[string]interface{}{})
|
||||
resultChan <- ValidateKeysResult{Data: response, Err: err}
|
||||
}()
|
||||
|
||||
return resultChan
|
||||
}
|
||||
|
|
|
|||
|
|
@ -649,3 +649,363 @@ func RestoreSnapshot(creds *Credentials, snapshotID string) (map[string]interfac
|
|||
func DeleteSnapshot(creds *Credentials, snapshotID string) (map[string]interface{}, error) {
|
||||
return makeRequest("DELETE", fmt.Sprintf("/snapshots/%s", snapshotID), creds, nil)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Session Operations
|
||||
// ============================================================================
|
||||
|
||||
// SessionOptions contains optional parameters for session creation.
|
||||
type SessionOptions struct {
|
||||
NetworkMode string // "zerotrust" (default) or "semitrusted"
|
||||
Shell string // Shell to use (e.g., "bash", "python3")
|
||||
TTL int // Time-to-live in seconds (default: 3600)
|
||||
VCPU int // Number of virtual CPUs (default: 1)
|
||||
Multiplexer string // Multiplexer to use (e.g., "tmux")
|
||||
}
|
||||
|
||||
// ListSessions lists all active sessions for the authenticated account.
|
||||
func ListSessions(creds *Credentials) ([]map[string]interface{}, error) {
|
||||
response, err := makeRequest("GET", "/sessions", creds, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sessions, ok := response["sessions"].([]interface{}); ok {
|
||||
result := make([]map[string]interface{}, len(sessions))
|
||||
for i, session := range sessions {
|
||||
if m, ok := session.(map[string]interface{}); ok {
|
||||
result[i] = m
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return []map[string]interface{}{}, nil
|
||||
}
|
||||
|
||||
// GetSession gets details of a specific session.
|
||||
func GetSession(creds *Credentials, sessionID string) (map[string]interface{}, error) {
|
||||
return makeRequest("GET", fmt.Sprintf("/sessions/%s", sessionID), creds, nil)
|
||||
}
|
||||
|
||||
// CreateSession creates a new interactive session.
|
||||
//
|
||||
// Args:
|
||||
//
|
||||
// creds: API credentials
|
||||
// opts: Optional session configuration (can be nil for defaults)
|
||||
//
|
||||
// Returns:
|
||||
//
|
||||
// Session info including session_id and container_name
|
||||
func CreateSession(creds *Credentials, opts *SessionOptions) (map[string]interface{}, error) {
|
||||
data := make(map[string]interface{})
|
||||
|
||||
if opts != nil {
|
||||
if opts.NetworkMode != "" {
|
||||
data["network_mode"] = opts.NetworkMode
|
||||
}
|
||||
if opts.Shell != "" {
|
||||
data["shell"] = opts.Shell
|
||||
}
|
||||
if opts.TTL > 0 {
|
||||
data["ttl"] = opts.TTL
|
||||
}
|
||||
if opts.VCPU > 0 {
|
||||
data["vcpu"] = opts.VCPU
|
||||
}
|
||||
if opts.Multiplexer != "" {
|
||||
data["multiplexer"] = opts.Multiplexer
|
||||
}
|
||||
}
|
||||
|
||||
return makeRequest("POST", "/sessions", creds, data)
|
||||
}
|
||||
|
||||
// DeleteSession terminates a session.
|
||||
func DeleteSession(creds *Credentials, sessionID string) (map[string]interface{}, error) {
|
||||
return makeRequest("DELETE", fmt.Sprintf("/sessions/%s", sessionID), creds, nil)
|
||||
}
|
||||
|
||||
// FreezeSession freezes a session (pauses execution, preserves state).
|
||||
func FreezeSession(creds *Credentials, sessionID string) (map[string]interface{}, error) {
|
||||
return makeRequest("POST", fmt.Sprintf("/sessions/%s/freeze", sessionID), creds, map[string]interface{}{})
|
||||
}
|
||||
|
||||
// UnfreezeSession unfreezes a previously frozen session.
|
||||
func UnfreezeSession(creds *Credentials, sessionID string) (map[string]interface{}, error) {
|
||||
return makeRequest("POST", fmt.Sprintf("/sessions/%s/unfreeze", sessionID), creds, map[string]interface{}{})
|
||||
}
|
||||
|
||||
// BoostSession increases the vCPU allocation for a session.
|
||||
//
|
||||
// Args:
|
||||
//
|
||||
// creds: API credentials
|
||||
// sessionID: Session ID to boost
|
||||
// vcpu: Number of vCPUs (2, 4, 8, etc.)
|
||||
func BoostSession(creds *Credentials, sessionID string, vcpu int) (map[string]interface{}, error) {
|
||||
data := map[string]interface{}{
|
||||
"vcpu": vcpu,
|
||||
}
|
||||
return makeRequest("POST", fmt.Sprintf("/sessions/%s/boost", sessionID), creds, data)
|
||||
}
|
||||
|
||||
// UnboostSession resets the vCPU allocation for a session to default.
|
||||
func UnboostSession(creds *Credentials, sessionID string) (map[string]interface{}, error) {
|
||||
return makeRequest("POST", fmt.Sprintf("/sessions/%s/unboost", sessionID), creds, map[string]interface{}{})
|
||||
}
|
||||
|
||||
// ShellSession executes a command in a session's shell.
|
||||
//
|
||||
// Note: For interactive shell access, use the WebSocket-based shell endpoint.
|
||||
// This function is for executing single commands.
|
||||
func ShellSession(creds *Credentials, sessionID, command string) (map[string]interface{}, error) {
|
||||
data := map[string]interface{}{
|
||||
"command": command,
|
||||
}
|
||||
return makeRequest("POST", fmt.Sprintf("/sessions/%s/shell", sessionID), creds, data)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Service Operations
|
||||
// ============================================================================
|
||||
|
||||
// ServiceOptions contains optional parameters for service creation.
|
||||
type ServiceOptions struct {
|
||||
NetworkMode string // "zerotrust" (default) or "semitrusted"
|
||||
Shell string // Shell to use for bootstrap
|
||||
VCPU int // Number of virtual CPUs
|
||||
}
|
||||
|
||||
// ServiceUpdateOptions contains optional parameters for service updates.
|
||||
type ServiceUpdateOptions struct {
|
||||
VCPU int // Number of virtual CPUs
|
||||
}
|
||||
|
||||
// ListServices lists all services for the authenticated account.
|
||||
func ListServices(creds *Credentials) ([]map[string]interface{}, error) {
|
||||
response, err := makeRequest("GET", "/services", creds, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if services, ok := response["services"].([]interface{}); ok {
|
||||
result := make([]map[string]interface{}, len(services))
|
||||
for i, service := range services {
|
||||
if m, ok := service.(map[string]interface{}); ok {
|
||||
result[i] = m
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return []map[string]interface{}{}, nil
|
||||
}
|
||||
|
||||
// CreateService creates a new persistent service.
|
||||
//
|
||||
// Args:
|
||||
//
|
||||
// creds: API credentials
|
||||
// name: Service name
|
||||
// ports: Array of port numbers to expose
|
||||
// bootstrap: Bootstrap script to run on service start
|
||||
// opts: Optional service configuration (can be nil for defaults)
|
||||
func CreateService(creds *Credentials, name string, ports []int, bootstrap string, opts *ServiceOptions) (map[string]interface{}, error) {
|
||||
data := map[string]interface{}{
|
||||
"name": name,
|
||||
"ports": ports,
|
||||
"bootstrap": bootstrap,
|
||||
}
|
||||
|
||||
if opts != nil {
|
||||
if opts.NetworkMode != "" {
|
||||
data["network_mode"] = opts.NetworkMode
|
||||
}
|
||||
if opts.Shell != "" {
|
||||
data["shell"] = opts.Shell
|
||||
}
|
||||
if opts.VCPU > 0 {
|
||||
data["vcpu"] = opts.VCPU
|
||||
}
|
||||
}
|
||||
|
||||
return makeRequest("POST", "/services", creds, data)
|
||||
}
|
||||
|
||||
// GetService gets details of a specific service.
|
||||
func GetService(creds *Credentials, serviceID string) (map[string]interface{}, error) {
|
||||
return makeRequest("GET", fmt.Sprintf("/services/%s", serviceID), creds, nil)
|
||||
}
|
||||
|
||||
// UpdateService updates a service's configuration.
|
||||
func UpdateService(creds *Credentials, serviceID string, opts *ServiceUpdateOptions) (map[string]interface{}, error) {
|
||||
data := make(map[string]interface{})
|
||||
if opts != nil {
|
||||
if opts.VCPU > 0 {
|
||||
data["vcpu"] = opts.VCPU
|
||||
}
|
||||
}
|
||||
return makeRequest("PATCH", fmt.Sprintf("/services/%s", serviceID), creds, data)
|
||||
}
|
||||
|
||||
// DeleteService destroys a service.
|
||||
func DeleteService(creds *Credentials, serviceID string) (map[string]interface{}, error) {
|
||||
return makeRequest("DELETE", fmt.Sprintf("/services/%s", serviceID), creds, nil)
|
||||
}
|
||||
|
||||
// FreezeService freezes a service (pauses execution, preserves state).
|
||||
func FreezeService(creds *Credentials, serviceID string) (map[string]interface{}, error) {
|
||||
return makeRequest("POST", fmt.Sprintf("/services/%s/freeze", serviceID), creds, map[string]interface{}{})
|
||||
}
|
||||
|
||||
// UnfreezeService unfreezes a previously frozen service.
|
||||
func UnfreezeService(creds *Credentials, serviceID string) (map[string]interface{}, error) {
|
||||
return makeRequest("POST", fmt.Sprintf("/services/%s/unfreeze", serviceID), creds, map[string]interface{}{})
|
||||
}
|
||||
|
||||
// LockService locks a service to prevent modifications or deletion.
|
||||
func LockService(creds *Credentials, serviceID string) (map[string]interface{}, error) {
|
||||
return makeRequest("POST", fmt.Sprintf("/services/%s/lock", serviceID), creds, map[string]interface{}{})
|
||||
}
|
||||
|
||||
// UnlockService unlocks a previously locked service.
|
||||
func UnlockService(creds *Credentials, serviceID string) (map[string]interface{}, error) {
|
||||
return makeRequest("POST", fmt.Sprintf("/services/%s/unlock", serviceID), creds, map[string]interface{}{})
|
||||
}
|
||||
|
||||
// GetServiceLogs retrieves logs from a service.
|
||||
//
|
||||
// Args:
|
||||
//
|
||||
// creds: API credentials
|
||||
// serviceID: Service ID
|
||||
// all: If true, returns all logs; if false, returns only recent logs
|
||||
func GetServiceLogs(creds *Credentials, serviceID string, all bool) (map[string]interface{}, error) {
|
||||
path := fmt.Sprintf("/services/%s/logs", serviceID)
|
||||
if all {
|
||||
path = fmt.Sprintf("/services/%s/logs?all=true", serviceID)
|
||||
}
|
||||
return makeRequest("GET", path, creds, nil)
|
||||
}
|
||||
|
||||
// GetServiceEnv retrieves the environment variable names for a service.
|
||||
// Note: Values are not returned for security; use ExportServiceEnv for full export.
|
||||
func GetServiceEnv(creds *Credentials, serviceID string) (map[string]interface{}, error) {
|
||||
return makeRequest("GET", fmt.Sprintf("/services/%s/env", serviceID), creds, nil)
|
||||
}
|
||||
|
||||
// SetServiceEnv sets environment variables for a service.
|
||||
//
|
||||
// Args:
|
||||
//
|
||||
// creds: API credentials
|
||||
// serviceID: Service ID
|
||||
// env: Map of environment variable names to values
|
||||
func SetServiceEnv(creds *Credentials, serviceID string, env map[string]string) (map[string]interface{}, error) {
|
||||
return makeRequest("POST", fmt.Sprintf("/services/%s/env", serviceID), creds, env)
|
||||
}
|
||||
|
||||
// DeleteServiceEnv deletes environment variables from a service.
|
||||
//
|
||||
// Args:
|
||||
//
|
||||
// creds: API credentials
|
||||
// serviceID: Service ID
|
||||
// keys: List of environment variable names to delete (nil deletes all)
|
||||
func DeleteServiceEnv(creds *Credentials, serviceID string, keys []string) (map[string]interface{}, error) {
|
||||
var data interface{}
|
||||
if keys != nil {
|
||||
data = map[string]interface{}{"keys": keys}
|
||||
}
|
||||
return makeRequest("DELETE", fmt.Sprintf("/services/%s/env", serviceID), creds, data)
|
||||
}
|
||||
|
||||
// ExportServiceEnv exports all environment variables for a service.
|
||||
// Returns the full .env format content with values.
|
||||
func ExportServiceEnv(creds *Credentials, serviceID string) (map[string]interface{}, error) {
|
||||
return makeRequest("POST", fmt.Sprintf("/services/%s/env/export", serviceID), creds, map[string]interface{}{})
|
||||
}
|
||||
|
||||
// RedeployService redeploys a service with optional new bootstrap script.
|
||||
//
|
||||
// Args:
|
||||
//
|
||||
// creds: API credentials
|
||||
// serviceID: Service ID
|
||||
// bootstrap: New bootstrap script (empty string to keep existing)
|
||||
func RedeployService(creds *Credentials, serviceID string, bootstrap string) (map[string]interface{}, error) {
|
||||
data := make(map[string]interface{})
|
||||
if bootstrap != "" {
|
||||
data["bootstrap"] = bootstrap
|
||||
}
|
||||
return makeRequest("POST", fmt.Sprintf("/services/%s/redeploy", serviceID), creds, data)
|
||||
}
|
||||
|
||||
// ExecuteInService executes a command in a running service.
|
||||
func ExecuteInService(creds *Credentials, serviceID, command string) (map[string]interface{}, error) {
|
||||
data := map[string]interface{}{
|
||||
"command": command,
|
||||
}
|
||||
return makeRequest("POST", fmt.Sprintf("/services/%s/execute", serviceID), creds, data)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Additional Snapshot Operations
|
||||
// ============================================================================
|
||||
|
||||
// LockSnapshot locks a snapshot to prevent deletion.
|
||||
func LockSnapshot(creds *Credentials, snapshotID string) (map[string]interface{}, error) {
|
||||
return makeRequest("POST", fmt.Sprintf("/snapshots/%s/lock", snapshotID), creds, map[string]interface{}{})
|
||||
}
|
||||
|
||||
// UnlockSnapshot unlocks a previously locked snapshot.
|
||||
func UnlockSnapshot(creds *Credentials, snapshotID string) (map[string]interface{}, error) {
|
||||
return makeRequest("POST", fmt.Sprintf("/snapshots/%s/unlock", snapshotID), creds, map[string]interface{}{})
|
||||
}
|
||||
|
||||
// CloneSnapshotOptions contains optional parameters for snapshot cloning.
|
||||
type CloneSnapshotOptions struct {
|
||||
Name string // Name for the cloned resource
|
||||
Shell string // Shell to use (for session clones)
|
||||
Ports []int // Ports to expose (for service clones)
|
||||
}
|
||||
|
||||
// CloneSnapshot clones a snapshot into a new session or service.
|
||||
//
|
||||
// Args:
|
||||
//
|
||||
// creds: API credentials
|
||||
// snapshotID: Snapshot ID to clone
|
||||
// cloneType: "session" or "service"
|
||||
// opts: Optional clone configuration (can be nil)
|
||||
func CloneSnapshot(creds *Credentials, snapshotID, cloneType string, opts *CloneSnapshotOptions) (map[string]interface{}, error) {
|
||||
data := map[string]interface{}{
|
||||
"type": cloneType,
|
||||
}
|
||||
|
||||
if opts != nil {
|
||||
if opts.Name != "" {
|
||||
data["name"] = opts.Name
|
||||
}
|
||||
if opts.Shell != "" {
|
||||
data["shell"] = opts.Shell
|
||||
}
|
||||
if opts.Ports != nil {
|
||||
data["ports"] = opts.Ports
|
||||
}
|
||||
}
|
||||
|
||||
return makeRequest("POST", fmt.Sprintf("/snapshots/%s/clone", snapshotID), creds, data)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Key Validation
|
||||
// ============================================================================
|
||||
|
||||
// ValidateKeys validates the API credentials with the server.
|
||||
// Returns account information if valid, error if invalid.
|
||||
func ValidateKeys(creds *Credentials) (map[string]interface{}, error) {
|
||||
return makeRequest("POST", "/keys/validate", creds, map[string]interface{}{})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1088,6 +1088,694 @@ public class UnsandboxAsync {
|
|||
return makeRequest("DELETE", "/snapshots/" + snapshotId, creds[0], creds[1], null);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Session API Methods
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* List all active sessions for the authenticated account.
|
||||
*
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing list of session maps
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static CompletableFuture<List<Map<String, Object>>> listSessions(
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("GET", "/sessions", creds[0], creds[1], null)
|
||||
.thenApply(response -> {
|
||||
Object sessions = response.get("sessions");
|
||||
if (sessions instanceof List) {
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (Object item : (List<?>) sessions) {
|
||||
if (item instanceof Map) {
|
||||
result.add((Map<String, Object>) item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return new ArrayList<>();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get details of a specific session.
|
||||
*
|
||||
* @param sessionId Session ID to retrieve
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing session details map
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> getSession(
|
||||
String sessionId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("GET", "/sessions/" + sessionId, creds[0], creds[1], null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new interactive session.
|
||||
*
|
||||
* @param language Programming language/shell for the session (e.g., "bash", "python3")
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @param opts Optional parameters: network_mode, ttl, shell, multiplexer, vcpu
|
||||
* @return CompletableFuture containing response map with session_id, container_name
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> createSession(
|
||||
String language,
|
||||
String publicKey,
|
||||
String secretKey,
|
||||
Map<String, Object> opts
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("network_mode", "zerotrust");
|
||||
data.put("ttl", 3600);
|
||||
if (language != null && !language.isEmpty()) {
|
||||
data.put("shell", language);
|
||||
}
|
||||
if (opts != null) {
|
||||
data.putAll(opts);
|
||||
}
|
||||
|
||||
return makeRequest("POST", "/sessions", creds[0], creds[1], data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete (terminate) a session.
|
||||
*
|
||||
* @param sessionId Session ID to terminate
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with termination confirmation
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> deleteSession(
|
||||
String sessionId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("DELETE", "/sessions/" + sessionId, creds[0], creds[1], null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeze a session (pause execution, reduce resource consumption).
|
||||
*
|
||||
* @param sessionId Session ID to freeze
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with freeze confirmation
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> freezeSession(
|
||||
String sessionId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/sessions/" + sessionId + "/freeze", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfreeze a session (resume execution).
|
||||
*
|
||||
* @param sessionId Session ID to unfreeze
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with unfreeze confirmation
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> unfreezeSession(
|
||||
String sessionId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/sessions/" + sessionId + "/unfreeze", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Boost a session's resources (increase vCPU and memory).
|
||||
*
|
||||
* @param sessionId Session ID to boost
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with boost confirmation
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> boostSession(
|
||||
String sessionId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("vcpu", 2);
|
||||
return makeRequest("POST", "/sessions/" + sessionId + "/boost", creds[0], creds[1], data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove boost from a session (return to base resources).
|
||||
*
|
||||
* @param sessionId Session ID to unboost
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with unboost confirmation
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> unboostSession(
|
||||
String sessionId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/sessions/" + sessionId + "/unboost", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a shell command in an existing session.
|
||||
*
|
||||
* @param sessionId Session ID to execute command in
|
||||
* @param command Shell command to execute
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with command output
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> shellSession(
|
||||
String sessionId,
|
||||
String command,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("command", command);
|
||||
return makeRequest("POST", "/sessions/" + sessionId + "/shell", creds[0], creds[1], data);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Service API Methods
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* List all services for the authenticated account.
|
||||
*
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing list of service maps
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static CompletableFuture<List<Map<String, Object>>> listServices(
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("GET", "/services", creds[0], creds[1], null)
|
||||
.thenApply(response -> {
|
||||
Object services = response.get("services");
|
||||
if (services instanceof List) {
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (Object item : (List<?>) services) {
|
||||
if (item instanceof Map) {
|
||||
result.add((Map<String, Object>) item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return new ArrayList<>();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new persistent service.
|
||||
*
|
||||
* @param name Service name
|
||||
* @param ports Comma-separated list of ports to expose (e.g., "80,443")
|
||||
* @param bootstrap Bootstrap script or URL to run on service creation
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with service_id
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> createService(
|
||||
String name,
|
||||
String ports,
|
||||
String bootstrap,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("name", name);
|
||||
if (ports != null && !ports.isEmpty()) {
|
||||
List<Integer> portList = new ArrayList<>();
|
||||
for (String p : ports.split(",")) {
|
||||
try {
|
||||
portList.add(Integer.parseInt(p.trim()));
|
||||
} catch (NumberFormatException e) {
|
||||
// Skip invalid port
|
||||
}
|
||||
}
|
||||
data.put("ports", portList);
|
||||
}
|
||||
if (bootstrap != null && !bootstrap.isEmpty()) {
|
||||
if (bootstrap.startsWith("http://") || bootstrap.startsWith("https://")) {
|
||||
data.put("bootstrap_url", bootstrap);
|
||||
} else {
|
||||
data.put("bootstrap", bootstrap);
|
||||
}
|
||||
}
|
||||
|
||||
return makeRequest("POST", "/services", creds[0], creds[1], data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get details of a specific service.
|
||||
*
|
||||
* @param serviceId Service ID to retrieve
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing service details map
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> getService(
|
||||
String serviceId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("GET", "/services/" + serviceId, creds[0], creds[1], null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a service's configuration.
|
||||
*
|
||||
* @param serviceId Service ID to update
|
||||
* @param opts Update options (e.g., vcpu for resizing)
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with update confirmation
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> updateService(
|
||||
String serviceId,
|
||||
Map<String, Object> opts,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequestWithMethod("PATCH", "/services/" + serviceId, creds[0], creds[1], opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete (destroy) a service.
|
||||
*
|
||||
* @param serviceId Service ID to destroy
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with deletion confirmation
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> deleteService(
|
||||
String serviceId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("DELETE", "/services/" + serviceId, creds[0], creds[1], null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeze a service (pause execution, reduce resource consumption).
|
||||
*
|
||||
* @param serviceId Service ID to freeze
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with freeze confirmation
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> freezeService(
|
||||
String serviceId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/services/" + serviceId + "/freeze", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfreeze a service (resume execution).
|
||||
*
|
||||
* @param serviceId Service ID to unfreeze
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with unfreeze confirmation
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> unfreezeService(
|
||||
String serviceId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/services/" + serviceId + "/unfreeze", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock a service (prevent modifications and termination).
|
||||
*
|
||||
* @param serviceId Service ID to lock
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with lock confirmation
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> lockService(
|
||||
String serviceId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/services/" + serviceId + "/lock", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock a service (allow modifications and termination).
|
||||
*
|
||||
* @param serviceId Service ID to unlock
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with unlock confirmation
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> unlockService(
|
||||
String serviceId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/services/" + serviceId + "/unlock", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bootstrap logs for a service.
|
||||
*
|
||||
* @param serviceId Service ID to get logs for
|
||||
* @param all If true, return all logs; if false, return recent logs only
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with logs
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> getServiceLogs(
|
||||
String serviceId,
|
||||
boolean all,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
String path = "/services/" + serviceId + "/logs";
|
||||
if (all) {
|
||||
path += "?all=true";
|
||||
}
|
||||
return makeRequest("GET", path, creds[0], creds[1], null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get environment vault status for a service.
|
||||
*
|
||||
* @param serviceId Service ID to get env status for
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with has_vault, count, updated_at
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> getServiceEnv(
|
||||
String serviceId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("GET", "/services/" + serviceId + "/env", creds[0], creds[1], null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set environment vault for a service.
|
||||
*
|
||||
* @param serviceId Service ID to set env for
|
||||
* @param env Environment variables map (KEY=VALUE pairs)
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with set confirmation
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> setServiceEnv(
|
||||
String serviceId,
|
||||
Map<String, String> env,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("env", env);
|
||||
return makeRequest("POST", "/services/" + serviceId + "/env", creds[0], creds[1], data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete environment variables from a service's vault.
|
||||
*
|
||||
* @param serviceId Service ID to delete env from
|
||||
* @param keys List of keys to delete (null to delete entire vault)
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with deletion confirmation
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> deleteServiceEnv(
|
||||
String serviceId,
|
||||
List<String> keys,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
String path = "/services/" + serviceId + "/env";
|
||||
if (keys != null && !keys.isEmpty()) {
|
||||
path += "?keys=" + String.join(",", keys);
|
||||
}
|
||||
return makeRequest("DELETE", path, creds[0], creds[1], null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export environment vault for a service (returns decrypted values).
|
||||
*
|
||||
* @param serviceId Service ID to export env from
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with exported environment variables
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> exportServiceEnv(
|
||||
String serviceId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/services/" + serviceId + "/env/export", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Redeploy a service (re-run bootstrap script).
|
||||
*
|
||||
* @param serviceId Service ID to redeploy
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with redeploy confirmation
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> redeployService(
|
||||
String serviceId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/services/" + serviceId + "/redeploy", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command in a service container.
|
||||
*
|
||||
* @param serviceId Service ID to execute command in
|
||||
* @param command Command to execute
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with stdout, stderr, exit_code
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> executeInService(
|
||||
String serviceId,
|
||||
String command,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("command", command);
|
||||
return makeRequest("POST", "/services/" + serviceId + "/execute", creds[0], creds[1], data);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Additional Snapshot API Methods
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* Lock a snapshot (prevent deletion).
|
||||
*
|
||||
* @param snapshotId Snapshot ID to lock
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with lock confirmation
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> lockSnapshot(
|
||||
String snapshotId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/snapshots/" + snapshotId + "/lock", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock a snapshot (allow deletion).
|
||||
*
|
||||
* @param snapshotId Snapshot ID to unlock
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with unlock confirmation
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> unlockSnapshot(
|
||||
String snapshotId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/snapshots/" + snapshotId + "/unlock", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone a snapshot to create a new snapshot with a different name.
|
||||
*
|
||||
* @param snapshotId Snapshot ID to clone
|
||||
* @param name Name for the cloned snapshot
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return CompletableFuture containing response map with new snapshot_id
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> cloneSnapshot(
|
||||
String snapshotId,
|
||||
String name,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
if (name != null && !name.isEmpty()) {
|
||||
data.put("name", name);
|
||||
}
|
||||
return makeRequest("POST", "/snapshots/" + snapshotId + "/clone", creds[0], creds[1], data);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Key Validation API
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* Validate API key credentials.
|
||||
*
|
||||
* @param publicKey API public key to validate
|
||||
* @param secretKey API secret key to validate
|
||||
* @return CompletableFuture containing response map with validation result (valid, tier, etc.)
|
||||
*/
|
||||
public static CompletableFuture<Map<String, Object>> validateKeys(
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/keys/validate", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// HTTP Request Helpers
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* Make an HTTP request with a specified method (supports PATCH).
|
||||
*/
|
||||
private static CompletableFuture<Map<String, Object>> makeRequestWithMethod(
|
||||
String method,
|
||||
String path,
|
||||
String publicKey,
|
||||
String secretKey,
|
||||
Map<String, Object> data
|
||||
) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
return makeRequestSyncWithMethod(method, path, publicKey, secretKey, data);
|
||||
} catch (IOException e) {
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
}, executor);
|
||||
}
|
||||
|
||||
private static Map<String, Object> makeRequestSyncWithMethod(
|
||||
String method,
|
||||
String path,
|
||||
String publicKey,
|
||||
String secretKey,
|
||||
Map<String, Object> data
|
||||
) throws IOException {
|
||||
String url = API_BASE + path;
|
||||
long timestamp = System.currentTimeMillis() / 1000;
|
||||
String body = (data != null) ? mapToJson(data) : "";
|
||||
|
||||
String signature = signRequest(secretKey, timestamp, method, path, data != null ? body : null);
|
||||
|
||||
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
|
||||
conn.setRequestMethod(method);
|
||||
conn.setConnectTimeout(DEFAULT_TIMEOUT_MS);
|
||||
conn.setReadTimeout(DEFAULT_TIMEOUT_MS);
|
||||
|
||||
conn.setRequestProperty("Authorization", "Bearer " + publicKey);
|
||||
conn.setRequestProperty("X-Timestamp", String.valueOf(timestamp));
|
||||
conn.setRequestProperty("X-Signature", signature);
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
|
||||
if (data != null) {
|
||||
conn.setDoOutput(true);
|
||||
try (OutputStream os = conn.getOutputStream()) {
|
||||
os.write(body.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
String responseBody;
|
||||
|
||||
InputStream inputStream = (responseCode >= 200 && responseCode < 300)
|
||||
? conn.getInputStream()
|
||||
: conn.getErrorStream();
|
||||
|
||||
if (inputStream == null) {
|
||||
responseBody = "";
|
||||
} else {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
responseBody = sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (responseCode < 200 || responseCode >= 300) {
|
||||
throw new ApiException(
|
||||
"API request failed with status " + responseCode,
|
||||
responseCode,
|
||||
responseBody
|
||||
);
|
||||
}
|
||||
|
||||
return parseJson(responseBody);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the executor services used by this class.
|
||||
* Call this when your application is shutting down.
|
||||
|
|
|
|||
|
|
@ -1050,4 +1050,761 @@ public class Un {
|
|||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("DELETE", "/snapshots/" + snapshotId, creds[0], creds[1], null);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Session API Methods
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* List all active sessions for the authenticated account.
|
||||
*
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return List of session maps containing id, container_name, shell, status, remaining_ttl
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<Map<String, Object>> listSessions(
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
Map<String, Object> response = makeRequest("GET", "/sessions", creds[0], creds[1], null);
|
||||
Object sessions = response.get("sessions");
|
||||
if (sessions instanceof List) {
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (Object item : (List<?>) sessions) {
|
||||
if (item instanceof Map) {
|
||||
result.add((Map<String, Object>) item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get details of a specific session.
|
||||
*
|
||||
* @param sessionId Session ID to retrieve
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Session details map
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> getSession(
|
||||
String sessionId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("GET", "/sessions/" + sessionId, creds[0], creds[1], null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new interactive session.
|
||||
*
|
||||
* @param language Programming language/shell for the session (e.g., "bash", "python3")
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @param opts Optional parameters: network_mode, ttl, shell, multiplexer, vcpu
|
||||
* @return Response map containing session_id, container_name
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> createSession(
|
||||
String language,
|
||||
String publicKey,
|
||||
String secretKey,
|
||||
Map<String, Object> opts
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("network_mode", "zerotrust");
|
||||
data.put("ttl", 3600);
|
||||
if (language != null && !language.isEmpty()) {
|
||||
data.put("shell", language);
|
||||
}
|
||||
if (opts != null) {
|
||||
data.putAll(opts);
|
||||
}
|
||||
|
||||
return makeRequest("POST", "/sessions", creds[0], creds[1], data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete (terminate) a session.
|
||||
*
|
||||
* @param sessionId Session ID to terminate
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map with termination confirmation
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> deleteSession(
|
||||
String sessionId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("DELETE", "/sessions/" + sessionId, creds[0], creds[1], null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeze a session (pause execution, reduce resource consumption).
|
||||
*
|
||||
* @param sessionId Session ID to freeze
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map with freeze confirmation
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> freezeSession(
|
||||
String sessionId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/sessions/" + sessionId + "/freeze", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfreeze a session (resume execution).
|
||||
*
|
||||
* @param sessionId Session ID to unfreeze
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map with unfreeze confirmation
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> unfreezeSession(
|
||||
String sessionId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/sessions/" + sessionId + "/unfreeze", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Boost a session's resources (increase vCPU and memory).
|
||||
*
|
||||
* @param sessionId Session ID to boost
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map with boost confirmation
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> boostSession(
|
||||
String sessionId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("vcpu", 2);
|
||||
return makeRequest("POST", "/sessions/" + sessionId + "/boost", creds[0], creds[1], data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove boost from a session (return to base resources).
|
||||
*
|
||||
* @param sessionId Session ID to unboost
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map with unboost confirmation
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> unboostSession(
|
||||
String sessionId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/sessions/" + sessionId + "/unboost", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a shell command in an existing session.
|
||||
*
|
||||
* @param sessionId Session ID to execute command in
|
||||
* @param command Shell command to execute
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map with command output
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> shellSession(
|
||||
String sessionId,
|
||||
String command,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("command", command);
|
||||
return makeRequest("POST", "/sessions/" + sessionId + "/shell", creds[0], creds[1], data);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Service API Methods
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* List all services for the authenticated account.
|
||||
*
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return List of service maps containing id, name, state, ports, disk_used
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<Map<String, Object>> listServices(
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
Map<String, Object> response = makeRequest("GET", "/services", creds[0], creds[1], null);
|
||||
Object services = response.get("services");
|
||||
if (services instanceof List) {
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (Object item : (List<?>) services) {
|
||||
if (item instanceof Map) {
|
||||
result.add((Map<String, Object>) item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new persistent service.
|
||||
*
|
||||
* @param name Service name
|
||||
* @param ports Comma-separated list of ports to expose (e.g., "80,443")
|
||||
* @param bootstrap Bootstrap script or URL to run on service creation
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map containing service_id
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> createService(
|
||||
String name,
|
||||
String ports,
|
||||
String bootstrap,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("name", name);
|
||||
if (ports != null && !ports.isEmpty()) {
|
||||
// Parse ports string into list
|
||||
List<Integer> portList = new ArrayList<>();
|
||||
for (String p : ports.split(",")) {
|
||||
try {
|
||||
portList.add(Integer.parseInt(p.trim()));
|
||||
} catch (NumberFormatException e) {
|
||||
// Skip invalid port
|
||||
}
|
||||
}
|
||||
data.put("ports", portList);
|
||||
}
|
||||
if (bootstrap != null && !bootstrap.isEmpty()) {
|
||||
if (bootstrap.startsWith("http://") || bootstrap.startsWith("https://")) {
|
||||
data.put("bootstrap_url", bootstrap);
|
||||
} else {
|
||||
data.put("bootstrap", bootstrap);
|
||||
}
|
||||
}
|
||||
|
||||
return makeRequest("POST", "/services", creds[0], creds[1], data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get details of a specific service.
|
||||
*
|
||||
* @param serviceId Service ID to retrieve
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Service details map
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> getService(
|
||||
String serviceId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("GET", "/services/" + serviceId, creds[0], creds[1], null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a service's configuration.
|
||||
*
|
||||
* @param serviceId Service ID to update
|
||||
* @param opts Update options (e.g., vcpu for resizing)
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map with update confirmation
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> updateService(
|
||||
String serviceId,
|
||||
Map<String, Object> opts,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequestWithMethod("PATCH", "/services/" + serviceId, creds[0], creds[1], opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete (destroy) a service.
|
||||
*
|
||||
* @param serviceId Service ID to destroy
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map with deletion confirmation
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> deleteService(
|
||||
String serviceId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("DELETE", "/services/" + serviceId, creds[0], creds[1], null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeze a service (pause execution, reduce resource consumption).
|
||||
*
|
||||
* @param serviceId Service ID to freeze
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map with freeze confirmation
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> freezeService(
|
||||
String serviceId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/services/" + serviceId + "/freeze", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfreeze a service (resume execution).
|
||||
*
|
||||
* @param serviceId Service ID to unfreeze
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map with unfreeze confirmation
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> unfreezeService(
|
||||
String serviceId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/services/" + serviceId + "/unfreeze", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock a service (prevent modifications and termination).
|
||||
*
|
||||
* @param serviceId Service ID to lock
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map with lock confirmation
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> lockService(
|
||||
String serviceId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/services/" + serviceId + "/lock", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock a service (allow modifications and termination).
|
||||
*
|
||||
* @param serviceId Service ID to unlock
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map with unlock confirmation
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> unlockService(
|
||||
String serviceId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/services/" + serviceId + "/unlock", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bootstrap logs for a service.
|
||||
*
|
||||
* @param serviceId Service ID to get logs for
|
||||
* @param all If true, return all logs; if false, return recent logs only
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map containing logs
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> getServiceLogs(
|
||||
String serviceId,
|
||||
boolean all,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
String path = "/services/" + serviceId + "/logs";
|
||||
if (all) {
|
||||
path += "?all=true";
|
||||
}
|
||||
return makeRequest("GET", path, creds[0], creds[1], null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get environment vault status for a service.
|
||||
*
|
||||
* @param serviceId Service ID to get env status for
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map containing has_vault, count, updated_at
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> getServiceEnv(
|
||||
String serviceId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("GET", "/services/" + serviceId + "/env", creds[0], creds[1], null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set environment vault for a service.
|
||||
*
|
||||
* @param serviceId Service ID to set env for
|
||||
* @param env Environment variables map (KEY=VALUE pairs)
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map with set confirmation
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> setServiceEnv(
|
||||
String serviceId,
|
||||
Map<String, String> env,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("env", env);
|
||||
return makeRequest("POST", "/services/" + serviceId + "/env", creds[0], creds[1], data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete environment variables from a service's vault.
|
||||
*
|
||||
* @param serviceId Service ID to delete env from
|
||||
* @param keys List of keys to delete (null to delete entire vault)
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map with deletion confirmation
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> deleteServiceEnv(
|
||||
String serviceId,
|
||||
List<String> keys,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
String path = "/services/" + serviceId + "/env";
|
||||
if (keys != null && !keys.isEmpty()) {
|
||||
// URL encode keys parameter
|
||||
path += "?keys=" + String.join(",", keys);
|
||||
}
|
||||
return makeRequest("DELETE", path, creds[0], creds[1], null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export environment vault for a service (returns decrypted values).
|
||||
*
|
||||
* @param serviceId Service ID to export env from
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map containing exported environment variables
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> exportServiceEnv(
|
||||
String serviceId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/services/" + serviceId + "/env/export", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Redeploy a service (re-run bootstrap script).
|
||||
*
|
||||
* @param serviceId Service ID to redeploy
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map with redeploy confirmation
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> redeployService(
|
||||
String serviceId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/services/" + serviceId + "/redeploy", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command in a service container.
|
||||
*
|
||||
* @param serviceId Service ID to execute command in
|
||||
* @param command Command to execute
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map containing stdout, stderr, exit_code
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> executeInService(
|
||||
String serviceId,
|
||||
String command,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("command", command);
|
||||
return makeRequest("POST", "/services/" + serviceId + "/execute", creds[0], creds[1], data);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Additional Snapshot API Methods
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* Lock a snapshot (prevent deletion).
|
||||
*
|
||||
* @param snapshotId Snapshot ID to lock
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map with lock confirmation
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> lockSnapshot(
|
||||
String snapshotId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/snapshots/" + snapshotId + "/lock", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock a snapshot (allow deletion).
|
||||
*
|
||||
* @param snapshotId Snapshot ID to unlock
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map with unlock confirmation
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> unlockSnapshot(
|
||||
String snapshotId,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest("POST", "/snapshots/" + snapshotId + "/unlock", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone a snapshot to create a new snapshot with a different name.
|
||||
*
|
||||
* @param snapshotId Snapshot ID to clone
|
||||
* @param name Name for the cloned snapshot
|
||||
* @param publicKey Optional API key
|
||||
* @param secretKey Optional API secret
|
||||
* @return Response map containing new snapshot_id
|
||||
* @throws IOException on network errors
|
||||
* @throws CredentialsException if credentials cannot be found
|
||||
* @throws ApiException if API returns an error
|
||||
*/
|
||||
public static Map<String, Object> cloneSnapshot(
|
||||
String snapshotId,
|
||||
String name,
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
if (name != null && !name.isEmpty()) {
|
||||
data.put("name", name);
|
||||
}
|
||||
return makeRequest("POST", "/snapshots/" + snapshotId + "/clone", creds[0], creds[1], data);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Key Validation API
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* Validate API key credentials.
|
||||
*
|
||||
* @param publicKey API public key to validate
|
||||
* @param secretKey API secret key to validate
|
||||
* @return Response map with validation result (valid, tier, etc.)
|
||||
* @throws IOException on network errors
|
||||
* @throws ApiException if API returns an error (including invalid credentials)
|
||||
*/
|
||||
public static Map<String, Object> validateKeys(
|
||||
String publicKey,
|
||||
String secretKey
|
||||
) throws IOException {
|
||||
String[] creds = resolveCredentials(publicKey, secretKey);
|
||||
// Note: validateKeys uses POST to /keys/validate
|
||||
return makeRequest("POST", "/keys/validate", creds[0], creds[1], new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// HTTP Request Helpers
|
||||
// ========================================================================
|
||||
|
||||
/**
|
||||
* Make an HTTP request with a specified method (supports PATCH).
|
||||
*/
|
||||
private static Map<String, Object> makeRequestWithMethod(
|
||||
String method,
|
||||
String path,
|
||||
String publicKey,
|
||||
String secretKey,
|
||||
Map<String, Object> data
|
||||
) throws IOException {
|
||||
String url = API_BASE + path;
|
||||
long timestamp = System.currentTimeMillis() / 1000;
|
||||
String body = (data != null) ? mapToJson(data) : "";
|
||||
|
||||
String signature = signRequest(secretKey, timestamp, method, path, data != null ? body : null);
|
||||
|
||||
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
|
||||
conn.setRequestMethod(method);
|
||||
conn.setConnectTimeout(DEFAULT_TIMEOUT_MS);
|
||||
conn.setReadTimeout(DEFAULT_TIMEOUT_MS);
|
||||
|
||||
conn.setRequestProperty("Authorization", "Bearer " + publicKey);
|
||||
conn.setRequestProperty("X-Timestamp", String.valueOf(timestamp));
|
||||
conn.setRequestProperty("X-Signature", signature);
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
|
||||
if (data != null) {
|
||||
conn.setDoOutput(true);
|
||||
try (OutputStream os = conn.getOutputStream()) {
|
||||
os.write(body.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
String responseBody;
|
||||
|
||||
InputStream inputStream = (responseCode >= 200 && responseCode < 300)
|
||||
? conn.getInputStream()
|
||||
: conn.getErrorStream();
|
||||
|
||||
if (inputStream == null) {
|
||||
responseBody = "";
|
||||
} else {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
responseBody = sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (responseCode < 200 || responseCode >= 300) {
|
||||
throw new ApiException(
|
||||
"API request failed with status " + responseCode,
|
||||
responseCode,
|
||||
responseBody
|
||||
);
|
||||
}
|
||||
|
||||
return parseJson(responseBody);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,19 +5,22 @@
|
|||
*
|
||||
* Library Usage:
|
||||
* import {
|
||||
* executeCode,
|
||||
* executeAsync,
|
||||
* getJob,
|
||||
* waitForJob,
|
||||
* cancelJob,
|
||||
* listJobs,
|
||||
* getLanguages,
|
||||
* detectLanguage,
|
||||
* sessionSnapshot,
|
||||
* serviceSnapshot,
|
||||
* listSnapshots,
|
||||
* restoreSnapshot,
|
||||
* deleteSnapshot,
|
||||
* // Code execution
|
||||
* executeCode, executeAsync, getJob, waitForJob, cancelJob, listJobs,
|
||||
* getLanguages, detectLanguage,
|
||||
* // Session management
|
||||
* listSessions, getSession, createSession, deleteSession,
|
||||
* freezeSession, unfreezeSession, boostSession, unboostSession, shellSession,
|
||||
* // Service management
|
||||
* listServices, createService, getService, updateService, deleteService,
|
||||
* freezeService, unfreezeService, lockService, unlockService,
|
||||
* getServiceLogs, getServiceEnv, setServiceEnv, deleteServiceEnv,
|
||||
* exportServiceEnv, redeployService, executeInService,
|
||||
* // Snapshot management
|
||||
* sessionSnapshot, serviceSnapshot, listSnapshots, restoreSnapshot,
|
||||
* deleteSnapshot, lockSnapshot, unlockSnapshot, cloneSnapshot,
|
||||
* // Key validation
|
||||
* validateKeys,
|
||||
* } from './un_async.js';
|
||||
*
|
||||
* // Execute code (awaits until completion)
|
||||
|
|
@ -220,7 +223,8 @@ async function makeRequest(method, urlPath, publicKey, secretKey, data) {
|
|||
signal: AbortSignal.timeout(120000), // 120 seconds timeout
|
||||
};
|
||||
|
||||
if (method === 'POST' && body) {
|
||||
// Add body for methods that support it
|
||||
if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(method) && body) {
|
||||
options.body = body;
|
||||
}
|
||||
|
||||
|
|
@ -581,8 +585,491 @@ async function deleteSnapshot(snapshotId, publicKey, secretKey) {
|
|||
return makeRequest('DELETE', `/snapshots/${snapshotId}`, publicKey, secretKey);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Session Management Functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* List all active sessions.
|
||||
*
|
||||
* Returns: Promise<Array> (list of session objects)
|
||||
*/
|
||||
async function listSessions(publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
const response = await makeRequest('GET', '/sessions', publicKey, secretKey);
|
||||
return response.sessions || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get details of a specific session.
|
||||
*
|
||||
* Args:
|
||||
* sessionId: Session ID to retrieve
|
||||
*
|
||||
* Returns: Promise<Object> (session details)
|
||||
*/
|
||||
async function getSession(sessionId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('GET', `/sessions/${sessionId}`, publicKey, secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new interactive session.
|
||||
*
|
||||
* Args:
|
||||
* language: Optional programming language/shell (default: "bash")
|
||||
* opts: Optional settings:
|
||||
* - networkMode: "zerotrust" (default) or "semitrusted"
|
||||
* - shell: Shell to use (e.g., "python3", "bash")
|
||||
* - multiplexer: "tmux", "screen", or null
|
||||
* - vcpu: Number of vCPUs (1-8)
|
||||
* - ttl: Time-to-live in seconds
|
||||
*
|
||||
* Returns: Promise<Object> (session info with session_id, container_name)
|
||||
*/
|
||||
async function createSession(language, opts = {}, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
const data = {
|
||||
network_mode: opts.networkMode || 'zerotrust',
|
||||
ttl: opts.ttl || 3600,
|
||||
};
|
||||
if (language) data.shell = language;
|
||||
if (opts.shell) data.shell = opts.shell;
|
||||
if (opts.multiplexer) data.multiplexer = opts.multiplexer;
|
||||
if (opts.vcpu && opts.vcpu > 1) data.vcpu = opts.vcpu;
|
||||
|
||||
return makeRequest('POST', '/sessions', publicKey, secretKey, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete/terminate a session.
|
||||
*
|
||||
* Args:
|
||||
* sessionId: Session ID to terminate
|
||||
*
|
||||
* Returns: Promise<Object> (deletion confirmation)
|
||||
*/
|
||||
async function deleteSession(sessionId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('DELETE', `/sessions/${sessionId}`, publicKey, secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeze a session (pause execution, preserve state).
|
||||
*
|
||||
* Args:
|
||||
* sessionId: Session ID to freeze
|
||||
*
|
||||
* Returns: Promise<Object> (freeze confirmation)
|
||||
*/
|
||||
async function freezeSession(sessionId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/sessions/${sessionId}/freeze`, publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfreeze a session (resume execution).
|
||||
*
|
||||
* Args:
|
||||
* sessionId: Session ID to unfreeze
|
||||
*
|
||||
* Returns: Promise<Object> (unfreeze confirmation)
|
||||
*/
|
||||
async function unfreezeSession(sessionId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/sessions/${sessionId}/unfreeze`, publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Boost a session's resources (increase vCPU, memory).
|
||||
*
|
||||
* Args:
|
||||
* sessionId: Session ID to boost
|
||||
* vcpu: Number of vCPUs (default: 2)
|
||||
*
|
||||
* Returns: Promise<Object> (boost confirmation)
|
||||
*/
|
||||
async function boostSession(sessionId, vcpu = 2, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/sessions/${sessionId}/boost`, publicKey, secretKey, { vcpu });
|
||||
}
|
||||
|
||||
/**
|
||||
* Unboost a session (return to base resources).
|
||||
*
|
||||
* Args:
|
||||
* sessionId: Session ID to unboost
|
||||
*
|
||||
* Returns: Promise<Object> (unboost confirmation)
|
||||
*/
|
||||
async function unboostSession(sessionId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/sessions/${sessionId}/unboost`, publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a shell command in a session.
|
||||
*
|
||||
* Note: This initiates a WebSocket connection for interactive shell.
|
||||
* For simple command execution, this sends the command via the shell endpoint.
|
||||
*
|
||||
* Args:
|
||||
* sessionId: Session ID
|
||||
* command: Command to execute
|
||||
*
|
||||
* Returns: Promise<Object> (command result)
|
||||
*/
|
||||
async function shellSession(sessionId, command, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/sessions/${sessionId}/shell`, publicKey, secretKey, { command });
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Service Management Functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* List all services.
|
||||
*
|
||||
* Returns: Promise<Array> (list of service objects)
|
||||
*/
|
||||
async function listServices(publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
const response = await makeRequest('GET', '/services', publicKey, secretKey);
|
||||
return response.services || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new service (persistent container).
|
||||
*
|
||||
* Args:
|
||||
* name: Service name
|
||||
* ports: Array of port numbers to expose (e.g., [80, 443])
|
||||
* bootstrap: Bootstrap script content or URL
|
||||
* opts: Optional settings:
|
||||
* - networkMode: "zerotrust" or "semitrusted"
|
||||
* - vcpu: Number of vCPUs (1-8)
|
||||
* - domains: Array of custom domains
|
||||
* - serviceType: Service type for SRV records (minecraft, mumble, etc.)
|
||||
*
|
||||
* Returns: Promise<Object> (service info with service_id)
|
||||
*/
|
||||
async function createService(name, ports, bootstrap, opts = {}, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
const data = {};
|
||||
if (name) data.name = name;
|
||||
if (ports && ports.length > 0) data.ports = ports;
|
||||
if (bootstrap) {
|
||||
// If bootstrap starts with http, treat as URL, otherwise as content
|
||||
if (bootstrap.startsWith('http://') || bootstrap.startsWith('https://')) {
|
||||
data.bootstrap = bootstrap;
|
||||
} else {
|
||||
data.bootstrap_content = bootstrap;
|
||||
}
|
||||
}
|
||||
if (opts.networkMode) data.network_mode = opts.networkMode;
|
||||
if (opts.vcpu && opts.vcpu > 1) data.vcpu = opts.vcpu;
|
||||
if (opts.domains) data.custom_domains = opts.domains;
|
||||
if (opts.serviceType) data.service_type = opts.serviceType;
|
||||
|
||||
return makeRequest('POST', '/services', publicKey, secretKey, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get details of a specific service.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID to retrieve
|
||||
*
|
||||
* Returns: Promise<Object> (service details)
|
||||
*/
|
||||
async function getService(serviceId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('GET', `/services/${serviceId}`, publicKey, secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a service (resize vCPU/memory).
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID to update
|
||||
* opts: Update options:
|
||||
* - vcpu: New vCPU count (1-8)
|
||||
*
|
||||
* Returns: Promise<Object> (update confirmation)
|
||||
*/
|
||||
async function updateService(serviceId, opts = {}, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
const data = {};
|
||||
if (opts.vcpu) data.vcpu = opts.vcpu;
|
||||
return makeRequest('PATCH', `/services/${serviceId}`, publicKey, secretKey, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete/destroy a service.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID to destroy
|
||||
*
|
||||
* Returns: Promise<Object> (deletion confirmation)
|
||||
*/
|
||||
async function deleteService(serviceId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('DELETE', `/services/${serviceId}`, publicKey, secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeze a service (stop container, preserve disk).
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID to freeze
|
||||
*
|
||||
* Returns: Promise<Object> (freeze confirmation)
|
||||
*/
|
||||
async function freezeService(serviceId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/services/${serviceId}/freeze`, publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfreeze a service (restart container).
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID to unfreeze
|
||||
*
|
||||
* Returns: Promise<Object> (unfreeze confirmation)
|
||||
*/
|
||||
async function unfreezeService(serviceId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/services/${serviceId}/unfreeze`, publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock a service to prevent deletion.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID to lock
|
||||
*
|
||||
* Returns: Promise<Object> (lock confirmation)
|
||||
*/
|
||||
async function lockService(serviceId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/services/${serviceId}/lock`, publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock a service to allow deletion.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID to unlock
|
||||
*
|
||||
* Returns: Promise<Object> (unlock confirmation)
|
||||
*/
|
||||
async function unlockService(serviceId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/services/${serviceId}/unlock`, publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get service logs.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID
|
||||
* all: If true, get all logs; if false, get last ~9000 lines (default: false)
|
||||
*
|
||||
* Returns: Promise<Object> (log data)
|
||||
*/
|
||||
async function getServiceLogs(serviceId, all = false, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
const path = all ? `/services/${serviceId}/logs?all=true` : `/services/${serviceId}/logs`;
|
||||
return makeRequest('GET', path, publicKey, secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get service environment vault status.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID
|
||||
*
|
||||
* Returns: Promise<Object> (vault status with has_vault, count, updated_at)
|
||||
*/
|
||||
async function getServiceEnv(serviceId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('GET', `/services/${serviceId}/env`, publicKey, secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set service environment vault.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID
|
||||
* env: Environment content as string (KEY=VALUE format, newline separated)
|
||||
* or object { KEY: "value", KEY2: "value2" }
|
||||
*
|
||||
* Returns: Promise<Object> (set confirmation)
|
||||
*/
|
||||
async function setServiceEnv(serviceId, env, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
// Convert object to KEY=VALUE format if needed
|
||||
let envContent = env;
|
||||
if (typeof env === 'object' && !Array.isArray(env)) {
|
||||
envContent = Object.entries(env)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join('\n');
|
||||
}
|
||||
// Note: This endpoint uses PUT with text/plain body
|
||||
// The makeRequest function sends JSON, so we need to handle this specially
|
||||
return makeRequest('PUT', `/services/${serviceId}/env`, publicKey, secretKey, { content: envContent });
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete service environment vault.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID
|
||||
* keys: Optional array of specific keys to delete (deletes all if not specified)
|
||||
*
|
||||
* Returns: Promise<Object> (deletion confirmation)
|
||||
*/
|
||||
async function deleteServiceEnv(serviceId, keys = null, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
const data = keys ? { keys } : {};
|
||||
return makeRequest('DELETE', `/services/${serviceId}/env`, publicKey, secretKey, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export service environment vault.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID
|
||||
*
|
||||
* Returns: Promise<Object> (exported environment data)
|
||||
*/
|
||||
async function exportServiceEnv(serviceId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/services/${serviceId}/env/export`, publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Redeploy a service with new bootstrap script.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID to redeploy
|
||||
* bootstrap: Optional new bootstrap script content or URL
|
||||
*
|
||||
* Returns: Promise<Object> (redeploy confirmation)
|
||||
*/
|
||||
async function redeployService(serviceId, bootstrap = null, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
const data = {};
|
||||
if (bootstrap) {
|
||||
if (bootstrap.startsWith('http://') || bootstrap.startsWith('https://')) {
|
||||
data.bootstrap = bootstrap;
|
||||
} else {
|
||||
data.bootstrap_content = bootstrap;
|
||||
}
|
||||
}
|
||||
return makeRequest('POST', `/services/${serviceId}/redeploy`, publicKey, secretKey, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command in a running service container.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID
|
||||
* command: Command to execute
|
||||
* timeout: Optional timeout in milliseconds (default: 30000)
|
||||
*
|
||||
* Returns: Promise<Object> (execution result with stdout, stderr, exit_code)
|
||||
*/
|
||||
async function executeInService(serviceId, command, timeout = 30000, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
const response = await makeRequest('POST', `/services/${serviceId}/execute`, publicKey, secretKey, {
|
||||
command,
|
||||
timeout,
|
||||
});
|
||||
|
||||
// If we got a job_id, poll until completion
|
||||
const jobId = response.job_id;
|
||||
if (jobId) {
|
||||
return waitForJob(jobId, publicKey, secretKey);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Additional Snapshot Functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Lock a snapshot to prevent deletion.
|
||||
*
|
||||
* Args:
|
||||
* snapshotId: Snapshot ID to lock
|
||||
*
|
||||
* Returns: Promise<Object> (lock confirmation)
|
||||
*/
|
||||
async function lockSnapshot(snapshotId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/snapshots/${snapshotId}/lock`, publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock a snapshot to allow deletion.
|
||||
*
|
||||
* Args:
|
||||
* snapshotId: Snapshot ID to unlock
|
||||
*
|
||||
* Returns: Promise<Object> (unlock confirmation)
|
||||
*/
|
||||
async function unlockSnapshot(snapshotId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/snapshots/${snapshotId}/unlock`, publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone a snapshot to create a new session or service.
|
||||
*
|
||||
* Args:
|
||||
* snapshotId: Snapshot ID to clone
|
||||
* name: Name for the new resource
|
||||
* opts: Optional settings:
|
||||
* - type: "session" or "service" (default: inferred from snapshot)
|
||||
* - shell: Shell for session clones
|
||||
* - ports: Ports array for service clones
|
||||
*
|
||||
* Returns: Promise<Object> (clone result with new session_id or service_id)
|
||||
*/
|
||||
async function cloneSnapshot(snapshotId, name, opts = {}, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
const data = {};
|
||||
if (name) data.name = name;
|
||||
if (opts.type) data.type = opts.type;
|
||||
if (opts.shell) data.shell = opts.shell;
|
||||
if (opts.ports) data.ports = opts.ports;
|
||||
return makeRequest('POST', `/snapshots/${snapshotId}/clone`, publicKey, secretKey, data);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Key Validation
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Validate API keys.
|
||||
*
|
||||
* Returns: Promise<Object> (validation result with valid, tier, expires_at, etc.)
|
||||
*/
|
||||
async function validateKeys(publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
// Note: This endpoint is on the portal (unsandbox.com), not the API
|
||||
// For SDK purposes, we'll call the API endpoint if available
|
||||
return makeRequest('POST', '/keys/validate', publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
// ES Module exports
|
||||
export {
|
||||
// Code execution
|
||||
executeCode,
|
||||
executeAsync,
|
||||
getJob,
|
||||
|
|
@ -591,17 +1078,52 @@ export {
|
|||
listJobs,
|
||||
getLanguages,
|
||||
detectLanguage,
|
||||
// Session management
|
||||
listSessions,
|
||||
getSession,
|
||||
createSession,
|
||||
deleteSession,
|
||||
freezeSession,
|
||||
unfreezeSession,
|
||||
boostSession,
|
||||
unboostSession,
|
||||
shellSession,
|
||||
// Service management
|
||||
listServices,
|
||||
createService,
|
||||
getService,
|
||||
updateService,
|
||||
deleteService,
|
||||
freezeService,
|
||||
unfreezeService,
|
||||
lockService,
|
||||
unlockService,
|
||||
getServiceLogs,
|
||||
getServiceEnv,
|
||||
setServiceEnv,
|
||||
deleteServiceEnv,
|
||||
exportServiceEnv,
|
||||
redeployService,
|
||||
executeInService,
|
||||
// Snapshot management
|
||||
sessionSnapshot,
|
||||
serviceSnapshot,
|
||||
listSnapshots,
|
||||
restoreSnapshot,
|
||||
deleteSnapshot,
|
||||
lockSnapshot,
|
||||
unlockSnapshot,
|
||||
cloneSnapshot,
|
||||
// Key validation
|
||||
validateKeys,
|
||||
// Errors
|
||||
CredentialsError,
|
||||
TimeoutError,
|
||||
};
|
||||
|
||||
// Default export for convenience
|
||||
export default {
|
||||
// Code execution
|
||||
executeCode,
|
||||
executeAsync,
|
||||
getJob,
|
||||
|
|
@ -610,11 +1132,45 @@ export default {
|
|||
listJobs,
|
||||
getLanguages,
|
||||
detectLanguage,
|
||||
// Session management
|
||||
listSessions,
|
||||
getSession,
|
||||
createSession,
|
||||
deleteSession,
|
||||
freezeSession,
|
||||
unfreezeSession,
|
||||
boostSession,
|
||||
unboostSession,
|
||||
shellSession,
|
||||
// Service management
|
||||
listServices,
|
||||
createService,
|
||||
getService,
|
||||
updateService,
|
||||
deleteService,
|
||||
freezeService,
|
||||
unfreezeService,
|
||||
lockService,
|
||||
unlockService,
|
||||
getServiceLogs,
|
||||
getServiceEnv,
|
||||
setServiceEnv,
|
||||
deleteServiceEnv,
|
||||
exportServiceEnv,
|
||||
redeployService,
|
||||
executeInService,
|
||||
// Snapshot management
|
||||
sessionSnapshot,
|
||||
serviceSnapshot,
|
||||
listSnapshots,
|
||||
restoreSnapshot,
|
||||
deleteSnapshot,
|
||||
lockSnapshot,
|
||||
unlockSnapshot,
|
||||
cloneSnapshot,
|
||||
// Key validation
|
||||
validateKeys,
|
||||
// Errors
|
||||
CredentialsError,
|
||||
TimeoutError,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,21 +4,24 @@
|
|||
* unsandbox.com JavaScript SDK (Synchronous/Async)
|
||||
*
|
||||
* Library Usage:
|
||||
* import {
|
||||
* executeCode,
|
||||
* executeAsync,
|
||||
* getJob,
|
||||
* waitForJob,
|
||||
* cancelJob,
|
||||
* listJobs,
|
||||
* getLanguages,
|
||||
* detectLanguage,
|
||||
* sessionSnapshot,
|
||||
* serviceSnapshot,
|
||||
* listSnapshots,
|
||||
* restoreSnapshot,
|
||||
* deleteSnapshot,
|
||||
* } from './un.js';
|
||||
* const {
|
||||
* // Code execution
|
||||
* executeCode, executeAsync, getJob, waitForJob, cancelJob, listJobs,
|
||||
* getLanguages, detectLanguage,
|
||||
* // Session management
|
||||
* listSessions, getSession, createSession, deleteSession,
|
||||
* freezeSession, unfreezeSession, boostSession, unboostSession, shellSession,
|
||||
* // Service management
|
||||
* listServices, createService, getService, updateService, deleteService,
|
||||
* freezeService, unfreezeService, lockService, unlockService,
|
||||
* getServiceLogs, getServiceEnv, setServiceEnv, deleteServiceEnv,
|
||||
* exportServiceEnv, redeployService, executeInService,
|
||||
* // Snapshot management
|
||||
* sessionSnapshot, serviceSnapshot, listSnapshots, restoreSnapshot,
|
||||
* deleteSnapshot, lockSnapshot, unlockSnapshot, cloneSnapshot,
|
||||
* // Key validation
|
||||
* validateKeys,
|
||||
* } = require('./un.js');
|
||||
*
|
||||
* // Execute code asynchronously (returns Promise)
|
||||
* const result = await executeCode('python', 'print("hello")', publicKey, secretKey);
|
||||
|
|
@ -201,7 +204,8 @@ function makeRequest(method, path, publicKey, secretKey, data) {
|
|||
timeout: 120000, // 120 seconds
|
||||
};
|
||||
|
||||
if (method === 'POST' && body) {
|
||||
// Set Content-Length for methods with body
|
||||
if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(method) && body) {
|
||||
options.headers['Content-Length'] = Buffer.byteLength(body);
|
||||
}
|
||||
|
||||
|
|
@ -232,7 +236,8 @@ function makeRequest(method, path, publicKey, secretKey, data) {
|
|||
reject(new Error('Request timeout'));
|
||||
});
|
||||
|
||||
if (method === 'POST' && body) {
|
||||
// Write body for methods that support it
|
||||
if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(method) && body) {
|
||||
req.write(body);
|
||||
}
|
||||
|
||||
|
|
@ -559,8 +564,491 @@ async function deleteSnapshot(snapshotId, publicKey, secretKey) {
|
|||
return makeRequest('DELETE', `/snapshots/${snapshotId}`, publicKey, secretKey);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Session Management Functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* List all active sessions.
|
||||
*
|
||||
* Returns: Promise<Array> (list of session objects)
|
||||
*/
|
||||
async function listSessions(publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
const response = await makeRequest('GET', '/sessions', publicKey, secretKey);
|
||||
return response.sessions || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get details of a specific session.
|
||||
*
|
||||
* Args:
|
||||
* sessionId: Session ID to retrieve
|
||||
*
|
||||
* Returns: Promise<Object> (session details)
|
||||
*/
|
||||
async function getSession(sessionId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('GET', `/sessions/${sessionId}`, publicKey, secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new interactive session.
|
||||
*
|
||||
* Args:
|
||||
* language: Optional programming language/shell (default: "bash")
|
||||
* opts: Optional settings:
|
||||
* - networkMode: "zerotrust" (default) or "semitrusted"
|
||||
* - shell: Shell to use (e.g., "python3", "bash")
|
||||
* - multiplexer: "tmux", "screen", or null
|
||||
* - vcpu: Number of vCPUs (1-8)
|
||||
* - ttl: Time-to-live in seconds
|
||||
*
|
||||
* Returns: Promise<Object> (session info with session_id, container_name)
|
||||
*/
|
||||
async function createSession(language, opts = {}, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
const data = {
|
||||
network_mode: opts.networkMode || 'zerotrust',
|
||||
ttl: opts.ttl || 3600,
|
||||
};
|
||||
if (language) data.shell = language;
|
||||
if (opts.shell) data.shell = opts.shell;
|
||||
if (opts.multiplexer) data.multiplexer = opts.multiplexer;
|
||||
if (opts.vcpu && opts.vcpu > 1) data.vcpu = opts.vcpu;
|
||||
|
||||
return makeRequest('POST', '/sessions', publicKey, secretKey, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete/terminate a session.
|
||||
*
|
||||
* Args:
|
||||
* sessionId: Session ID to terminate
|
||||
*
|
||||
* Returns: Promise<Object> (deletion confirmation)
|
||||
*/
|
||||
async function deleteSession(sessionId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('DELETE', `/sessions/${sessionId}`, publicKey, secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeze a session (pause execution, preserve state).
|
||||
*
|
||||
* Args:
|
||||
* sessionId: Session ID to freeze
|
||||
*
|
||||
* Returns: Promise<Object> (freeze confirmation)
|
||||
*/
|
||||
async function freezeSession(sessionId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/sessions/${sessionId}/freeze`, publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfreeze a session (resume execution).
|
||||
*
|
||||
* Args:
|
||||
* sessionId: Session ID to unfreeze
|
||||
*
|
||||
* Returns: Promise<Object> (unfreeze confirmation)
|
||||
*/
|
||||
async function unfreezeSession(sessionId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/sessions/${sessionId}/unfreeze`, publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Boost a session's resources (increase vCPU, memory).
|
||||
*
|
||||
* Args:
|
||||
* sessionId: Session ID to boost
|
||||
* vcpu: Number of vCPUs (default: 2)
|
||||
*
|
||||
* Returns: Promise<Object> (boost confirmation)
|
||||
*/
|
||||
async function boostSession(sessionId, vcpu = 2, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/sessions/${sessionId}/boost`, publicKey, secretKey, { vcpu });
|
||||
}
|
||||
|
||||
/**
|
||||
* Unboost a session (return to base resources).
|
||||
*
|
||||
* Args:
|
||||
* sessionId: Session ID to unboost
|
||||
*
|
||||
* Returns: Promise<Object> (unboost confirmation)
|
||||
*/
|
||||
async function unboostSession(sessionId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/sessions/${sessionId}/unboost`, publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a shell command in a session.
|
||||
*
|
||||
* Note: This initiates a WebSocket connection for interactive shell.
|
||||
* For simple command execution, this sends the command via the shell endpoint.
|
||||
*
|
||||
* Args:
|
||||
* sessionId: Session ID
|
||||
* command: Command to execute
|
||||
*
|
||||
* Returns: Promise<Object> (command result)
|
||||
*/
|
||||
async function shellSession(sessionId, command, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/sessions/${sessionId}/shell`, publicKey, secretKey, { command });
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Service Management Functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* List all services.
|
||||
*
|
||||
* Returns: Promise<Array> (list of service objects)
|
||||
*/
|
||||
async function listServices(publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
const response = await makeRequest('GET', '/services', publicKey, secretKey);
|
||||
return response.services || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new service (persistent container).
|
||||
*
|
||||
* Args:
|
||||
* name: Service name
|
||||
* ports: Array of port numbers to expose (e.g., [80, 443])
|
||||
* bootstrap: Bootstrap script content or URL
|
||||
* opts: Optional settings:
|
||||
* - networkMode: "zerotrust" or "semitrusted"
|
||||
* - vcpu: Number of vCPUs (1-8)
|
||||
* - domains: Array of custom domains
|
||||
* - serviceType: Service type for SRV records (minecraft, mumble, etc.)
|
||||
*
|
||||
* Returns: Promise<Object> (service info with service_id)
|
||||
*/
|
||||
async function createService(name, ports, bootstrap, opts = {}, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
const data = {};
|
||||
if (name) data.name = name;
|
||||
if (ports && ports.length > 0) data.ports = ports;
|
||||
if (bootstrap) {
|
||||
// If bootstrap starts with http, treat as URL, otherwise as content
|
||||
if (bootstrap.startsWith('http://') || bootstrap.startsWith('https://')) {
|
||||
data.bootstrap = bootstrap;
|
||||
} else {
|
||||
data.bootstrap_content = bootstrap;
|
||||
}
|
||||
}
|
||||
if (opts.networkMode) data.network_mode = opts.networkMode;
|
||||
if (opts.vcpu && opts.vcpu > 1) data.vcpu = opts.vcpu;
|
||||
if (opts.domains) data.custom_domains = opts.domains;
|
||||
if (opts.serviceType) data.service_type = opts.serviceType;
|
||||
|
||||
return makeRequest('POST', '/services', publicKey, secretKey, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get details of a specific service.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID to retrieve
|
||||
*
|
||||
* Returns: Promise<Object> (service details)
|
||||
*/
|
||||
async function getService(serviceId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('GET', `/services/${serviceId}`, publicKey, secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a service (resize vCPU/memory).
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID to update
|
||||
* opts: Update options:
|
||||
* - vcpu: New vCPU count (1-8)
|
||||
*
|
||||
* Returns: Promise<Object> (update confirmation)
|
||||
*/
|
||||
async function updateService(serviceId, opts = {}, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
const data = {};
|
||||
if (opts.vcpu) data.vcpu = opts.vcpu;
|
||||
return makeRequest('PATCH', `/services/${serviceId}`, publicKey, secretKey, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete/destroy a service.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID to destroy
|
||||
*
|
||||
* Returns: Promise<Object> (deletion confirmation)
|
||||
*/
|
||||
async function deleteService(serviceId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('DELETE', `/services/${serviceId}`, publicKey, secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeze a service (stop container, preserve disk).
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID to freeze
|
||||
*
|
||||
* Returns: Promise<Object> (freeze confirmation)
|
||||
*/
|
||||
async function freezeService(serviceId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/services/${serviceId}/freeze`, publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfreeze a service (restart container).
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID to unfreeze
|
||||
*
|
||||
* Returns: Promise<Object> (unfreeze confirmation)
|
||||
*/
|
||||
async function unfreezeService(serviceId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/services/${serviceId}/unfreeze`, publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock a service to prevent deletion.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID to lock
|
||||
*
|
||||
* Returns: Promise<Object> (lock confirmation)
|
||||
*/
|
||||
async function lockService(serviceId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/services/${serviceId}/lock`, publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock a service to allow deletion.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID to unlock
|
||||
*
|
||||
* Returns: Promise<Object> (unlock confirmation)
|
||||
*/
|
||||
async function unlockService(serviceId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/services/${serviceId}/unlock`, publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get service logs.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID
|
||||
* all: If true, get all logs; if false, get last ~9000 lines (default: false)
|
||||
*
|
||||
* Returns: Promise<Object> (log data)
|
||||
*/
|
||||
async function getServiceLogs(serviceId, all = false, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
const path = all ? `/services/${serviceId}/logs?all=true` : `/services/${serviceId}/logs`;
|
||||
return makeRequest('GET', path, publicKey, secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get service environment vault status.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID
|
||||
*
|
||||
* Returns: Promise<Object> (vault status with has_vault, count, updated_at)
|
||||
*/
|
||||
async function getServiceEnv(serviceId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('GET', `/services/${serviceId}/env`, publicKey, secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set service environment vault.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID
|
||||
* env: Environment content as string (KEY=VALUE format, newline separated)
|
||||
* or object { KEY: "value", KEY2: "value2" }
|
||||
*
|
||||
* Returns: Promise<Object> (set confirmation)
|
||||
*/
|
||||
async function setServiceEnv(serviceId, env, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
// Convert object to KEY=VALUE format if needed
|
||||
let envContent = env;
|
||||
if (typeof env === 'object' && !Array.isArray(env)) {
|
||||
envContent = Object.entries(env)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join('\n');
|
||||
}
|
||||
// Note: This endpoint uses PUT with text/plain body
|
||||
// The makeRequest function sends JSON, so we need to handle this specially
|
||||
return makeRequest('PUT', `/services/${serviceId}/env`, publicKey, secretKey, { content: envContent });
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete service environment vault.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID
|
||||
* keys: Optional array of specific keys to delete (deletes all if not specified)
|
||||
*
|
||||
* Returns: Promise<Object> (deletion confirmation)
|
||||
*/
|
||||
async function deleteServiceEnv(serviceId, keys = null, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
const data = keys ? { keys } : {};
|
||||
return makeRequest('DELETE', `/services/${serviceId}/env`, publicKey, secretKey, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export service environment vault.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID
|
||||
*
|
||||
* Returns: Promise<Object> (exported environment data)
|
||||
*/
|
||||
async function exportServiceEnv(serviceId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/services/${serviceId}/env/export`, publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Redeploy a service with new bootstrap script.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID to redeploy
|
||||
* bootstrap: Optional new bootstrap script content or URL
|
||||
*
|
||||
* Returns: Promise<Object> (redeploy confirmation)
|
||||
*/
|
||||
async function redeployService(serviceId, bootstrap = null, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
const data = {};
|
||||
if (bootstrap) {
|
||||
if (bootstrap.startsWith('http://') || bootstrap.startsWith('https://')) {
|
||||
data.bootstrap = bootstrap;
|
||||
} else {
|
||||
data.bootstrap_content = bootstrap;
|
||||
}
|
||||
}
|
||||
return makeRequest('POST', `/services/${serviceId}/redeploy`, publicKey, secretKey, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command in a running service container.
|
||||
*
|
||||
* Args:
|
||||
* serviceId: Service ID
|
||||
* command: Command to execute
|
||||
* timeout: Optional timeout in milliseconds (default: 30000)
|
||||
*
|
||||
* Returns: Promise<Object> (execution result with stdout, stderr, exit_code)
|
||||
*/
|
||||
async function executeInService(serviceId, command, timeout = 30000, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
const response = await makeRequest('POST', `/services/${serviceId}/execute`, publicKey, secretKey, {
|
||||
command,
|
||||
timeout,
|
||||
});
|
||||
|
||||
// If we got a job_id, poll until completion
|
||||
const jobId = response.job_id;
|
||||
if (jobId) {
|
||||
return waitForJob(jobId, publicKey, secretKey);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Additional Snapshot Functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Lock a snapshot to prevent deletion.
|
||||
*
|
||||
* Args:
|
||||
* snapshotId: Snapshot ID to lock
|
||||
*
|
||||
* Returns: Promise<Object> (lock confirmation)
|
||||
*/
|
||||
async function lockSnapshot(snapshotId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/snapshots/${snapshotId}/lock`, publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock a snapshot to allow deletion.
|
||||
*
|
||||
* Args:
|
||||
* snapshotId: Snapshot ID to unlock
|
||||
*
|
||||
* Returns: Promise<Object> (unlock confirmation)
|
||||
*/
|
||||
async function unlockSnapshot(snapshotId, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
return makeRequest('POST', `/snapshots/${snapshotId}/unlock`, publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone a snapshot to create a new session or service.
|
||||
*
|
||||
* Args:
|
||||
* snapshotId: Snapshot ID to clone
|
||||
* name: Name for the new resource
|
||||
* opts: Optional settings:
|
||||
* - type: "session" or "service" (default: inferred from snapshot)
|
||||
* - shell: Shell for session clones
|
||||
* - ports: Ports array for service clones
|
||||
*
|
||||
* Returns: Promise<Object> (clone result with new session_id or service_id)
|
||||
*/
|
||||
async function cloneSnapshot(snapshotId, name, opts = {}, publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
const data = {};
|
||||
if (name) data.name = name;
|
||||
if (opts.type) data.type = opts.type;
|
||||
if (opts.shell) data.shell = opts.shell;
|
||||
if (opts.ports) data.ports = opts.ports;
|
||||
return makeRequest('POST', `/snapshots/${snapshotId}/clone`, publicKey, secretKey, data);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Key Validation
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Validate API keys.
|
||||
*
|
||||
* Returns: Promise<Object> (validation result with valid, tier, expires_at, etc.)
|
||||
*/
|
||||
async function validateKeys(publicKey, secretKey) {
|
||||
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
|
||||
// Note: This endpoint is on the portal (unsandbox.com), not the API
|
||||
// For SDK purposes, we'll call the API endpoint if available
|
||||
return makeRequest('POST', '/keys/validate', publicKey, secretKey, {});
|
||||
}
|
||||
|
||||
// Export all functions
|
||||
module.exports = {
|
||||
// Code execution
|
||||
executeCode,
|
||||
executeAsync,
|
||||
getJob,
|
||||
|
|
@ -569,10 +1057,44 @@ module.exports = {
|
|||
listJobs,
|
||||
getLanguages,
|
||||
detectLanguage,
|
||||
// Session management
|
||||
listSessions,
|
||||
getSession,
|
||||
createSession,
|
||||
deleteSession,
|
||||
freezeSession,
|
||||
unfreezeSession,
|
||||
boostSession,
|
||||
unboostSession,
|
||||
shellSession,
|
||||
// Service management
|
||||
listServices,
|
||||
createService,
|
||||
getService,
|
||||
updateService,
|
||||
deleteService,
|
||||
freezeService,
|
||||
unfreezeService,
|
||||
lockService,
|
||||
unlockService,
|
||||
getServiceLogs,
|
||||
getServiceEnv,
|
||||
setServiceEnv,
|
||||
deleteServiceEnv,
|
||||
exportServiceEnv,
|
||||
redeployService,
|
||||
executeInService,
|
||||
// Snapshot management
|
||||
sessionSnapshot,
|
||||
serviceSnapshot,
|
||||
listSnapshots,
|
||||
restoreSnapshot,
|
||||
deleteSnapshot,
|
||||
lockSnapshot,
|
||||
unlockSnapshot,
|
||||
cloneSnapshot,
|
||||
// Key validation
|
||||
validateKeys,
|
||||
// Errors
|
||||
CredentialsError,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -466,6 +466,545 @@ class UnsandboxAsync {
|
|||
return $this->makeRequest('DELETE', "/snapshots/{$snapshotId}", $publicKey, $secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock a snapshot to prevent deletion.
|
||||
*
|
||||
* @param string $snapshotId Snapshot ID to lock
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to response array with lock confirmation
|
||||
*/
|
||||
public function lockSnapshot(string $snapshotId, ?string $publicKey = null, ?string $secretKey = null): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/snapshots/{$snapshotId}/lock", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock a snapshot to allow deletion.
|
||||
*
|
||||
* @param string $snapshotId Snapshot ID to unlock
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to response array with unlock confirmation
|
||||
*/
|
||||
public function unlockSnapshot(string $snapshotId, ?string $publicKey = null, ?string $secretKey = null): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/snapshots/{$snapshotId}/unlock", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone a snapshot to create a new session or service.
|
||||
*
|
||||
* @param string $snapshotId Snapshot ID to clone
|
||||
* @param string|null $name Optional name for the new resource
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @param array $opts Optional parameters: 'type' (session|service), 'shell', 'ports'
|
||||
* @return PromiseInterface Resolves to response array with cloned resource info
|
||||
*/
|
||||
public function cloneSnapshot(
|
||||
string $snapshotId,
|
||||
?string $name = null,
|
||||
?string $publicKey = null,
|
||||
?string $secretKey = null,
|
||||
array $opts = []
|
||||
): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
|
||||
$data = [];
|
||||
if ($name !== null) {
|
||||
$data['name'] = $name;
|
||||
}
|
||||
if (isset($opts['type'])) {
|
||||
$data['type'] = $opts['type'];
|
||||
}
|
||||
if (isset($opts['shell'])) {
|
||||
$data['shell'] = $opts['shell'];
|
||||
}
|
||||
if (isset($opts['ports'])) {
|
||||
$data['ports'] = $opts['ports'];
|
||||
}
|
||||
|
||||
return $this->makeRequest('POST', "/snapshots/{$snapshotId}/clone", $publicKey, $secretKey, $data);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Session Methods
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* List all active sessions for the authenticated account.
|
||||
*
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to list of session arrays
|
||||
*/
|
||||
public function listSessions(?string $publicKey = null, ?string $secretKey = null): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('GET', '/sessions', $publicKey, $secretKey)->then(function (array $response) {
|
||||
return $response['sessions'] ?? [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get details of a specific session.
|
||||
*
|
||||
* @param string $sessionId Session ID
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to session details
|
||||
*/
|
||||
public function getSession(string $sessionId, ?string $publicKey = null, ?string $secretKey = null): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('GET', "/sessions/{$sessionId}", $publicKey, $secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new interactive session.
|
||||
*
|
||||
* @param string $language Programming language or shell (e.g., "bash", "python3")
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @param array $opts Optional parameters: 'network_mode', 'ttl', 'shell', 'multiplexer', 'vcpu'
|
||||
* @return PromiseInterface Resolves to session info including session_id and container_name
|
||||
*/
|
||||
public function createSession(
|
||||
string $language,
|
||||
?string $publicKey = null,
|
||||
?string $secretKey = null,
|
||||
array $opts = []
|
||||
): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
|
||||
$data = [
|
||||
'network_mode' => $opts['network_mode'] ?? 'zerotrust',
|
||||
'ttl' => $opts['ttl'] ?? 3600,
|
||||
];
|
||||
if (!empty($language)) {
|
||||
$data['shell'] = $language;
|
||||
}
|
||||
if (isset($opts['shell'])) {
|
||||
$data['shell'] = $opts['shell'];
|
||||
}
|
||||
if (isset($opts['multiplexer'])) {
|
||||
$data['multiplexer'] = $opts['multiplexer'];
|
||||
}
|
||||
if (isset($opts['vcpu']) && $opts['vcpu'] > 1) {
|
||||
$data['vcpu'] = $opts['vcpu'];
|
||||
}
|
||||
if (isset($opts['input_files'])) {
|
||||
$data['input_files'] = $opts['input_files'];
|
||||
}
|
||||
|
||||
return $this->makeRequest('POST', '/sessions', $publicKey, $secretKey, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete (terminate) a session.
|
||||
*
|
||||
* @param string $sessionId Session ID to delete
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to response array with deletion confirmation
|
||||
*/
|
||||
public function deleteSession(string $sessionId, ?string $publicKey = null, ?string $secretKey = null): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('DELETE', "/sessions/{$sessionId}", $publicKey, $secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeze a session to pause execution and reduce resource usage.
|
||||
*
|
||||
* @param string $sessionId Session ID to freeze
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to response array with freeze confirmation
|
||||
*/
|
||||
public function freezeSession(string $sessionId, ?string $publicKey = null, ?string $secretKey = null): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/sessions/{$sessionId}/freeze", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfreeze a session to resume execution.
|
||||
*
|
||||
* @param string $sessionId Session ID to unfreeze
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to response array with unfreeze confirmation
|
||||
*/
|
||||
public function unfreezeSession(string $sessionId, ?string $publicKey = null, ?string $secretKey = null): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/sessions/{$sessionId}/unfreeze", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Boost a session's resources (increase vCPU, memory is derived: vcpu * 2048MB).
|
||||
*
|
||||
* @param string $sessionId Session ID to boost
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @param int $vcpu Number of vCPUs (default: 2)
|
||||
* @return PromiseInterface Resolves to response array with boost confirmation
|
||||
*/
|
||||
public function boostSession(
|
||||
string $sessionId,
|
||||
?string $publicKey = null,
|
||||
?string $secretKey = null,
|
||||
int $vcpu = 2
|
||||
): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/sessions/{$sessionId}/boost", $publicKey, $secretKey, ['vcpu' => $vcpu]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove boost from a session (return to base resources).
|
||||
*
|
||||
* @param string $sessionId Session ID to unboost
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to response array with unboost confirmation
|
||||
*/
|
||||
public function unboostSession(string $sessionId, ?string $publicKey = null, ?string $secretKey = null): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/sessions/{$sessionId}/unboost", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a shell command in an active session.
|
||||
*
|
||||
* Note: This is for one-shot commands. For interactive sessions, use WebSocket connection.
|
||||
*
|
||||
* @param string $sessionId Session ID
|
||||
* @param string $command Command to execute
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to response array with command output
|
||||
*/
|
||||
public function shellSession(
|
||||
string $sessionId,
|
||||
string $command,
|
||||
?string $publicKey = null,
|
||||
?string $secretKey = null
|
||||
): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/sessions/{$sessionId}/shell", $publicKey, $secretKey, ['command' => $command]);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Service Methods
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* List all services for the authenticated account.
|
||||
*
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to list of service arrays
|
||||
*/
|
||||
public function listServices(?string $publicKey = null, ?string $secretKey = null): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('GET', '/services', $publicKey, $secretKey)->then(function (array $response) {
|
||||
return $response['services'] ?? [];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new persistent service.
|
||||
*
|
||||
* @param string $name Service name
|
||||
* @param array|string $ports Port(s) to expose (array of ints or comma-separated string)
|
||||
* @param string $bootstrap Bootstrap command or URL
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @param array $opts Optional parameters: 'network_mode', 'vcpu', 'service_type', 'custom_domains', 'bootstrap_content', 'input_files'
|
||||
* @return PromiseInterface Resolves to service info including service_id
|
||||
*/
|
||||
public function createService(
|
||||
string $name,
|
||||
$ports,
|
||||
string $bootstrap,
|
||||
?string $publicKey = null,
|
||||
?string $secretKey = null,
|
||||
array $opts = []
|
||||
): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
|
||||
// Convert ports to array if string
|
||||
if (is_string($ports)) {
|
||||
$ports = array_map('intval', explode(',', $ports));
|
||||
}
|
||||
|
||||
$data = [
|
||||
'name' => $name,
|
||||
'ports' => $ports,
|
||||
'bootstrap' => $bootstrap,
|
||||
];
|
||||
|
||||
if (isset($opts['network_mode'])) {
|
||||
$data['network_mode'] = $opts['network_mode'];
|
||||
}
|
||||
if (isset($opts['vcpu']) && $opts['vcpu'] > 1) {
|
||||
$data['vcpu'] = $opts['vcpu'];
|
||||
}
|
||||
if (isset($opts['service_type'])) {
|
||||
$data['service_type'] = $opts['service_type'];
|
||||
}
|
||||
if (isset($opts['custom_domains'])) {
|
||||
$data['custom_domains'] = $opts['custom_domains'];
|
||||
}
|
||||
if (isset($opts['bootstrap_content'])) {
|
||||
$data['bootstrap_content'] = $opts['bootstrap_content'];
|
||||
}
|
||||
if (isset($opts['input_files'])) {
|
||||
$data['input_files'] = $opts['input_files'];
|
||||
}
|
||||
|
||||
return $this->makeRequest('POST', '/services', $publicKey, $secretKey, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get details of a specific service.
|
||||
*
|
||||
* @param string $serviceId Service ID
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to service details
|
||||
*/
|
||||
public function getService(string $serviceId, ?string $publicKey = null, ?string $secretKey = null): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('GET', "/services/{$serviceId}", $publicKey, $secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a service (e.g., resize vCPU).
|
||||
*
|
||||
* @param string $serviceId Service ID
|
||||
* @param array $opts Update parameters: 'vcpu', 'name', etc.
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to updated service details
|
||||
*/
|
||||
public function updateService(
|
||||
string $serviceId,
|
||||
array $opts,
|
||||
?string $publicKey = null,
|
||||
?string $secretKey = null
|
||||
): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('PATCH', "/services/{$serviceId}", $publicKey, $secretKey, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete (destroy) a service.
|
||||
*
|
||||
* @param string $serviceId Service ID to delete
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to response array with deletion confirmation
|
||||
*/
|
||||
public function deleteService(string $serviceId, ?string $publicKey = null, ?string $secretKey = null): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('DELETE', "/services/{$serviceId}", $publicKey, $secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeze a service to pause execution and reduce resource usage.
|
||||
*
|
||||
* @param string $serviceId Service ID to freeze
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to response array with freeze confirmation
|
||||
*/
|
||||
public function freezeService(string $serviceId, ?string $publicKey = null, ?string $secretKey = null): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/services/{$serviceId}/freeze", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfreeze a service to resume execution.
|
||||
*
|
||||
* @param string $serviceId Service ID to unfreeze
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to response array with unfreeze confirmation
|
||||
*/
|
||||
public function unfreezeService(string $serviceId, ?string $publicKey = null, ?string $secretKey = null): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/services/{$serviceId}/unfreeze", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock a service to prevent deletion.
|
||||
*
|
||||
* @param string $serviceId Service ID to lock
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to response array with lock confirmation
|
||||
*/
|
||||
public function lockService(string $serviceId, ?string $publicKey = null, ?string $secretKey = null): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/services/{$serviceId}/lock", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock a service to allow deletion.
|
||||
*
|
||||
* @param string $serviceId Service ID to unlock
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to response array with unlock confirmation
|
||||
*/
|
||||
public function unlockService(string $serviceId, ?string $publicKey = null, ?string $secretKey = null): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/services/{$serviceId}/unlock", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bootstrap logs for a service.
|
||||
*
|
||||
* @param string $serviceId Service ID
|
||||
* @param bool $all If true, get all logs; if false, get last 9000 lines
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to response array with log content
|
||||
*/
|
||||
public function getServiceLogs(
|
||||
string $serviceId,
|
||||
bool $all = false,
|
||||
?string $publicKey = null,
|
||||
?string $secretKey = null
|
||||
): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
$path = "/services/{$serviceId}/logs" . ($all ? '?all=true' : '');
|
||||
return $this->makeRequest('GET', $path, $publicKey, $secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get environment vault status for a service.
|
||||
*
|
||||
* @param string $serviceId Service ID
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to response array with vault status (has_vault, count, updated_at)
|
||||
*/
|
||||
public function getServiceEnv(string $serviceId, ?string $publicKey = null, ?string $secretKey = null): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('GET', "/services/{$serviceId}/env", $publicKey, $secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set environment vault for a service.
|
||||
*
|
||||
* @param string $serviceId Service ID
|
||||
* @param string $env Environment content in .env format (KEY=VALUE\nKEY2=VALUE2)
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to response array with update confirmation
|
||||
*/
|
||||
public function setServiceEnv(
|
||||
string $serviceId,
|
||||
string $env,
|
||||
?string $publicKey = null,
|
||||
?string $secretKey = null
|
||||
): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequestRaw('PUT', "/services/{$serviceId}/env", $publicKey, $secretKey, $env, 'text/plain');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete environment vault for a service.
|
||||
*
|
||||
* @param string $serviceId Service ID
|
||||
* @param array|null $keys Optional specific keys to delete; if null, deletes entire vault
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to response array with deletion confirmation
|
||||
*/
|
||||
public function deleteServiceEnv(
|
||||
string $serviceId,
|
||||
?array $keys = null,
|
||||
?string $publicKey = null,
|
||||
?string $secretKey = null
|
||||
): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('DELETE', "/services/{$serviceId}/env", $publicKey, $secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export environment vault for a service (returns .env format).
|
||||
*
|
||||
* @param string $serviceId Service ID
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to response array with env content
|
||||
*/
|
||||
public function exportServiceEnv(string $serviceId, ?string $publicKey = null, ?string $secretKey = null): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/services/{$serviceId}/env/export", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redeploy a service (re-run bootstrap script).
|
||||
*
|
||||
* @param string $serviceId Service ID
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to response array with redeploy confirmation
|
||||
*/
|
||||
public function redeployService(string $serviceId, ?string $publicKey = null, ?string $secretKey = null): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/services/{$serviceId}/redeploy", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command in a running service container.
|
||||
*
|
||||
* @param string $serviceId Service ID
|
||||
* @param string $command Command to execute
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @param int $timeout Timeout in milliseconds (default: 30000)
|
||||
* @return PromiseInterface Resolves to response array with command output (stdout, stderr, exit_code)
|
||||
*/
|
||||
public function executeInService(
|
||||
string $serviceId,
|
||||
string $command,
|
||||
?string $publicKey = null,
|
||||
?string $secretKey = null,
|
||||
int $timeout = 30000
|
||||
): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
|
||||
return $this->makeRequest('POST', "/services/{$serviceId}/execute", $publicKey, $secretKey, [
|
||||
'command' => $command,
|
||||
'timeout' => $timeout,
|
||||
])->then(function (array $response) use ($publicKey, $secretKey) {
|
||||
// If we got a job_id, poll until completion
|
||||
$jobId = $response['job_id'] ?? null;
|
||||
if ($jobId) {
|
||||
return $this->waitForJob($jobId, $publicKey, $secretKey);
|
||||
}
|
||||
return $response;
|
||||
});
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Key Validation
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Validate API keys.
|
||||
*
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return PromiseInterface Resolves to response array with validation result
|
||||
*/
|
||||
public function validateKeys(?string $publicKey = null, ?string $secretKey = null): PromiseInterface {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', '/keys/validate', $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to ~/.unsandbox directory, creating if necessary.
|
||||
*
|
||||
|
|
@ -660,6 +1199,58 @@ class UnsandboxAsync {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an authenticated HTTP request with raw body content (non-JSON) asynchronously.
|
||||
*
|
||||
* @param string $method HTTP method (PUT, POST, etc.)
|
||||
* @param string $path API endpoint path
|
||||
* @param string $publicKey API public key
|
||||
* @param string $secretKey API secret key
|
||||
* @param string $body Raw request body
|
||||
* @param string $contentType Content type header (e.g., 'text/plain')
|
||||
* @return PromiseInterface Resolves to decoded JSON response array
|
||||
*/
|
||||
private function makeRequestRaw(string $method, string $path, string $publicKey, string $secretKey, string $body, string $contentType = 'text/plain'): PromiseInterface {
|
||||
$timestamp = time();
|
||||
|
||||
$signature = $this->signRequest($secretKey, $timestamp, $method, $path, $body);
|
||||
|
||||
$headers = [
|
||||
'Authorization' => 'Bearer ' . $publicKey,
|
||||
'X-Timestamp' => (string)$timestamp,
|
||||
'X-Signature' => $signature,
|
||||
'Content-Type' => $contentType,
|
||||
];
|
||||
|
||||
$options = [
|
||||
'headers' => $headers,
|
||||
'body' => $body,
|
||||
];
|
||||
|
||||
return $this->httpClient->requestAsync($method, $path, $options)->then(
|
||||
function ($response) {
|
||||
$body = (string)$response->getBody();
|
||||
$decoded = json_decode($body, true);
|
||||
if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new AsyncApiException("Invalid JSON response: " . json_last_error_msg());
|
||||
}
|
||||
return $decoded;
|
||||
},
|
||||
function ($exception) {
|
||||
if ($exception instanceof RequestException) {
|
||||
$response = $exception->getResponse();
|
||||
if ($response !== null) {
|
||||
$body = (string)$response->getBody();
|
||||
$decoded = json_decode($body, true);
|
||||
$errorMessage = $decoded['error'] ?? $decoded['message'] ?? "HTTP " . $response->getStatusCode();
|
||||
throw new AsyncApiException($errorMessage, $response->getStatusCode(), $decoded, $exception);
|
||||
}
|
||||
}
|
||||
throw new AsyncApiException($exception->getMessage(), 0, null, $exception);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to languages cache file.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -442,6 +442,602 @@ class Unsandbox {
|
|||
return $this->makeRequest('DELETE', "/snapshots/{$snapshotId}", $publicKey, $secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock a snapshot to prevent deletion.
|
||||
*
|
||||
* @param string $snapshotId Snapshot ID to lock
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Response array with lock confirmation
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function lockSnapshot(string $snapshotId, ?string $publicKey = null, ?string $secretKey = null): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/snapshots/{$snapshotId}/lock", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock a snapshot to allow deletion.
|
||||
*
|
||||
* @param string $snapshotId Snapshot ID to unlock
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Response array with unlock confirmation
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function unlockSnapshot(string $snapshotId, ?string $publicKey = null, ?string $secretKey = null): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/snapshots/{$snapshotId}/unlock", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone a snapshot to create a new session or service.
|
||||
*
|
||||
* @param string $snapshotId Snapshot ID to clone
|
||||
* @param string|null $name Optional name for the new resource
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @param array $opts Optional parameters: 'type' (session|service), 'shell', 'ports'
|
||||
* @return array Response array with cloned resource info
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function cloneSnapshot(
|
||||
string $snapshotId,
|
||||
?string $name = null,
|
||||
?string $publicKey = null,
|
||||
?string $secretKey = null,
|
||||
array $opts = []
|
||||
): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
|
||||
$data = [];
|
||||
if ($name !== null) {
|
||||
$data['name'] = $name;
|
||||
}
|
||||
if (isset($opts['type'])) {
|
||||
$data['type'] = $opts['type'];
|
||||
}
|
||||
if (isset($opts['shell'])) {
|
||||
$data['shell'] = $opts['shell'];
|
||||
}
|
||||
if (isset($opts['ports'])) {
|
||||
$data['ports'] = $opts['ports'];
|
||||
}
|
||||
|
||||
return $this->makeRequest('POST', "/snapshots/{$snapshotId}/clone", $publicKey, $secretKey, $data);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Session Methods
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* List all active sessions for the authenticated account.
|
||||
*
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array List of session arrays
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function listSessions(?string $publicKey = null, ?string $secretKey = null): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
$response = $this->makeRequest('GET', '/sessions', $publicKey, $secretKey);
|
||||
return $response['sessions'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get details of a specific session.
|
||||
*
|
||||
* @param string $sessionId Session ID
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Session details
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function getSession(string $sessionId, ?string $publicKey = null, ?string $secretKey = null): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('GET', "/sessions/{$sessionId}", $publicKey, $secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new interactive session.
|
||||
*
|
||||
* @param string $language Programming language or shell (e.g., "bash", "python3")
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @param array $opts Optional parameters: 'network_mode', 'ttl', 'shell', 'multiplexer', 'vcpu'
|
||||
* @return array Session info including session_id and container_name
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function createSession(
|
||||
string $language,
|
||||
?string $publicKey = null,
|
||||
?string $secretKey = null,
|
||||
array $opts = []
|
||||
): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
|
||||
$data = [
|
||||
'network_mode' => $opts['network_mode'] ?? 'zerotrust',
|
||||
'ttl' => $opts['ttl'] ?? 3600,
|
||||
];
|
||||
if (!empty($language)) {
|
||||
$data['shell'] = $language;
|
||||
}
|
||||
if (isset($opts['shell'])) {
|
||||
$data['shell'] = $opts['shell'];
|
||||
}
|
||||
if (isset($opts['multiplexer'])) {
|
||||
$data['multiplexer'] = $opts['multiplexer'];
|
||||
}
|
||||
if (isset($opts['vcpu']) && $opts['vcpu'] > 1) {
|
||||
$data['vcpu'] = $opts['vcpu'];
|
||||
}
|
||||
if (isset($opts['input_files'])) {
|
||||
$data['input_files'] = $opts['input_files'];
|
||||
}
|
||||
|
||||
return $this->makeRequest('POST', '/sessions', $publicKey, $secretKey, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete (terminate) a session.
|
||||
*
|
||||
* @param string $sessionId Session ID to delete
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Response array with deletion confirmation
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function deleteSession(string $sessionId, ?string $publicKey = null, ?string $secretKey = null): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('DELETE', "/sessions/{$sessionId}", $publicKey, $secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeze a session to pause execution and reduce resource usage.
|
||||
*
|
||||
* @param string $sessionId Session ID to freeze
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Response array with freeze confirmation
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function freezeSession(string $sessionId, ?string $publicKey = null, ?string $secretKey = null): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/sessions/{$sessionId}/freeze", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfreeze a session to resume execution.
|
||||
*
|
||||
* @param string $sessionId Session ID to unfreeze
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Response array with unfreeze confirmation
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function unfreezeSession(string $sessionId, ?string $publicKey = null, ?string $secretKey = null): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/sessions/{$sessionId}/unfreeze", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Boost a session's resources (increase vCPU, memory is derived: vcpu * 2048MB).
|
||||
*
|
||||
* @param string $sessionId Session ID to boost
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @param int $vcpu Number of vCPUs (default: 2)
|
||||
* @return array Response array with boost confirmation
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function boostSession(
|
||||
string $sessionId,
|
||||
?string $publicKey = null,
|
||||
?string $secretKey = null,
|
||||
int $vcpu = 2
|
||||
): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/sessions/{$sessionId}/boost", $publicKey, $secretKey, ['vcpu' => $vcpu]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove boost from a session (return to base resources).
|
||||
*
|
||||
* @param string $sessionId Session ID to unboost
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Response array with unboost confirmation
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function unboostSession(string $sessionId, ?string $publicKey = null, ?string $secretKey = null): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/sessions/{$sessionId}/unboost", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a shell command in an active session.
|
||||
*
|
||||
* Note: This is for one-shot commands. For interactive sessions, use WebSocket connection.
|
||||
*
|
||||
* @param string $sessionId Session ID
|
||||
* @param string $command Command to execute
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Response array with command output
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function shellSession(
|
||||
string $sessionId,
|
||||
string $command,
|
||||
?string $publicKey = null,
|
||||
?string $secretKey = null
|
||||
): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/sessions/{$sessionId}/shell", $publicKey, $secretKey, ['command' => $command]);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Service Methods
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* List all services for the authenticated account.
|
||||
*
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array List of service arrays
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function listServices(?string $publicKey = null, ?string $secretKey = null): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
$response = $this->makeRequest('GET', '/services', $publicKey, $secretKey);
|
||||
return $response['services'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new persistent service.
|
||||
*
|
||||
* @param string $name Service name
|
||||
* @param array|string $ports Port(s) to expose (array of ints or comma-separated string)
|
||||
* @param string $bootstrap Bootstrap command or URL
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @param array $opts Optional parameters: 'network_mode', 'vcpu', 'service_type', 'custom_domains', 'bootstrap_content', 'input_files'
|
||||
* @return array Service info including service_id
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function createService(
|
||||
string $name,
|
||||
$ports,
|
||||
string $bootstrap,
|
||||
?string $publicKey = null,
|
||||
?string $secretKey = null,
|
||||
array $opts = []
|
||||
): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
|
||||
// Convert ports to array if string
|
||||
if (is_string($ports)) {
|
||||
$ports = array_map('intval', explode(',', $ports));
|
||||
}
|
||||
|
||||
$data = [
|
||||
'name' => $name,
|
||||
'ports' => $ports,
|
||||
'bootstrap' => $bootstrap,
|
||||
];
|
||||
|
||||
if (isset($opts['network_mode'])) {
|
||||
$data['network_mode'] = $opts['network_mode'];
|
||||
}
|
||||
if (isset($opts['vcpu']) && $opts['vcpu'] > 1) {
|
||||
$data['vcpu'] = $opts['vcpu'];
|
||||
}
|
||||
if (isset($opts['service_type'])) {
|
||||
$data['service_type'] = $opts['service_type'];
|
||||
}
|
||||
if (isset($opts['custom_domains'])) {
|
||||
$data['custom_domains'] = $opts['custom_domains'];
|
||||
}
|
||||
if (isset($opts['bootstrap_content'])) {
|
||||
$data['bootstrap_content'] = $opts['bootstrap_content'];
|
||||
}
|
||||
if (isset($opts['input_files'])) {
|
||||
$data['input_files'] = $opts['input_files'];
|
||||
}
|
||||
|
||||
return $this->makeRequest('POST', '/services', $publicKey, $secretKey, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get details of a specific service.
|
||||
*
|
||||
* @param string $serviceId Service ID
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Service details
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function getService(string $serviceId, ?string $publicKey = null, ?string $secretKey = null): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('GET', "/services/{$serviceId}", $publicKey, $secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a service (e.g., resize vCPU).
|
||||
*
|
||||
* @param string $serviceId Service ID
|
||||
* @param array $opts Update parameters: 'vcpu', 'name', etc.
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Updated service details
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function updateService(
|
||||
string $serviceId,
|
||||
array $opts,
|
||||
?string $publicKey = null,
|
||||
?string $secretKey = null
|
||||
): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('PATCH', "/services/{$serviceId}", $publicKey, $secretKey, $opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete (destroy) a service.
|
||||
*
|
||||
* @param string $serviceId Service ID to delete
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Response array with deletion confirmation
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function deleteService(string $serviceId, ?string $publicKey = null, ?string $secretKey = null): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('DELETE', "/services/{$serviceId}", $publicKey, $secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Freeze a service to pause execution and reduce resource usage.
|
||||
*
|
||||
* @param string $serviceId Service ID to freeze
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Response array with freeze confirmation
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function freezeService(string $serviceId, ?string $publicKey = null, ?string $secretKey = null): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/services/{$serviceId}/freeze", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfreeze a service to resume execution.
|
||||
*
|
||||
* @param string $serviceId Service ID to unfreeze
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Response array with unfreeze confirmation
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function unfreezeService(string $serviceId, ?string $publicKey = null, ?string $secretKey = null): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/services/{$serviceId}/unfreeze", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock a service to prevent deletion.
|
||||
*
|
||||
* @param string $serviceId Service ID to lock
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Response array with lock confirmation
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function lockService(string $serviceId, ?string $publicKey = null, ?string $secretKey = null): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/services/{$serviceId}/lock", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock a service to allow deletion.
|
||||
*
|
||||
* @param string $serviceId Service ID to unlock
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Response array with unlock confirmation
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function unlockService(string $serviceId, ?string $publicKey = null, ?string $secretKey = null): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/services/{$serviceId}/unlock", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bootstrap logs for a service.
|
||||
*
|
||||
* @param string $serviceId Service ID
|
||||
* @param bool $all If true, get all logs; if false, get last 9000 lines
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Response array with log content
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function getServiceLogs(
|
||||
string $serviceId,
|
||||
bool $all = false,
|
||||
?string $publicKey = null,
|
||||
?string $secretKey = null
|
||||
): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
$path = "/services/{$serviceId}/logs" . ($all ? '?all=true' : '');
|
||||
return $this->makeRequest('GET', $path, $publicKey, $secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get environment vault status for a service.
|
||||
*
|
||||
* @param string $serviceId Service ID
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Response array with vault status (has_vault, count, updated_at)
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function getServiceEnv(string $serviceId, ?string $publicKey = null, ?string $secretKey = null): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('GET', "/services/{$serviceId}/env", $publicKey, $secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set environment vault for a service.
|
||||
*
|
||||
* @param string $serviceId Service ID
|
||||
* @param string $env Environment content in .env format (KEY=VALUE\nKEY2=VALUE2)
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Response array with update confirmation
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function setServiceEnv(
|
||||
string $serviceId,
|
||||
string $env,
|
||||
?string $publicKey = null,
|
||||
?string $secretKey = null
|
||||
): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequestRaw('PUT', "/services/{$serviceId}/env", $publicKey, $secretKey, $env, 'text/plain');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete environment vault for a service.
|
||||
*
|
||||
* @param string $serviceId Service ID
|
||||
* @param array|null $keys Optional specific keys to delete; if null, deletes entire vault
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Response array with deletion confirmation
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function deleteServiceEnv(
|
||||
string $serviceId,
|
||||
?array $keys = null,
|
||||
?string $publicKey = null,
|
||||
?string $secretKey = null
|
||||
): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('DELETE', "/services/{$serviceId}/env", $publicKey, $secretKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export environment vault for a service (returns .env format).
|
||||
*
|
||||
* @param string $serviceId Service ID
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Response array with env content
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function exportServiceEnv(string $serviceId, ?string $publicKey = null, ?string $secretKey = null): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/services/{$serviceId}/env/export", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Redeploy a service (re-run bootstrap script).
|
||||
*
|
||||
* @param string $serviceId Service ID
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Response array with redeploy confirmation
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function redeployService(string $serviceId, ?string $publicKey = null, ?string $secretKey = null): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', "/services/{$serviceId}/redeploy", $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command in a running service container.
|
||||
*
|
||||
* @param string $serviceId Service ID
|
||||
* @param string $command Command to execute
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @param int $timeout Timeout in milliseconds (default: 30000)
|
||||
* @return array Response array with command output (stdout, stderr, exit_code)
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function executeInService(
|
||||
string $serviceId,
|
||||
string $command,
|
||||
?string $publicKey = null,
|
||||
?string $secretKey = null,
|
||||
int $timeout = 30000
|
||||
): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
|
||||
$response = $this->makeRequest('POST', "/services/{$serviceId}/execute", $publicKey, $secretKey, [
|
||||
'command' => $command,
|
||||
'timeout' => $timeout,
|
||||
]);
|
||||
|
||||
// If we got a job_id, poll until completion
|
||||
$jobId = $response['job_id'] ?? null;
|
||||
if ($jobId) {
|
||||
return $this->waitForJob($jobId, $publicKey, $secretKey);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Key Validation
|
||||
// =========================================================================
|
||||
|
||||
/**
|
||||
* Validate API keys.
|
||||
*
|
||||
* @param string|null $publicKey Optional API key
|
||||
* @param string|null $secretKey Optional API secret
|
||||
* @return array Response array with validation result
|
||||
* @throws CredentialsException Missing credentials
|
||||
* @throws ApiException API request failed
|
||||
*/
|
||||
public function validateKeys(?string $publicKey = null, ?string $secretKey = null): array {
|
||||
[$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey);
|
||||
return $this->makeRequest('POST', '/keys/validate', $publicKey, $secretKey, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to ~/.unsandbox directory, creating if necessary.
|
||||
*
|
||||
|
|
@ -616,6 +1212,14 @@ class Unsandbox {
|
|||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||
break;
|
||||
case 'PUT':
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||
break;
|
||||
case 'PATCH':
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||
break;
|
||||
case 'DELETE':
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
|
||||
break;
|
||||
|
|
@ -647,6 +1251,61 @@ class Unsandbox {
|
|||
return $decoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an authenticated HTTP request with raw body content (non-JSON).
|
||||
*
|
||||
* @param string $method HTTP method (PUT, POST, etc.)
|
||||
* @param string $path API endpoint path
|
||||
* @param string $publicKey API public key
|
||||
* @param string $secretKey API secret key
|
||||
* @param string $body Raw request body
|
||||
* @param string $contentType Content type header (e.g., 'text/plain')
|
||||
* @return array Decoded JSON response
|
||||
* @throws ApiException On network errors or non-2xx response
|
||||
*/
|
||||
private function makeRequestRaw(string $method, string $path, string $publicKey, string $secretKey, string $body, string $contentType = 'text/plain'): array {
|
||||
$url = self::API_BASE . $path;
|
||||
$timestamp = time();
|
||||
|
||||
$signature = $this->signRequest($secretKey, $timestamp, $method, $path, $body);
|
||||
|
||||
$headers = [
|
||||
'Authorization: Bearer ' . $publicKey,
|
||||
'X-Timestamp: ' . $timestamp,
|
||||
'X-Signature: ' . $signature,
|
||||
'Content-Type: ' . $contentType,
|
||||
];
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 120);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($response === false) {
|
||||
throw new ApiException("cURL error: {$error}");
|
||||
}
|
||||
|
||||
$decoded = json_decode($response, true);
|
||||
if ($decoded === null && json_last_error() !== JSON_ERROR_NONE) {
|
||||
throw new ApiException("Invalid JSON response: " . json_last_error_msg());
|
||||
}
|
||||
|
||||
if ($httpCode >= 400) {
|
||||
$errorMessage = $decoded['error'] ?? $decoded['message'] ?? "HTTP {$httpCode}";
|
||||
throw new ApiException($errorMessage, $httpCode, $decoded);
|
||||
}
|
||||
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path to languages cache file.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ unsandbox.com Python SDK (Asynchronous)
|
|||
Library Usage:
|
||||
import asyncio
|
||||
from un_async import (
|
||||
# Execution
|
||||
execute_code,
|
||||
execute_async,
|
||||
get_job,
|
||||
|
|
@ -14,11 +15,44 @@ Library Usage:
|
|||
list_jobs,
|
||||
get_languages,
|
||||
detect_language,
|
||||
# Sessions
|
||||
list_sessions,
|
||||
get_session,
|
||||
create_session,
|
||||
delete_session,
|
||||
freeze_session,
|
||||
unfreeze_session,
|
||||
boost_session,
|
||||
unboost_session,
|
||||
shell_session,
|
||||
# Services
|
||||
list_services,
|
||||
create_service,
|
||||
get_service,
|
||||
update_service,
|
||||
delete_service,
|
||||
freeze_service,
|
||||
unfreeze_service,
|
||||
lock_service,
|
||||
unlock_service,
|
||||
get_service_logs,
|
||||
get_service_env,
|
||||
set_service_env,
|
||||
delete_service_env,
|
||||
export_service_env,
|
||||
redeploy_service,
|
||||
execute_in_service,
|
||||
# Snapshots
|
||||
session_snapshot,
|
||||
service_snapshot,
|
||||
list_snapshots,
|
||||
restore_snapshot,
|
||||
delete_snapshot,
|
||||
lock_snapshot,
|
||||
unlock_snapshot,
|
||||
clone_snapshot,
|
||||
# Key validation
|
||||
validate_keys,
|
||||
)
|
||||
|
||||
async def main():
|
||||
|
|
@ -214,6 +248,10 @@ async def _make_request(
|
|||
async with session.post(url, headers=headers, json=data, timeout=aiohttp.ClientTimeout(total=120)) as resp:
|
||||
resp.raise_for_status()
|
||||
return await resp.json()
|
||||
elif method == "PATCH":
|
||||
async with session.patch(url, headers=headers, json=data, timeout=aiohttp.ClientTimeout(total=120)) as resp:
|
||||
resp.raise_for_status()
|
||||
return await resp.json()
|
||||
elif method == "DELETE":
|
||||
async with session.delete(url, headers=headers, timeout=aiohttp.ClientTimeout(total=120)) as resp:
|
||||
resp.raise_for_status()
|
||||
|
|
@ -702,3 +740,913 @@ async def delete_snapshot(
|
|||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return await _make_request("DELETE", f"/snapshots/{snapshot_id}", public_key, secret_key)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Session Management Functions
|
||||
# =============================================================================
|
||||
|
||||
|
||||
async def list_sessions(
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
List all sessions for the authenticated account.
|
||||
|
||||
Args:
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
List of session dicts containing id, container_name, status, etc.
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
response = await _make_request("GET", "/sessions", public_key, secret_key)
|
||||
return response.get("sessions", [])
|
||||
|
||||
|
||||
async def get_session(
|
||||
session_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Get details of a specific session.
|
||||
|
||||
Args:
|
||||
session_id: Session ID to get details for
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Session details dict
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return await _make_request("GET", f"/sessions/{session_id}", public_key, secret_key)
|
||||
|
||||
|
||||
async def create_session(
|
||||
language: Optional[str] = None,
|
||||
network_mode: str = "zerotrust",
|
||||
ttl: int = 3600,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
shell: Optional[str] = None,
|
||||
multiplexer: Optional[str] = None,
|
||||
vcpu: int = 1,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Create a new interactive session.
|
||||
|
||||
Args:
|
||||
language: Optional programming language for the session
|
||||
network_mode: Network mode - "zerotrust" (default, no network) or "semitrusted" (with network)
|
||||
ttl: Time to live in seconds (default 3600)
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
shell: Optional shell to use (e.g., "bash", "python3")
|
||||
multiplexer: Optional terminal multiplexer ("tmux" or "screen")
|
||||
vcpu: Number of vCPUs (1-8, default 1)
|
||||
|
||||
Returns:
|
||||
Response dict containing session_id, container_name, etc.
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
data: Dict[str, Any] = {
|
||||
"network_mode": network_mode,
|
||||
"ttl": ttl,
|
||||
}
|
||||
if language:
|
||||
data["language"] = language
|
||||
if shell:
|
||||
data["shell"] = shell
|
||||
if multiplexer:
|
||||
data["multiplexer"] = multiplexer
|
||||
if vcpu > 1:
|
||||
data["vcpu"] = vcpu
|
||||
|
||||
return await _make_request("POST", "/sessions", public_key, secret_key, data)
|
||||
|
||||
|
||||
async def delete_session(
|
||||
session_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Delete/terminate a session.
|
||||
|
||||
Args:
|
||||
session_id: Session ID to delete
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with deletion confirmation and optional artifacts
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return await _make_request("DELETE", f"/sessions/{session_id}", public_key, secret_key)
|
||||
|
||||
|
||||
async def freeze_session(
|
||||
session_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Freeze a session (pause execution, preserve state).
|
||||
|
||||
Args:
|
||||
session_id: Session ID to freeze
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with freeze confirmation
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return await _make_request("POST", f"/sessions/{session_id}/freeze", public_key, secret_key, {})
|
||||
|
||||
|
||||
async def unfreeze_session(
|
||||
session_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Unfreeze a session (resume execution).
|
||||
|
||||
Args:
|
||||
session_id: Session ID to unfreeze
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with unfreeze confirmation
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return await _make_request("POST", f"/sessions/{session_id}/unfreeze", public_key, secret_key, {})
|
||||
|
||||
|
||||
async def boost_session(
|
||||
session_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Boost a session (increase resources).
|
||||
|
||||
Args:
|
||||
session_id: Session ID to boost
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with boost confirmation
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return await _make_request("POST", f"/sessions/{session_id}/boost", public_key, secret_key, {})
|
||||
|
||||
|
||||
async def unboost_session(
|
||||
session_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Unboost a session (return to normal resources).
|
||||
|
||||
Args:
|
||||
session_id: Session ID to unboost
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with unboost confirmation
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return await _make_request("POST", f"/sessions/{session_id}/unboost", public_key, secret_key, {})
|
||||
|
||||
|
||||
async def shell_session(
|
||||
session_id: str,
|
||||
command: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Execute a shell command in a session.
|
||||
|
||||
Note: This is for one-off commands. For interactive shell access,
|
||||
use WebSocket connection to /sessions/{id}/shell.
|
||||
|
||||
Args:
|
||||
session_id: Session ID to execute command in
|
||||
command: Shell command to execute
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with command output
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return await _make_request(
|
||||
"POST",
|
||||
f"/sessions/{session_id}/shell",
|
||||
public_key,
|
||||
secret_key,
|
||||
{"command": command},
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Service Management Functions
|
||||
# =============================================================================
|
||||
|
||||
|
||||
async def list_services(
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
List all services for the authenticated account.
|
||||
|
||||
Args:
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
List of service dicts containing id, name, status, ports, etc.
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
response = await _make_request("GET", "/services", public_key, secret_key)
|
||||
return response.get("services", [])
|
||||
|
||||
|
||||
async def create_service(
|
||||
name: str,
|
||||
ports: List[int],
|
||||
bootstrap: Optional[str] = None,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
network_mode: str = "semitrusted",
|
||||
custom_domains: Optional[List[str]] = None,
|
||||
vcpu: int = 1,
|
||||
service_type: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Create a new persistent service.
|
||||
|
||||
Args:
|
||||
name: Service name (used for subdomain: name.on.unsandbox.com)
|
||||
ports: List of ports to expose (e.g., [80, 443])
|
||||
bootstrap: Bootstrap script content, URL, or inline command
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
network_mode: Network mode (default "semitrusted" for services)
|
||||
custom_domains: Optional list of custom domain names
|
||||
vcpu: Number of vCPUs (1-8, default 1)
|
||||
service_type: Optional service type for SRV records (e.g., "minecraft")
|
||||
|
||||
Returns:
|
||||
Response dict containing service_id, etc.
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
data: Dict[str, Any] = {
|
||||
"name": name,
|
||||
"ports": ports,
|
||||
"network_mode": network_mode,
|
||||
}
|
||||
if bootstrap:
|
||||
# Check if it looks like a URL
|
||||
if bootstrap.startswith("http://") or bootstrap.startswith("https://"):
|
||||
data["bootstrap"] = bootstrap
|
||||
else:
|
||||
data["bootstrap_content"] = bootstrap
|
||||
if custom_domains:
|
||||
data["custom_domains"] = custom_domains
|
||||
if vcpu > 1:
|
||||
data["vcpu"] = vcpu
|
||||
if service_type:
|
||||
data["service_type"] = service_type
|
||||
|
||||
return await _make_request("POST", "/services", public_key, secret_key, data)
|
||||
|
||||
|
||||
async def get_service(
|
||||
service_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Get details of a specific service.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to get details for
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Service details dict
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return await _make_request("GET", f"/services/{service_id}", public_key, secret_key)
|
||||
|
||||
|
||||
async def update_service(
|
||||
service_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
vcpu: Optional[int] = None,
|
||||
**kwargs,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Update a service (e.g., resize vCPU/memory).
|
||||
|
||||
Args:
|
||||
service_id: Service ID to update
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
vcpu: Optional new vCPU count (1-8)
|
||||
**kwargs: Additional fields to update
|
||||
|
||||
Returns:
|
||||
Response dict with update confirmation
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
data: Dict[str, Any] = {}
|
||||
if vcpu is not None:
|
||||
data["vcpu"] = vcpu
|
||||
data.update(kwargs)
|
||||
|
||||
return await _make_request("PATCH", f"/services/{service_id}", public_key, secret_key, data)
|
||||
|
||||
|
||||
async def delete_service(
|
||||
service_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Delete/destroy a service.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to delete
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with deletion confirmation
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return await _make_request("DELETE", f"/services/{service_id}", public_key, secret_key)
|
||||
|
||||
|
||||
async def freeze_service(
|
||||
service_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Freeze a service (pause execution, preserve state).
|
||||
|
||||
Args:
|
||||
service_id: Service ID to freeze
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with freeze confirmation
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return await _make_request("POST", f"/services/{service_id}/freeze", public_key, secret_key, {})
|
||||
|
||||
|
||||
async def unfreeze_service(
|
||||
service_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Unfreeze a service (resume execution).
|
||||
|
||||
Args:
|
||||
service_id: Service ID to unfreeze
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with unfreeze confirmation
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return await _make_request("POST", f"/services/{service_id}/unfreeze", public_key, secret_key, {})
|
||||
|
||||
|
||||
async def lock_service(
|
||||
service_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Lock a service to prevent accidental deletion.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to lock
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with lock confirmation
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return await _make_request("POST", f"/services/{service_id}/lock", public_key, secret_key, {})
|
||||
|
||||
|
||||
async def unlock_service(
|
||||
service_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Unlock a service to allow deletion.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to unlock
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with unlock confirmation
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return await _make_request("POST", f"/services/{service_id}/unlock", public_key, secret_key, {})
|
||||
|
||||
|
||||
async def get_service_logs(
|
||||
service_id: str,
|
||||
all_logs: bool = False,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Get bootstrap/runtime logs for a service.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to get logs for
|
||||
all_logs: If True, get all logs; if False, get last ~9000 lines (tail)
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict containing "log" field with log content
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
path = f"/services/{service_id}/logs"
|
||||
if all_logs:
|
||||
path += "?all=true"
|
||||
return await _make_request("GET", path, public_key, secret_key)
|
||||
|
||||
|
||||
async def get_service_env(
|
||||
service_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Get environment vault status for a service.
|
||||
|
||||
Returns metadata about the vault (has_vault, count, updated_at)
|
||||
but NOT the actual secrets. Use export_service_env to retrieve secrets.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to get env status for
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with has_vault, count, updated_at fields
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return await _make_request("GET", f"/services/{service_id}/env", public_key, secret_key)
|
||||
|
||||
|
||||
async def set_service_env(
|
||||
service_id: str,
|
||||
env_dict: Dict[str, str],
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Set environment variables for a service.
|
||||
|
||||
Replaces the entire environment vault with the provided variables.
|
||||
Variables are encrypted at rest and injected into the container.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to set env for
|
||||
env_dict: Dictionary of environment variables (KEY: VALUE)
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with count of variables set
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
# Convert dict to .env format for the API
|
||||
env_content = "\n".join(f"{k}={v}" for k, v in env_dict.items())
|
||||
|
||||
# Note: This endpoint expects text/plain body, but we'll send as JSON
|
||||
# and let the API handle conversion
|
||||
return await _make_request(
|
||||
"POST",
|
||||
f"/services/{service_id}/env",
|
||||
public_key,
|
||||
secret_key,
|
||||
{"env": env_content},
|
||||
)
|
||||
|
||||
|
||||
async def delete_service_env(
|
||||
service_id: str,
|
||||
keys: Optional[List[str]] = None,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Delete environment vault or specific keys from a service.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to delete env from
|
||||
keys: Optional list of specific keys to delete; if None, deletes entire vault
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with deletion confirmation
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
path = f"/services/{service_id}/env"
|
||||
# If specific keys provided, could add as query params (API dependent)
|
||||
return await _make_request("DELETE", path, public_key, secret_key)
|
||||
|
||||
|
||||
async def export_service_env(
|
||||
service_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Export environment vault secrets for a service.
|
||||
|
||||
Requires HMAC authentication to prove ownership.
|
||||
Returns the actual secret values in .env format.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to export env from
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict containing "env" field with KEY=VALUE content
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return await _make_request("POST", f"/services/{service_id}/env/export", public_key, secret_key, {})
|
||||
|
||||
|
||||
async def redeploy_service(
|
||||
service_id: str,
|
||||
bootstrap: Optional[str] = None,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Redeploy a service (re-run bootstrap script).
|
||||
|
||||
Bootstrap scripts should be idempotent for proper upgrade behavior.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to redeploy
|
||||
bootstrap: Optional new bootstrap script/URL (uses existing if not provided)
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with redeploy confirmation
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
data: Dict[str, Any] = {}
|
||||
if bootstrap:
|
||||
if bootstrap.startswith("http://") or bootstrap.startswith("https://"):
|
||||
data["bootstrap"] = bootstrap
|
||||
else:
|
||||
data["bootstrap_content"] = bootstrap
|
||||
|
||||
return await _make_request("POST", f"/services/{service_id}/redeploy", public_key, secret_key, data)
|
||||
|
||||
|
||||
async def execute_in_service(
|
||||
service_id: str,
|
||||
command: str,
|
||||
timeout: int = 30000,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Execute a command in a running service container.
|
||||
|
||||
Uses async job polling for long-running commands.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to execute command in
|
||||
command: Shell command to execute
|
||||
timeout: Command timeout in milliseconds (default 30000)
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with job_id for async polling, or direct result
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return await _make_request(
|
||||
"POST",
|
||||
f"/services/{service_id}/execute",
|
||||
public_key,
|
||||
secret_key,
|
||||
{"command": command, "timeout": timeout},
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Additional Snapshot Functions
|
||||
# =============================================================================
|
||||
|
||||
|
||||
async def lock_snapshot(
|
||||
snapshot_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Lock a snapshot to prevent accidental deletion.
|
||||
|
||||
Args:
|
||||
snapshot_id: Snapshot ID to lock
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with lock confirmation
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return await _make_request("POST", f"/snapshots/{snapshot_id}/lock", public_key, secret_key, {})
|
||||
|
||||
|
||||
async def unlock_snapshot(
|
||||
snapshot_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Unlock a snapshot to allow deletion.
|
||||
|
||||
Args:
|
||||
snapshot_id: Snapshot ID to unlock
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with unlock confirmation
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return await _make_request("POST", f"/snapshots/{snapshot_id}/unlock", public_key, secret_key, {})
|
||||
|
||||
|
||||
async def clone_snapshot(
|
||||
snapshot_id: str,
|
||||
clone_type: str = "session",
|
||||
name: Optional[str] = None,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
shell: Optional[str] = None,
|
||||
ports: Optional[List[int]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Clone a snapshot to create a new session or service.
|
||||
|
||||
Args:
|
||||
snapshot_id: Snapshot ID to clone from
|
||||
clone_type: Type of resource to create ("session" or "service")
|
||||
name: Optional name for the new resource
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
shell: Optional shell for session clones
|
||||
ports: Optional ports list for service clones
|
||||
|
||||
Returns:
|
||||
Response dict containing session_id or service_id
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
data: Dict[str, Any] = {"type": clone_type}
|
||||
if name:
|
||||
data["name"] = name
|
||||
if shell:
|
||||
data["shell"] = shell
|
||||
if ports:
|
||||
data["ports"] = ports
|
||||
|
||||
return await _make_request("POST", f"/snapshots/{snapshot_id}/clone", public_key, secret_key, data)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Key Validation
|
||||
# =============================================================================
|
||||
|
||||
|
||||
PORTAL_BASE = "https://unsandbox.com"
|
||||
|
||||
|
||||
async def validate_keys(
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Validate API keys against the portal.
|
||||
|
||||
Checks if the keys are valid, not expired, and not suspended.
|
||||
|
||||
Args:
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with validation result:
|
||||
- valid: True if keys are valid
|
||||
- tier: Account tier level
|
||||
- expires_at: Expiration timestamp (if applicable)
|
||||
- reason: Reason for invalid status (if applicable)
|
||||
|
||||
Raises:
|
||||
aiohttp.ClientError: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
|
||||
url = f"{PORTAL_BASE}/keys/validate"
|
||||
timestamp = int(time.time())
|
||||
body = ""
|
||||
|
||||
signature = _sign_request(secret_key, timestamp, "POST", "/keys/validate", body)
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {public_key}",
|
||||
"X-Timestamp": str(timestamp),
|
||||
"X-Signature": signature,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(url, headers=headers, data=body, timeout=aiohttp.ClientTimeout(total=30)) as resp:
|
||||
resp.raise_for_status()
|
||||
return await resp.json()
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ unsandbox.com Python SDK (Synchronous)
|
|||
|
||||
Library Usage:
|
||||
from un import (
|
||||
# Execution
|
||||
execute_code,
|
||||
execute_async,
|
||||
get_job,
|
||||
|
|
@ -13,11 +14,44 @@ Library Usage:
|
|||
list_jobs,
|
||||
get_languages,
|
||||
detect_language,
|
||||
# Sessions
|
||||
list_sessions,
|
||||
get_session,
|
||||
create_session,
|
||||
delete_session,
|
||||
freeze_session,
|
||||
unfreeze_session,
|
||||
boost_session,
|
||||
unboost_session,
|
||||
shell_session,
|
||||
# Services
|
||||
list_services,
|
||||
create_service,
|
||||
get_service,
|
||||
update_service,
|
||||
delete_service,
|
||||
freeze_service,
|
||||
unfreeze_service,
|
||||
lock_service,
|
||||
unlock_service,
|
||||
get_service_logs,
|
||||
get_service_env,
|
||||
set_service_env,
|
||||
delete_service_env,
|
||||
export_service_env,
|
||||
redeploy_service,
|
||||
execute_in_service,
|
||||
# Snapshots
|
||||
session_snapshot,
|
||||
service_snapshot,
|
||||
list_snapshots,
|
||||
restore_snapshot,
|
||||
delete_snapshot,
|
||||
lock_snapshot,
|
||||
unlock_snapshot,
|
||||
clone_snapshot,
|
||||
# Key validation
|
||||
validate_keys,
|
||||
)
|
||||
|
||||
# Execute code synchronously
|
||||
|
|
@ -221,6 +255,8 @@ def _make_request(
|
|||
response = requests.get(url, headers=headers, timeout=120)
|
||||
elif method == "POST":
|
||||
response = requests.post(url, headers=headers, json=data, timeout=120)
|
||||
elif method == "PATCH":
|
||||
response = requests.patch(url, headers=headers, json=data, timeout=120)
|
||||
elif method == "DELETE":
|
||||
response = requests.delete(url, headers=headers, timeout=120)
|
||||
else:
|
||||
|
|
@ -718,3 +754,912 @@ def delete_snapshot(
|
|||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return _make_request("DELETE", f"/snapshots/{snapshot_id}", public_key, secret_key)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Session Management Functions
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def list_sessions(
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
List all sessions for the authenticated account.
|
||||
|
||||
Args:
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
List of session dicts containing id, container_name, status, etc.
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
response = _make_request("GET", "/sessions", public_key, secret_key)
|
||||
return response.get("sessions", [])
|
||||
|
||||
|
||||
def get_session(
|
||||
session_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Get details of a specific session.
|
||||
|
||||
Args:
|
||||
session_id: Session ID to get details for
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Session details dict
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return _make_request("GET", f"/sessions/{session_id}", public_key, secret_key)
|
||||
|
||||
|
||||
def create_session(
|
||||
language: Optional[str] = None,
|
||||
network_mode: str = "zerotrust",
|
||||
ttl: int = 3600,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
shell: Optional[str] = None,
|
||||
multiplexer: Optional[str] = None,
|
||||
vcpu: int = 1,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Create a new interactive session.
|
||||
|
||||
Args:
|
||||
language: Optional programming language for the session
|
||||
network_mode: Network mode - "zerotrust" (default, no network) or "semitrusted" (with network)
|
||||
ttl: Time to live in seconds (default 3600)
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
shell: Optional shell to use (e.g., "bash", "python3")
|
||||
multiplexer: Optional terminal multiplexer ("tmux" or "screen")
|
||||
vcpu: Number of vCPUs (1-8, default 1)
|
||||
|
||||
Returns:
|
||||
Response dict containing session_id, container_name, etc.
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
data: Dict[str, Any] = {
|
||||
"network_mode": network_mode,
|
||||
"ttl": ttl,
|
||||
}
|
||||
if language:
|
||||
data["language"] = language
|
||||
if shell:
|
||||
data["shell"] = shell
|
||||
if multiplexer:
|
||||
data["multiplexer"] = multiplexer
|
||||
if vcpu > 1:
|
||||
data["vcpu"] = vcpu
|
||||
|
||||
return _make_request("POST", "/sessions", public_key, secret_key, data)
|
||||
|
||||
|
||||
def delete_session(
|
||||
session_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Delete/terminate a session.
|
||||
|
||||
Args:
|
||||
session_id: Session ID to delete
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with deletion confirmation and optional artifacts
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return _make_request("DELETE", f"/sessions/{session_id}", public_key, secret_key)
|
||||
|
||||
|
||||
def freeze_session(
|
||||
session_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Freeze a session (pause execution, preserve state).
|
||||
|
||||
Args:
|
||||
session_id: Session ID to freeze
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with freeze confirmation
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return _make_request("POST", f"/sessions/{session_id}/freeze", public_key, secret_key, {})
|
||||
|
||||
|
||||
def unfreeze_session(
|
||||
session_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Unfreeze a session (resume execution).
|
||||
|
||||
Args:
|
||||
session_id: Session ID to unfreeze
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with unfreeze confirmation
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return _make_request("POST", f"/sessions/{session_id}/unfreeze", public_key, secret_key, {})
|
||||
|
||||
|
||||
def boost_session(
|
||||
session_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Boost a session (increase resources).
|
||||
|
||||
Args:
|
||||
session_id: Session ID to boost
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with boost confirmation
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return _make_request("POST", f"/sessions/{session_id}/boost", public_key, secret_key, {})
|
||||
|
||||
|
||||
def unboost_session(
|
||||
session_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Unboost a session (return to normal resources).
|
||||
|
||||
Args:
|
||||
session_id: Session ID to unboost
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with unboost confirmation
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return _make_request("POST", f"/sessions/{session_id}/unboost", public_key, secret_key, {})
|
||||
|
||||
|
||||
def shell_session(
|
||||
session_id: str,
|
||||
command: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Execute a shell command in a session.
|
||||
|
||||
Note: This is for one-off commands. For interactive shell access,
|
||||
use WebSocket connection to /sessions/{id}/shell.
|
||||
|
||||
Args:
|
||||
session_id: Session ID to execute command in
|
||||
command: Shell command to execute
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with command output
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return _make_request(
|
||||
"POST",
|
||||
f"/sessions/{session_id}/shell",
|
||||
public_key,
|
||||
secret_key,
|
||||
{"command": command},
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Service Management Functions
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def list_services(
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
List all services for the authenticated account.
|
||||
|
||||
Args:
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
List of service dicts containing id, name, status, ports, etc.
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
response = _make_request("GET", "/services", public_key, secret_key)
|
||||
return response.get("services", [])
|
||||
|
||||
|
||||
def create_service(
|
||||
name: str,
|
||||
ports: List[int],
|
||||
bootstrap: Optional[str] = None,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
network_mode: str = "semitrusted",
|
||||
custom_domains: Optional[List[str]] = None,
|
||||
vcpu: int = 1,
|
||||
service_type: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Create a new persistent service.
|
||||
|
||||
Args:
|
||||
name: Service name (used for subdomain: name.on.unsandbox.com)
|
||||
ports: List of ports to expose (e.g., [80, 443])
|
||||
bootstrap: Bootstrap script content, URL, or inline command
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
network_mode: Network mode (default "semitrusted" for services)
|
||||
custom_domains: Optional list of custom domain names
|
||||
vcpu: Number of vCPUs (1-8, default 1)
|
||||
service_type: Optional service type for SRV records (e.g., "minecraft")
|
||||
|
||||
Returns:
|
||||
Response dict containing service_id, etc.
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
data: Dict[str, Any] = {
|
||||
"name": name,
|
||||
"ports": ports,
|
||||
"network_mode": network_mode,
|
||||
}
|
||||
if bootstrap:
|
||||
# Check if it looks like a URL
|
||||
if bootstrap.startswith("http://") or bootstrap.startswith("https://"):
|
||||
data["bootstrap"] = bootstrap
|
||||
else:
|
||||
data["bootstrap_content"] = bootstrap
|
||||
if custom_domains:
|
||||
data["custom_domains"] = custom_domains
|
||||
if vcpu > 1:
|
||||
data["vcpu"] = vcpu
|
||||
if service_type:
|
||||
data["service_type"] = service_type
|
||||
|
||||
return _make_request("POST", "/services", public_key, secret_key, data)
|
||||
|
||||
|
||||
def get_service(
|
||||
service_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Get details of a specific service.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to get details for
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Service details dict
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return _make_request("GET", f"/services/{service_id}", public_key, secret_key)
|
||||
|
||||
|
||||
def update_service(
|
||||
service_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
vcpu: Optional[int] = None,
|
||||
**kwargs,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Update a service (e.g., resize vCPU/memory).
|
||||
|
||||
Args:
|
||||
service_id: Service ID to update
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
vcpu: Optional new vCPU count (1-8)
|
||||
**kwargs: Additional fields to update
|
||||
|
||||
Returns:
|
||||
Response dict with update confirmation
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
data: Dict[str, Any] = {}
|
||||
if vcpu is not None:
|
||||
data["vcpu"] = vcpu
|
||||
data.update(kwargs)
|
||||
|
||||
return _make_request("PATCH", f"/services/{service_id}", public_key, secret_key, data)
|
||||
|
||||
|
||||
def delete_service(
|
||||
service_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Delete/destroy a service.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to delete
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with deletion confirmation
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return _make_request("DELETE", f"/services/{service_id}", public_key, secret_key)
|
||||
|
||||
|
||||
def freeze_service(
|
||||
service_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Freeze a service (pause execution, preserve state).
|
||||
|
||||
Args:
|
||||
service_id: Service ID to freeze
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with freeze confirmation
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return _make_request("POST", f"/services/{service_id}/freeze", public_key, secret_key, {})
|
||||
|
||||
|
||||
def unfreeze_service(
|
||||
service_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Unfreeze a service (resume execution).
|
||||
|
||||
Args:
|
||||
service_id: Service ID to unfreeze
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with unfreeze confirmation
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return _make_request("POST", f"/services/{service_id}/unfreeze", public_key, secret_key, {})
|
||||
|
||||
|
||||
def lock_service(
|
||||
service_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Lock a service to prevent accidental deletion.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to lock
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with lock confirmation
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return _make_request("POST", f"/services/{service_id}/lock", public_key, secret_key, {})
|
||||
|
||||
|
||||
def unlock_service(
|
||||
service_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Unlock a service to allow deletion.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to unlock
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with unlock confirmation
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return _make_request("POST", f"/services/{service_id}/unlock", public_key, secret_key, {})
|
||||
|
||||
|
||||
def get_service_logs(
|
||||
service_id: str,
|
||||
all_logs: bool = False,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Get bootstrap/runtime logs for a service.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to get logs for
|
||||
all_logs: If True, get all logs; if False, get last ~9000 lines (tail)
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict containing "log" field with log content
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
path = f"/services/{service_id}/logs"
|
||||
if all_logs:
|
||||
path += "?all=true"
|
||||
return _make_request("GET", path, public_key, secret_key)
|
||||
|
||||
|
||||
def get_service_env(
|
||||
service_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Get environment vault status for a service.
|
||||
|
||||
Returns metadata about the vault (has_vault, count, updated_at)
|
||||
but NOT the actual secrets. Use export_service_env to retrieve secrets.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to get env status for
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with has_vault, count, updated_at fields
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return _make_request("GET", f"/services/{service_id}/env", public_key, secret_key)
|
||||
|
||||
|
||||
def set_service_env(
|
||||
service_id: str,
|
||||
env_dict: Dict[str, str],
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Set environment variables for a service.
|
||||
|
||||
Replaces the entire environment vault with the provided variables.
|
||||
Variables are encrypted at rest and injected into the container.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to set env for
|
||||
env_dict: Dictionary of environment variables (KEY: VALUE)
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with count of variables set
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
# Convert dict to .env format for the API
|
||||
env_content = "\n".join(f"{k}={v}" for k, v in env_dict.items())
|
||||
|
||||
# Note: This endpoint expects text/plain body, but we'll send as JSON
|
||||
# and let the API handle conversion
|
||||
return _make_request(
|
||||
"POST",
|
||||
f"/services/{service_id}/env",
|
||||
public_key,
|
||||
secret_key,
|
||||
{"env": env_content},
|
||||
)
|
||||
|
||||
|
||||
def delete_service_env(
|
||||
service_id: str,
|
||||
keys: Optional[List[str]] = None,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Delete environment vault or specific keys from a service.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to delete env from
|
||||
keys: Optional list of specific keys to delete; if None, deletes entire vault
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with deletion confirmation
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
path = f"/services/{service_id}/env"
|
||||
# If specific keys provided, could add as query params (API dependent)
|
||||
return _make_request("DELETE", path, public_key, secret_key)
|
||||
|
||||
|
||||
def export_service_env(
|
||||
service_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Export environment vault secrets for a service.
|
||||
|
||||
Requires HMAC authentication to prove ownership.
|
||||
Returns the actual secret values in .env format.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to export env from
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict containing "env" field with KEY=VALUE content
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return _make_request("POST", f"/services/{service_id}/env/export", public_key, secret_key, {})
|
||||
|
||||
|
||||
def redeploy_service(
|
||||
service_id: str,
|
||||
bootstrap: Optional[str] = None,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Redeploy a service (re-run bootstrap script).
|
||||
|
||||
Bootstrap scripts should be idempotent for proper upgrade behavior.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to redeploy
|
||||
bootstrap: Optional new bootstrap script/URL (uses existing if not provided)
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with redeploy confirmation
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
data: Dict[str, Any] = {}
|
||||
if bootstrap:
|
||||
if bootstrap.startswith("http://") or bootstrap.startswith("https://"):
|
||||
data["bootstrap"] = bootstrap
|
||||
else:
|
||||
data["bootstrap_content"] = bootstrap
|
||||
|
||||
return _make_request("POST", f"/services/{service_id}/redeploy", public_key, secret_key, data)
|
||||
|
||||
|
||||
def execute_in_service(
|
||||
service_id: str,
|
||||
command: str,
|
||||
timeout: int = 30000,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Execute a command in a running service container.
|
||||
|
||||
Uses async job polling for long-running commands.
|
||||
|
||||
Args:
|
||||
service_id: Service ID to execute command in
|
||||
command: Shell command to execute
|
||||
timeout: Command timeout in milliseconds (default 30000)
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with job_id for async polling, or direct result
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return _make_request(
|
||||
"POST",
|
||||
f"/services/{service_id}/execute",
|
||||
public_key,
|
||||
secret_key,
|
||||
{"command": command, "timeout": timeout},
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Additional Snapshot Functions
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def lock_snapshot(
|
||||
snapshot_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Lock a snapshot to prevent accidental deletion.
|
||||
|
||||
Args:
|
||||
snapshot_id: Snapshot ID to lock
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with lock confirmation
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return _make_request("POST", f"/snapshots/{snapshot_id}/lock", public_key, secret_key, {})
|
||||
|
||||
|
||||
def unlock_snapshot(
|
||||
snapshot_id: str,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Unlock a snapshot to allow deletion.
|
||||
|
||||
Args:
|
||||
snapshot_id: Snapshot ID to unlock
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with unlock confirmation
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
return _make_request("POST", f"/snapshots/{snapshot_id}/unlock", public_key, secret_key, {})
|
||||
|
||||
|
||||
def clone_snapshot(
|
||||
snapshot_id: str,
|
||||
clone_type: str = "session",
|
||||
name: Optional[str] = None,
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
shell: Optional[str] = None,
|
||||
ports: Optional[List[int]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Clone a snapshot to create a new session or service.
|
||||
|
||||
Args:
|
||||
snapshot_id: Snapshot ID to clone from
|
||||
clone_type: Type of resource to create ("session" or "service")
|
||||
name: Optional name for the new resource
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
shell: Optional shell for session clones
|
||||
ports: Optional ports list for service clones
|
||||
|
||||
Returns:
|
||||
Response dict containing session_id or service_id
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
data: Dict[str, Any] = {"type": clone_type}
|
||||
if name:
|
||||
data["name"] = name
|
||||
if shell:
|
||||
data["shell"] = shell
|
||||
if ports:
|
||||
data["ports"] = ports
|
||||
|
||||
return _make_request("POST", f"/snapshots/{snapshot_id}/clone", public_key, secret_key, data)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Key Validation
|
||||
# =============================================================================
|
||||
|
||||
|
||||
PORTAL_BASE = "https://unsandbox.com"
|
||||
|
||||
|
||||
def validate_keys(
|
||||
public_key: Optional[str] = None,
|
||||
secret_key: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Validate API keys against the portal.
|
||||
|
||||
Checks if the keys are valid, not expired, and not suspended.
|
||||
|
||||
Args:
|
||||
public_key: Optional API key
|
||||
secret_key: Optional API secret
|
||||
|
||||
Returns:
|
||||
Response dict with validation result:
|
||||
- valid: True if keys are valid
|
||||
- tier: Account tier level
|
||||
- expires_at: Expiration timestamp (if applicable)
|
||||
- reason: Reason for invalid status (if applicable)
|
||||
|
||||
Raises:
|
||||
requests.RequestException: Network errors
|
||||
ValueError: Invalid response format
|
||||
CredentialsError: Missing credentials
|
||||
"""
|
||||
public_key, secret_key = _resolve_credentials(public_key, secret_key)
|
||||
|
||||
url = f"{PORTAL_BASE}/keys/validate"
|
||||
timestamp = int(time.time())
|
||||
body = ""
|
||||
|
||||
signature = _sign_request(secret_key, timestamp, "POST", "/keys/validate", body)
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {public_key}",
|
||||
"X-Timestamp": str(timestamp),
|
||||
"X-Signature": signature,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers, data=body, timeout=30)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
|
|
|||
|
|
@ -477,6 +477,619 @@ module UnAsync
|
|||
end
|
||||
end
|
||||
|
||||
# Lock a snapshot to prevent deletion
|
||||
#
|
||||
# @param snapshot_id [String] Snapshot ID to lock
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Hash>] Future resolving to response hash with lock confirmation
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# UnAsync.lock_snapshot(snapshot_id).value
|
||||
def lock_snapshot(snapshot_id, public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request_sync('POST', "/snapshots/#{snapshot_id}/lock", pk, sk, {})
|
||||
end
|
||||
end
|
||||
|
||||
# Unlock a snapshot to allow deletion
|
||||
#
|
||||
# @param snapshot_id [String] Snapshot ID to unlock
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Hash>] Future resolving to response hash with unlock confirmation
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# UnAsync.unlock_snapshot(snapshot_id).value
|
||||
def unlock_snapshot(snapshot_id, public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request_sync('POST', "/snapshots/#{snapshot_id}/unlock", pk, sk, {})
|
||||
end
|
||||
end
|
||||
|
||||
# Clone a snapshot to create a new session or service
|
||||
#
|
||||
# @param snapshot_id [String] Snapshot ID to clone
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @param name [String, nil] Optional name for the cloned resource
|
||||
# @param type [String] Type of resource to create ("session" or "service")
|
||||
# @param shell [String, nil] Optional shell for session clones
|
||||
# @param ports [Array<Integer>, nil] Optional ports for service clones
|
||||
# @return [Future<Hash>] Future resolving to response hash with cloned resource info
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example Clone to session
|
||||
# result = UnAsync.clone_snapshot(snapshot_id, type: "session").value
|
||||
# puts result["session_id"]
|
||||
def clone_snapshot(snapshot_id, public_key: nil, secret_key: nil, name: nil, type: 'session', shell: nil, ports: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
data = { type: type }
|
||||
data[:name] = name if name
|
||||
data[:shell] = shell if shell
|
||||
data[:ports] = ports if ports
|
||||
make_request_sync('POST', "/snapshots/#{snapshot_id}/clone", pk, sk, data)
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Session Functions
|
||||
# ============================================================================
|
||||
|
||||
# List all sessions for the authenticated account
|
||||
#
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Array<Hash>>] Future resolving to list of session hashes
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# sessions = UnAsync.list_sessions.value
|
||||
# sessions.each { |s| puts "#{s['id']}: #{s['status']}" }
|
||||
def list_sessions(public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
response = make_request_sync('GET', '/sessions', pk, sk)
|
||||
response['sessions'] || []
|
||||
end
|
||||
end
|
||||
|
||||
# Get session details by ID
|
||||
#
|
||||
# @param session_id [String] Session ID to retrieve
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Hash>] Future resolving to session details hash
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# session = UnAsync.get_session(session_id).value
|
||||
# puts session["status"]
|
||||
def get_session(session_id, public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request_sync('GET', "/sessions/#{session_id}", pk, sk)
|
||||
end
|
||||
end
|
||||
|
||||
# Create a new interactive session
|
||||
#
|
||||
# @param language [String] Shell or language for the session (e.g., "bash", "python3")
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @param network_mode [String] Network mode ("zerotrust" or "semitrusted")
|
||||
# @param ttl [Integer] Time-to-live in seconds (default: 3600)
|
||||
# @param multiplexer [String, nil] Terminal multiplexer ("tmux" or "screen")
|
||||
# @param vcpu [Integer] Number of vCPUs (1-8)
|
||||
# @return [Future<Hash>] Future resolving to response hash with session_id and container_name
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# result = UnAsync.create_session("bash", network_mode: "semitrusted").value
|
||||
# puts result["session_id"]
|
||||
def create_session(language, public_key: nil, secret_key: nil, network_mode: 'zerotrust', ttl: 3600, multiplexer: nil, vcpu: 1)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
data = {
|
||||
network_mode: network_mode,
|
||||
ttl: ttl
|
||||
}
|
||||
data[:shell] = language if language
|
||||
data[:multiplexer] = multiplexer if multiplexer
|
||||
data[:vcpu] = vcpu if vcpu > 1
|
||||
make_request_sync('POST', '/sessions', pk, sk, data)
|
||||
end
|
||||
end
|
||||
|
||||
# Delete (terminate) a session
|
||||
#
|
||||
# @param session_id [String] Session ID to delete
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Hash>] Future resolving to response hash with deletion confirmation
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# UnAsync.delete_session(session_id).value
|
||||
def delete_session(session_id, public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request_sync('DELETE', "/sessions/#{session_id}", pk, sk)
|
||||
end
|
||||
end
|
||||
|
||||
# Freeze a session (pause execution, reduce resource usage)
|
||||
#
|
||||
# @param session_id [String] Session ID to freeze
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Hash>] Future resolving to response hash with freeze confirmation
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# UnAsync.freeze_session(session_id).value
|
||||
def freeze_session(session_id, public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request_sync('POST', "/sessions/#{session_id}/freeze", pk, sk, {})
|
||||
end
|
||||
end
|
||||
|
||||
# Unfreeze a session (resume execution)
|
||||
#
|
||||
# @param session_id [String] Session ID to unfreeze
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Hash>] Future resolving to response hash with unfreeze confirmation
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# UnAsync.unfreeze_session(session_id).value
|
||||
def unfreeze_session(session_id, public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request_sync('POST', "/sessions/#{session_id}/unfreeze", pk, sk, {})
|
||||
end
|
||||
end
|
||||
|
||||
# Boost a session (increase vCPU allocation)
|
||||
#
|
||||
# @param session_id [String] Session ID to boost
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Hash>] Future resolving to response hash with boost confirmation
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# UnAsync.boost_session(session_id).value
|
||||
def boost_session(session_id, public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request_sync('POST', "/sessions/#{session_id}/boost", pk, sk, {})
|
||||
end
|
||||
end
|
||||
|
||||
# Unboost a session (reduce vCPU allocation)
|
||||
#
|
||||
# @param session_id [String] Session ID to unboost
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Hash>] Future resolving to response hash with unboost confirmation
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# UnAsync.unboost_session(session_id).value
|
||||
def unboost_session(session_id, public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request_sync('POST', "/sessions/#{session_id}/unboost", pk, sk, {})
|
||||
end
|
||||
end
|
||||
|
||||
# Execute a shell command in an existing session
|
||||
#
|
||||
# @param session_id [String] Session ID to execute command in
|
||||
# @param command [String] Command to execute
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Hash>] Future resolving to response hash with command output
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# result = UnAsync.shell_session(session_id, "ls -la").value
|
||||
# puts result["stdout"]
|
||||
def shell_session(session_id, command, public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request_sync('POST', "/sessions/#{session_id}/shell", pk, sk, { command: command })
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Service Functions
|
||||
# ============================================================================
|
||||
|
||||
# List all services for the authenticated account
|
||||
#
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Array<Hash>>] Future resolving to list of service hashes
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# services = UnAsync.list_services.value
|
||||
# services.each { |s| puts "#{s['id']}: #{s['name']} (#{s['state']})" }
|
||||
def list_services(public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
response = make_request_sync('GET', '/services', pk, sk)
|
||||
response['services'] || []
|
||||
end
|
||||
end
|
||||
|
||||
# Create a new persistent service
|
||||
#
|
||||
# @param name [String] Service name
|
||||
# @param ports [Array<Integer>] Ports to expose
|
||||
# @param bootstrap [String] Bootstrap script content or URL
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @param network_mode [String] Network mode ("zerotrust" or "semitrusted")
|
||||
# @param vcpu [Integer] Number of vCPUs (1-8)
|
||||
# @param custom_domains [Array<String>, nil] Custom domains for the service
|
||||
# @param service_type [String, nil] Service type for SRV records
|
||||
# @return [Future<Hash>] Future resolving to response hash with service_id
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# result = UnAsync.create_service("web", [80, 443], "apt install -y nginx && nginx").value
|
||||
# puts result["service_id"]
|
||||
def create_service(name, ports, bootstrap, public_key: nil, secret_key: nil, network_mode: 'semitrusted', vcpu: 1, custom_domains: nil, service_type: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
data = {
|
||||
name: name,
|
||||
ports: ports,
|
||||
bootstrap: bootstrap,
|
||||
network_mode: network_mode
|
||||
}
|
||||
data[:vcpu] = vcpu if vcpu > 1
|
||||
data[:custom_domains] = custom_domains if custom_domains
|
||||
data[:service_type] = service_type if service_type
|
||||
make_request_sync('POST', '/services', pk, sk, data)
|
||||
end
|
||||
end
|
||||
|
||||
# Get service details by ID
|
||||
#
|
||||
# @param service_id [String] Service ID to retrieve
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Hash>] Future resolving to service details hash
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# service = UnAsync.get_service(service_id).value
|
||||
# puts service["status"]
|
||||
def get_service(service_id, public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request_sync('GET', "/services/#{service_id}", pk, sk)
|
||||
end
|
||||
end
|
||||
|
||||
# Update a service (e.g., resize vCPU)
|
||||
#
|
||||
# @param service_id [String] Service ID to update
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @param vcpu [Integer, nil] New vCPU count (1-8)
|
||||
# @param name [String, nil] New service name
|
||||
# @return [Future<Hash>] Future resolving to response hash with update confirmation
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# UnAsync.update_service(service_id, vcpu: 4).value
|
||||
def update_service(service_id, public_key: nil, secret_key: nil, vcpu: nil, name: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
data = {}
|
||||
data[:vcpu] = vcpu if vcpu
|
||||
data[:name] = name if name
|
||||
make_request_sync('PATCH', "/services/#{service_id}", pk, sk, data)
|
||||
end
|
||||
end
|
||||
|
||||
# Delete (destroy) a service
|
||||
#
|
||||
# @param service_id [String] Service ID to delete
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Hash>] Future resolving to response hash with deletion confirmation
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# UnAsync.delete_service(service_id).value
|
||||
def delete_service(service_id, public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request_sync('DELETE', "/services/#{service_id}", pk, sk)
|
||||
end
|
||||
end
|
||||
|
||||
# Freeze a service (stop container, reduce resource usage)
|
||||
#
|
||||
# @param service_id [String] Service ID to freeze
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Hash>] Future resolving to response hash with freeze confirmation
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# UnAsync.freeze_service(service_id).value
|
||||
def freeze_service(service_id, public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request_sync('POST', "/services/#{service_id}/freeze", pk, sk, {})
|
||||
end
|
||||
end
|
||||
|
||||
# Unfreeze a service (start container)
|
||||
#
|
||||
# @param service_id [String] Service ID to unfreeze
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Hash>] Future resolving to response hash with unfreeze confirmation
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# UnAsync.unfreeze_service(service_id).value
|
||||
def unfreeze_service(service_id, public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request_sync('POST', "/services/#{service_id}/unfreeze", pk, sk, {})
|
||||
end
|
||||
end
|
||||
|
||||
# Lock a service to prevent deletion
|
||||
#
|
||||
# @param service_id [String] Service ID to lock
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Hash>] Future resolving to response hash with lock confirmation
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# UnAsync.lock_service(service_id).value
|
||||
def lock_service(service_id, public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request_sync('POST', "/services/#{service_id}/lock", pk, sk, {})
|
||||
end
|
||||
end
|
||||
|
||||
# Unlock a service to allow deletion
|
||||
#
|
||||
# @param service_id [String] Service ID to unlock
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Hash>] Future resolving to response hash with unlock confirmation
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# UnAsync.unlock_service(service_id).value
|
||||
def unlock_service(service_id, public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request_sync('POST', "/services/#{service_id}/unlock", pk, sk, {})
|
||||
end
|
||||
end
|
||||
|
||||
# Get service logs (bootstrap output)
|
||||
#
|
||||
# @param service_id [String] Service ID to get logs for
|
||||
# @param all [Boolean] If true, get all logs; if false, get last 9000 lines
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Hash>] Future resolving to response hash with log content
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# logs = UnAsync.get_service_logs(service_id).value
|
||||
# puts logs["log"]
|
||||
def get_service_logs(service_id, all: false, public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
path = "/services/#{service_id}/logs"
|
||||
path += '?all=true' if all
|
||||
make_request_sync('GET', path, pk, sk)
|
||||
end
|
||||
end
|
||||
|
||||
# Get service environment vault status
|
||||
#
|
||||
# @param service_id [String] Service ID to get env status for
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Hash>] Future resolving to response hash with vault status
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# status = UnAsync.get_service_env(service_id).value
|
||||
# puts "Variables: #{status['count']}"
|
||||
def get_service_env(service_id, public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request_sync('GET', "/services/#{service_id}/env", pk, sk)
|
||||
end
|
||||
end
|
||||
|
||||
# Set service environment variables (replaces existing vault)
|
||||
#
|
||||
# @param service_id [String] Service ID to set env for
|
||||
# @param env [String] Environment content in .env format (KEY=VALUE per line)
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Hash>] Future resolving to response hash with confirmation
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# UnAsync.set_service_env(service_id, "API_KEY=secret\nDEBUG=true").value
|
||||
def set_service_env(service_id, env, public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request_text_sync('PUT', "/services/#{service_id}/env", pk, sk, env)
|
||||
end
|
||||
end
|
||||
|
||||
# Delete service environment vault
|
||||
#
|
||||
# @param service_id [String] Service ID to delete env for
|
||||
# @param keys [Array<String>, nil] Specific keys to delete (nil = delete entire vault)
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Hash>] Future resolving to response hash with deletion confirmation
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example Delete entire vault
|
||||
# UnAsync.delete_service_env(service_id).value
|
||||
def delete_service_env(service_id, keys: nil, public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
if keys
|
||||
make_request_sync('DELETE', "/services/#{service_id}/env", pk, sk, { keys: keys })
|
||||
else
|
||||
make_request_sync('DELETE', "/services/#{service_id}/env", pk, sk)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Export service environment vault (returns .env format)
|
||||
#
|
||||
# @param service_id [String] Service ID to export env from
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Hash>] Future resolving to response hash with env content
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# result = UnAsync.export_service_env(service_id).value
|
||||
# puts result["env"]
|
||||
def export_service_env(service_id, public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request_sync('POST', "/services/#{service_id}/env/export", pk, sk, {})
|
||||
end
|
||||
end
|
||||
|
||||
# Redeploy a service (re-run bootstrap script)
|
||||
#
|
||||
# @param service_id [String] Service ID to redeploy
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @param bootstrap [String, nil] New bootstrap script (optional)
|
||||
# @return [Future<Hash>] Future resolving to response hash with redeploy confirmation
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# UnAsync.redeploy_service(service_id).value
|
||||
def redeploy_service(service_id, public_key: nil, secret_key: nil, bootstrap: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
data = {}
|
||||
data[:bootstrap] = bootstrap if bootstrap
|
||||
make_request_sync('POST', "/services/#{service_id}/redeploy", pk, sk, data)
|
||||
end
|
||||
end
|
||||
|
||||
# Execute a command in a running service
|
||||
#
|
||||
# @param service_id [String] Service ID to execute command in
|
||||
# @param command [String] Command to execute
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @param timeout [Integer] Command timeout in milliseconds (default: 30000)
|
||||
# @return [Future<Hash>] Future resolving to response hash with command output
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails (on .value)
|
||||
#
|
||||
# @example
|
||||
# result = UnAsync.execute_in_service(service_id, "ls -la").value
|
||||
# puts result["stdout"]
|
||||
def execute_in_service(service_id, command, public_key: nil, secret_key: nil, timeout: 30_000)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
|
||||
# Start async execution
|
||||
response = make_request_sync('POST', "/services/#{service_id}/execute", pk, sk, {
|
||||
command: command,
|
||||
timeout: timeout
|
||||
})
|
||||
|
||||
job_id = response['job_id']
|
||||
if job_id
|
||||
# Poll for completion
|
||||
wait_for_job_sync(job_id, pk, sk, (timeout / 1000) + 10)
|
||||
else
|
||||
response
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Key Validation
|
||||
# ============================================================================
|
||||
|
||||
# Validate API keys
|
||||
#
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Future<Hash>] Future resolving to response hash with validation result
|
||||
# @raise [CredentialsError] If no credentials found (on .value)
|
||||
# @raise [APIError] If API request fails or keys invalid (on .value)
|
||||
#
|
||||
# @example
|
||||
# result = UnAsync.validate_keys.value
|
||||
# puts result["valid"]
|
||||
def validate_keys(public_key: nil, secret_key: nil)
|
||||
Future.new do
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request_sync('POST', '/keys/validate', pk, sk, {})
|
||||
end
|
||||
end
|
||||
|
||||
# Execute multiple futures concurrently and wait for all to complete
|
||||
#
|
||||
# @param futures [Array<Future>] Array of futures to wait for
|
||||
|
|
@ -741,8 +1354,14 @@ module UnAsync
|
|||
http.get(uri.request_uri, headers)
|
||||
when 'POST'
|
||||
http.post(uri.request_uri, body, headers)
|
||||
when 'PATCH'
|
||||
http.patch(uri.request_uri, body, headers)
|
||||
when 'PUT'
|
||||
http.put(uri.request_uri, body, headers)
|
||||
when 'DELETE'
|
||||
http.delete(uri.request_uri, headers)
|
||||
req = Net::HTTP::Delete.new(uri.request_uri, headers)
|
||||
req.body = body if data
|
||||
http.request(req)
|
||||
else
|
||||
raise APIError, "Unsupported HTTP method: #{method}"
|
||||
end
|
||||
|
|
@ -766,6 +1385,59 @@ module UnAsync
|
|||
raise
|
||||
end
|
||||
|
||||
# Make a synchronous authenticated HTTP request with text/plain content type
|
||||
#
|
||||
# @param method [String] HTTP method (PUT)
|
||||
# @param path [String] API path
|
||||
# @param public_key [String] API public key
|
||||
# @param secret_key [String] API secret key
|
||||
# @param body [String] Plain text request body
|
||||
# @return [Hash] Parsed JSON response
|
||||
# @raise [APIError] If request fails
|
||||
def make_request_text_sync(method, path, public_key, secret_key, body)
|
||||
uri = URI.parse("#{API_BASE}#{path}")
|
||||
timestamp = Time.now.to_i
|
||||
|
||||
signature = sign_request(secret_key, timestamp, method, path, body)
|
||||
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
http.use_ssl = true
|
||||
http.open_timeout = REQUEST_TIMEOUT
|
||||
http.read_timeout = REQUEST_TIMEOUT
|
||||
|
||||
headers = {
|
||||
'Authorization' => "Bearer #{public_key}",
|
||||
'X-Timestamp' => timestamp.to_s,
|
||||
'X-Signature' => signature,
|
||||
'Content-Type' => 'text/plain'
|
||||
}
|
||||
|
||||
response = case method
|
||||
when 'PUT'
|
||||
http.put(uri.request_uri, body, headers)
|
||||
else
|
||||
raise APIError, "Unsupported HTTP method for text: #{method}"
|
||||
end
|
||||
|
||||
unless response.is_a?(Net::HTTPSuccess)
|
||||
raise APIError.new(
|
||||
"API request failed: #{response.code} #{response.message}",
|
||||
status_code: response.code.to_i,
|
||||
response_body: response.body
|
||||
)
|
||||
end
|
||||
|
||||
JSON.parse(response.body)
|
||||
rescue JSON::ParserError => e
|
||||
raise APIError, "Invalid JSON response: #{e.message}"
|
||||
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
||||
raise APIError, "Request timeout: #{e.message}"
|
||||
rescue StandardError => e
|
||||
raise APIError, "Request failed: #{e.message}" unless e.is_a?(APIError)
|
||||
|
||||
raise
|
||||
end
|
||||
|
||||
# Wait for job completion synchronously (used internally)
|
||||
#
|
||||
# @param job_id [String] Job ID
|
||||
|
|
|
|||
|
|
@ -366,6 +366,569 @@ module Un
|
|||
make_request('DELETE', "/snapshots/#{snapshot_id}", pk, sk)
|
||||
end
|
||||
|
||||
# Lock a snapshot to prevent deletion
|
||||
#
|
||||
# @param snapshot_id [String] Snapshot ID to lock
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Hash] Response hash with lock confirmation
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# Un.lock_snapshot(snapshot_id)
|
||||
def lock_snapshot(snapshot_id, public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request('POST', "/snapshots/#{snapshot_id}/lock", pk, sk, {})
|
||||
end
|
||||
|
||||
# Unlock a snapshot to allow deletion
|
||||
#
|
||||
# @param snapshot_id [String] Snapshot ID to unlock
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Hash] Response hash with unlock confirmation
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# Un.unlock_snapshot(snapshot_id)
|
||||
def unlock_snapshot(snapshot_id, public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request('POST', "/snapshots/#{snapshot_id}/unlock", pk, sk, {})
|
||||
end
|
||||
|
||||
# Clone a snapshot to create a new session or service
|
||||
#
|
||||
# @param snapshot_id [String] Snapshot ID to clone
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @param name [String, nil] Optional name for the cloned resource
|
||||
# @param type [String] Type of resource to create ("session" or "service")
|
||||
# @param shell [String, nil] Optional shell for session clones
|
||||
# @param ports [Array<Integer>, nil] Optional ports for service clones
|
||||
# @return [Hash] Response hash with cloned resource info (session_id or service_id)
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example Clone to session
|
||||
# result = Un.clone_snapshot(snapshot_id, type: "session")
|
||||
# puts result["session_id"]
|
||||
#
|
||||
# @example Clone to service
|
||||
# result = Un.clone_snapshot(snapshot_id, type: "service", ports: [80, 443])
|
||||
# puts result["service_id"]
|
||||
def clone_snapshot(snapshot_id, public_key: nil, secret_key: nil, name: nil, type: 'session', shell: nil, ports: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
data = { type: type }
|
||||
data[:name] = name if name
|
||||
data[:shell] = shell if shell
|
||||
data[:ports] = ports if ports
|
||||
make_request('POST', "/snapshots/#{snapshot_id}/clone", pk, sk, data)
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Session Functions
|
||||
# ============================================================================
|
||||
|
||||
# List all sessions for the authenticated account
|
||||
#
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Array<Hash>] List of session hashes
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# sessions = Un.list_sessions
|
||||
# sessions.each { |s| puts "#{s['id']}: #{s['status']}" }
|
||||
def list_sessions(public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
response = make_request('GET', '/sessions', pk, sk)
|
||||
response['sessions'] || []
|
||||
end
|
||||
|
||||
# Get session details by ID
|
||||
#
|
||||
# @param session_id [String] Session ID to retrieve
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Hash] Session details hash
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# session = Un.get_session(session_id)
|
||||
# puts session["status"]
|
||||
def get_session(session_id, public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request('GET', "/sessions/#{session_id}", pk, sk)
|
||||
end
|
||||
|
||||
# Create a new interactive session
|
||||
#
|
||||
# @param language [String] Shell or language for the session (e.g., "bash", "python3")
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @param network_mode [String] Network mode ("zerotrust" or "semitrusted")
|
||||
# @param ttl [Integer] Time-to-live in seconds (default: 3600)
|
||||
# @param multiplexer [String, nil] Terminal multiplexer ("tmux" or "screen")
|
||||
# @param vcpu [Integer] Number of vCPUs (1-8)
|
||||
# @return [Hash] Response hash with session_id and container_name
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# result = Un.create_session("bash", network_mode: "semitrusted")
|
||||
# puts result["session_id"]
|
||||
def create_session(language, public_key: nil, secret_key: nil, network_mode: 'zerotrust', ttl: 3600, multiplexer: nil, vcpu: 1)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
data = {
|
||||
network_mode: network_mode,
|
||||
ttl: ttl
|
||||
}
|
||||
data[:shell] = language if language
|
||||
data[:multiplexer] = multiplexer if multiplexer
|
||||
data[:vcpu] = vcpu if vcpu > 1
|
||||
make_request('POST', '/sessions', pk, sk, data)
|
||||
end
|
||||
|
||||
# Delete (terminate) a session
|
||||
#
|
||||
# @param session_id [String] Session ID to delete
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Hash] Response hash with deletion confirmation
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# Un.delete_session(session_id)
|
||||
def delete_session(session_id, public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request('DELETE', "/sessions/#{session_id}", pk, sk)
|
||||
end
|
||||
|
||||
# Freeze a session (pause execution, reduce resource usage)
|
||||
#
|
||||
# @param session_id [String] Session ID to freeze
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Hash] Response hash with freeze confirmation
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# Un.freeze_session(session_id)
|
||||
def freeze_session(session_id, public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request('POST', "/sessions/#{session_id}/freeze", pk, sk, {})
|
||||
end
|
||||
|
||||
# Unfreeze a session (resume execution)
|
||||
#
|
||||
# @param session_id [String] Session ID to unfreeze
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Hash] Response hash with unfreeze confirmation
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# Un.unfreeze_session(session_id)
|
||||
def unfreeze_session(session_id, public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request('POST', "/sessions/#{session_id}/unfreeze", pk, sk, {})
|
||||
end
|
||||
|
||||
# Boost a session (increase vCPU allocation)
|
||||
#
|
||||
# @param session_id [String] Session ID to boost
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Hash] Response hash with boost confirmation
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# Un.boost_session(session_id)
|
||||
def boost_session(session_id, public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request('POST', "/sessions/#{session_id}/boost", pk, sk, {})
|
||||
end
|
||||
|
||||
# Unboost a session (reduce vCPU allocation)
|
||||
#
|
||||
# @param session_id [String] Session ID to unboost
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Hash] Response hash with unboost confirmation
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# Un.unboost_session(session_id)
|
||||
def unboost_session(session_id, public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request('POST', "/sessions/#{session_id}/unboost", pk, sk, {})
|
||||
end
|
||||
|
||||
# Execute a shell command in an existing session
|
||||
# Note: This is for non-interactive command execution, not for WebSocket shell access
|
||||
#
|
||||
# @param session_id [String] Session ID to execute command in
|
||||
# @param command [String] Command to execute
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Hash] Response hash with command output
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# result = Un.shell_session(session_id, "ls -la")
|
||||
# puts result["stdout"]
|
||||
def shell_session(session_id, command, public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request('POST', "/sessions/#{session_id}/shell", pk, sk, { command: command })
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Service Functions
|
||||
# ============================================================================
|
||||
|
||||
# List all services for the authenticated account
|
||||
#
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Array<Hash>] List of service hashes
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# services = Un.list_services
|
||||
# services.each { |s| puts "#{s['id']}: #{s['name']} (#{s['state']})" }
|
||||
def list_services(public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
response = make_request('GET', '/services', pk, sk)
|
||||
response['services'] || []
|
||||
end
|
||||
|
||||
# Create a new persistent service
|
||||
#
|
||||
# @param name [String] Service name
|
||||
# @param ports [Array<Integer>] Ports to expose
|
||||
# @param bootstrap [String] Bootstrap script content or URL
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @param network_mode [String] Network mode ("zerotrust" or "semitrusted")
|
||||
# @param vcpu [Integer] Number of vCPUs (1-8)
|
||||
# @param custom_domains [Array<String>, nil] Custom domains for the service
|
||||
# @param service_type [String, nil] Service type for SRV records (e.g., "minecraft")
|
||||
# @return [Hash] Response hash with service_id
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# result = Un.create_service("web", [80, 443], "apt install -y nginx && nginx")
|
||||
# puts result["service_id"]
|
||||
def create_service(name, ports, bootstrap, public_key: nil, secret_key: nil, network_mode: 'semitrusted', vcpu: 1, custom_domains: nil, service_type: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
data = {
|
||||
name: name,
|
||||
ports: ports,
|
||||
bootstrap: bootstrap,
|
||||
network_mode: network_mode
|
||||
}
|
||||
data[:vcpu] = vcpu if vcpu > 1
|
||||
data[:custom_domains] = custom_domains if custom_domains
|
||||
data[:service_type] = service_type if service_type
|
||||
make_request('POST', '/services', pk, sk, data)
|
||||
end
|
||||
|
||||
# Get service details by ID
|
||||
#
|
||||
# @param service_id [String] Service ID to retrieve
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Hash] Service details hash
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# service = Un.get_service(service_id)
|
||||
# puts service["status"]
|
||||
def get_service(service_id, public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request('GET', "/services/#{service_id}", pk, sk)
|
||||
end
|
||||
|
||||
# Update a service (e.g., resize vCPU)
|
||||
#
|
||||
# @param service_id [String] Service ID to update
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @param vcpu [Integer, nil] New vCPU count (1-8)
|
||||
# @param name [String, nil] New service name
|
||||
# @return [Hash] Response hash with update confirmation
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# Un.update_service(service_id, vcpu: 4)
|
||||
def update_service(service_id, public_key: nil, secret_key: nil, vcpu: nil, name: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
data = {}
|
||||
data[:vcpu] = vcpu if vcpu
|
||||
data[:name] = name if name
|
||||
make_request('PATCH', "/services/#{service_id}", pk, sk, data)
|
||||
end
|
||||
|
||||
# Delete (destroy) a service
|
||||
#
|
||||
# @param service_id [String] Service ID to delete
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Hash] Response hash with deletion confirmation
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# Un.delete_service(service_id)
|
||||
def delete_service(service_id, public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request('DELETE', "/services/#{service_id}", pk, sk)
|
||||
end
|
||||
|
||||
# Freeze a service (stop container, reduce resource usage)
|
||||
#
|
||||
# @param service_id [String] Service ID to freeze
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Hash] Response hash with freeze confirmation
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# Un.freeze_service(service_id)
|
||||
def freeze_service(service_id, public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request('POST', "/services/#{service_id}/freeze", pk, sk, {})
|
||||
end
|
||||
|
||||
# Unfreeze a service (start container)
|
||||
#
|
||||
# @param service_id [String] Service ID to unfreeze
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Hash] Response hash with unfreeze confirmation
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# Un.unfreeze_service(service_id)
|
||||
def unfreeze_service(service_id, public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request('POST', "/services/#{service_id}/unfreeze", pk, sk, {})
|
||||
end
|
||||
|
||||
# Lock a service to prevent deletion
|
||||
#
|
||||
# @param service_id [String] Service ID to lock
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Hash] Response hash with lock confirmation
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# Un.lock_service(service_id)
|
||||
def lock_service(service_id, public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request('POST', "/services/#{service_id}/lock", pk, sk, {})
|
||||
end
|
||||
|
||||
# Unlock a service to allow deletion
|
||||
#
|
||||
# @param service_id [String] Service ID to unlock
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Hash] Response hash with unlock confirmation
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# Un.unlock_service(service_id)
|
||||
def unlock_service(service_id, public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request('POST', "/services/#{service_id}/unlock", pk, sk, {})
|
||||
end
|
||||
|
||||
# Get service logs (bootstrap output)
|
||||
#
|
||||
# @param service_id [String] Service ID to get logs for
|
||||
# @param all [Boolean] If true, get all logs; if false, get last 9000 lines (default: false)
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Hash] Response hash with log content
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# logs = Un.get_service_logs(service_id)
|
||||
# puts logs["log"]
|
||||
def get_service_logs(service_id, all: false, public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
path = "/services/#{service_id}/logs"
|
||||
path += '?all=true' if all
|
||||
make_request('GET', path, pk, sk)
|
||||
end
|
||||
|
||||
# Get service environment vault status
|
||||
#
|
||||
# @param service_id [String] Service ID to get env status for
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Hash] Response hash with vault status (has_vault, count, updated_at)
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# status = Un.get_service_env(service_id)
|
||||
# puts "Variables: #{status['count']}"
|
||||
def get_service_env(service_id, public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request('GET', "/services/#{service_id}/env", pk, sk)
|
||||
end
|
||||
|
||||
# Set service environment variables (replaces existing vault)
|
||||
#
|
||||
# @param service_id [String] Service ID to set env for
|
||||
# @param env [String] Environment content in .env format (KEY=VALUE per line)
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Hash] Response hash with confirmation
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# Un.set_service_env(service_id, "API_KEY=secret\nDEBUG=true")
|
||||
def set_service_env(service_id, env, public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
# This endpoint uses text/plain content type and PUT method
|
||||
make_request_text('PUT', "/services/#{service_id}/env", pk, sk, env)
|
||||
end
|
||||
|
||||
# Delete service environment vault
|
||||
#
|
||||
# @param service_id [String] Service ID to delete env for
|
||||
# @param keys [Array<String>, nil] Specific keys to delete (nil = delete entire vault)
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Hash] Response hash with deletion confirmation
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example Delete entire vault
|
||||
# Un.delete_service_env(service_id)
|
||||
#
|
||||
# @example Delete specific keys (if API supports it)
|
||||
# Un.delete_service_env(service_id, keys: ["API_KEY", "DEBUG"])
|
||||
def delete_service_env(service_id, keys: nil, public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
if keys
|
||||
make_request('DELETE', "/services/#{service_id}/env", pk, sk, { keys: keys })
|
||||
else
|
||||
make_request('DELETE', "/services/#{service_id}/env", pk, sk)
|
||||
end
|
||||
end
|
||||
|
||||
# Export service environment vault (returns .env format)
|
||||
#
|
||||
# @param service_id [String] Service ID to export env from
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Hash] Response hash with env content
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# result = Un.export_service_env(service_id)
|
||||
# puts result["env"] # API_KEY=secret\nDEBUG=true
|
||||
def export_service_env(service_id, public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
make_request('POST', "/services/#{service_id}/env/export", pk, sk, {})
|
||||
end
|
||||
|
||||
# Redeploy a service (re-run bootstrap script)
|
||||
#
|
||||
# @param service_id [String] Service ID to redeploy
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @param bootstrap [String, nil] New bootstrap script (optional)
|
||||
# @return [Hash] Response hash with redeploy confirmation
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# Un.redeploy_service(service_id)
|
||||
def redeploy_service(service_id, public_key: nil, secret_key: nil, bootstrap: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
data = {}
|
||||
data[:bootstrap] = bootstrap if bootstrap
|
||||
make_request('POST', "/services/#{service_id}/redeploy", pk, sk, data)
|
||||
end
|
||||
|
||||
# Execute a command in a running service
|
||||
#
|
||||
# @param service_id [String] Service ID to execute command in
|
||||
# @param command [String] Command to execute
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @param timeout [Integer] Command timeout in milliseconds (default: 30000)
|
||||
# @return [Hash] Response hash with command output (stdout, stderr, exit_code)
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails
|
||||
#
|
||||
# @example
|
||||
# result = Un.execute_in_service(service_id, "ls -la")
|
||||
# puts result["stdout"]
|
||||
def execute_in_service(service_id, command, public_key: nil, secret_key: nil, timeout: 30_000)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
|
||||
# Start async execution
|
||||
response = make_request('POST', "/services/#{service_id}/execute", pk, sk, {
|
||||
command: command,
|
||||
timeout: timeout
|
||||
})
|
||||
|
||||
job_id = response['job_id']
|
||||
return response unless job_id
|
||||
|
||||
# Poll for completion
|
||||
wait_for_job(job_id, public_key: pk, secret_key: sk, timeout: (timeout / 1000) + 10)
|
||||
end
|
||||
|
||||
# ============================================================================
|
||||
# Key Validation
|
||||
# ============================================================================
|
||||
|
||||
# Validate API keys
|
||||
#
|
||||
# @param public_key [String, nil] Optional API key
|
||||
# @param secret_key [String, nil] Optional API secret
|
||||
# @return [Hash] Response hash with validation result and account info
|
||||
# @raise [CredentialsError] If no credentials found
|
||||
# @raise [APIError] If API request fails or keys invalid
|
||||
#
|
||||
# @example
|
||||
# result = Un.validate_keys
|
||||
# puts result["valid"] # true or false
|
||||
def validate_keys(public_key: nil, secret_key: nil)
|
||||
pk, sk = resolve_credentials(public_key, secret_key)
|
||||
# Note: This endpoint is on the portal, not API, but we use same auth
|
||||
make_request('POST', '/keys/validate', pk, sk, {})
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Language detection mapping (file extension -> language)
|
||||
|
|
@ -545,8 +1108,14 @@ module Un
|
|||
http.get(uri.request_uri, headers)
|
||||
when 'POST'
|
||||
http.post(uri.request_uri, body, headers)
|
||||
when 'PATCH'
|
||||
http.patch(uri.request_uri, body, headers)
|
||||
when 'PUT'
|
||||
http.put(uri.request_uri, body, headers)
|
||||
when 'DELETE'
|
||||
http.delete(uri.request_uri, headers)
|
||||
req = Net::HTTP::Delete.new(uri.request_uri, headers)
|
||||
req.body = body if data
|
||||
http.request(req)
|
||||
else
|
||||
raise APIError, "Unsupported HTTP method: #{method}"
|
||||
end
|
||||
|
|
@ -570,6 +1139,59 @@ module Un
|
|||
raise
|
||||
end
|
||||
|
||||
# Make an authenticated HTTP request with text/plain content type
|
||||
#
|
||||
# @param method [String] HTTP method (PUT)
|
||||
# @param path [String] API path
|
||||
# @param public_key [String] API public key
|
||||
# @param secret_key [String] API secret key
|
||||
# @param body [String] Plain text request body
|
||||
# @return [Hash] Parsed JSON response
|
||||
# @raise [APIError] If request fails
|
||||
def make_request_text(method, path, public_key, secret_key, body)
|
||||
uri = URI.parse("#{API_BASE}#{path}")
|
||||
timestamp = Time.now.to_i
|
||||
|
||||
signature = sign_request(secret_key, timestamp, method, path, body)
|
||||
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
http.use_ssl = true
|
||||
http.open_timeout = REQUEST_TIMEOUT
|
||||
http.read_timeout = REQUEST_TIMEOUT
|
||||
|
||||
headers = {
|
||||
'Authorization' => "Bearer #{public_key}",
|
||||
'X-Timestamp' => timestamp.to_s,
|
||||
'X-Signature' => signature,
|
||||
'Content-Type' => 'text/plain'
|
||||
}
|
||||
|
||||
response = case method
|
||||
when 'PUT'
|
||||
http.put(uri.request_uri, body, headers)
|
||||
else
|
||||
raise APIError, "Unsupported HTTP method for text: #{method}"
|
||||
end
|
||||
|
||||
unless response.is_a?(Net::HTTPSuccess)
|
||||
raise APIError.new(
|
||||
"API request failed: #{response.code} #{response.message}",
|
||||
status_code: response.code.to_i,
|
||||
response_body: response.body
|
||||
)
|
||||
end
|
||||
|
||||
JSON.parse(response.body)
|
||||
rescue JSON::ParserError => e
|
||||
raise APIError, "Invalid JSON response: #{e.message}"
|
||||
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
||||
raise APIError, "Request timeout: #{e.message}"
|
||||
rescue StandardError => e
|
||||
raise APIError, "Request failed: #{e.message}" unless e.is_a?(APIError)
|
||||
|
||||
raise
|
||||
end
|
||||
|
||||
# Get path to languages cache file
|
||||
#
|
||||
# @return [String] Path to languages.json
|
||||
|
|
|
|||
|
|
@ -250,6 +250,153 @@ pub struct RestoreResult {
|
|||
pub message: String,
|
||||
}
|
||||
|
||||
/// Session information
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Session {
|
||||
/// Session ID
|
||||
pub session_id: String,
|
||||
/// Container name (e.g., "unsb-vm-abc123")
|
||||
#[serde(default)]
|
||||
pub container_name: String,
|
||||
/// Session status: "running", "frozen", "stopped"
|
||||
#[serde(default)]
|
||||
pub status: String,
|
||||
/// Network mode: "zerotrust" or "semitrusted"
|
||||
#[serde(default)]
|
||||
pub network_mode: String,
|
||||
/// Shell type (e.g., "bash", "python3")
|
||||
#[serde(default)]
|
||||
pub shell: String,
|
||||
/// Number of vCPUs
|
||||
#[serde(default)]
|
||||
pub vcpu: u32,
|
||||
/// Memory in MB
|
||||
#[serde(default)]
|
||||
pub memory_mb: u32,
|
||||
/// Whether the session is boosted
|
||||
#[serde(default)]
|
||||
pub boosted: bool,
|
||||
/// Created timestamp
|
||||
#[serde(default)]
|
||||
pub created_at: String,
|
||||
/// Last activity timestamp
|
||||
#[serde(default)]
|
||||
pub last_activity: String,
|
||||
}
|
||||
|
||||
/// Service information
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Service {
|
||||
/// Service ID
|
||||
pub service_id: String,
|
||||
/// Service name
|
||||
#[serde(default)]
|
||||
pub name: String,
|
||||
/// Container name
|
||||
#[serde(default)]
|
||||
pub container_name: String,
|
||||
/// Service status: "running", "frozen", "stopped", "locked"
|
||||
#[serde(default)]
|
||||
pub status: String,
|
||||
/// Exposed ports
|
||||
#[serde(default)]
|
||||
pub ports: Vec<u16>,
|
||||
/// Custom domains
|
||||
#[serde(default)]
|
||||
pub domains: Vec<String>,
|
||||
/// Network mode
|
||||
#[serde(default)]
|
||||
pub network_mode: String,
|
||||
/// Number of vCPUs
|
||||
#[serde(default)]
|
||||
pub vcpu: u32,
|
||||
/// Memory in MB
|
||||
#[serde(default)]
|
||||
pub memory_mb: u32,
|
||||
/// Whether the service is locked (cannot be modified)
|
||||
#[serde(default)]
|
||||
pub locked: bool,
|
||||
/// Public URL for the service
|
||||
#[serde(default)]
|
||||
pub url: String,
|
||||
/// Created timestamp
|
||||
#[serde(default)]
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
/// Result of shell command execution in a session
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ShellResult {
|
||||
/// Command output (stdout + stderr)
|
||||
#[serde(default)]
|
||||
pub output: String,
|
||||
/// Exit code
|
||||
#[serde(default)]
|
||||
pub exit_code: i32,
|
||||
}
|
||||
|
||||
/// Result of validating API keys
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct KeysValid {
|
||||
/// Whether the keys are valid
|
||||
pub valid: bool,
|
||||
/// Account ID associated with the keys
|
||||
#[serde(default)]
|
||||
pub account_id: String,
|
||||
/// Account email (if available)
|
||||
#[serde(default)]
|
||||
pub email: String,
|
||||
/// Account plan/tier
|
||||
#[serde(default)]
|
||||
pub plan: String,
|
||||
/// Error message if invalid
|
||||
#[serde(default)]
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
/// Options for creating a session
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct SessionCreateOptions {
|
||||
/// Network mode: "zerotrust" (default) or "semitrusted"
|
||||
pub network_mode: Option<String>,
|
||||
/// Shell to use (e.g., "bash", "python3")
|
||||
pub shell: Option<String>,
|
||||
/// Number of vCPUs (default: 1)
|
||||
pub vcpu: Option<u32>,
|
||||
/// Whether to use tmux multiplexer
|
||||
pub tmux: Option<bool>,
|
||||
/// Whether to use screen multiplexer
|
||||
pub screen: Option<bool>,
|
||||
}
|
||||
|
||||
/// Options for creating a service
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ServiceCreateOptions {
|
||||
/// Network mode: "zerotrust" (default) or "semitrusted"
|
||||
pub network_mode: Option<String>,
|
||||
/// Number of vCPUs (default: 1)
|
||||
pub vcpu: Option<u32>,
|
||||
/// Custom domains for the service
|
||||
pub domains: Option<Vec<String>>,
|
||||
/// Bootstrap script content
|
||||
pub bootstrap: Option<String>,
|
||||
/// Bootstrap script URL
|
||||
pub bootstrap_url: Option<String>,
|
||||
}
|
||||
|
||||
/// Options for updating a service
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ServiceUpdateOptions {
|
||||
/// New service name
|
||||
pub name: Option<String>,
|
||||
/// New ports
|
||||
pub ports: Option<Vec<u16>>,
|
||||
/// New domains
|
||||
pub domains: Option<Vec<String>>,
|
||||
/// New vCPU count
|
||||
pub vcpu: Option<u32>,
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Internal Response Types
|
||||
// =============================================================================
|
||||
|
|
@ -304,6 +451,26 @@ struct LanguagesCache {
|
|||
timestamp: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SessionsListResponse {
|
||||
sessions: Vec<Session>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ServicesListResponse {
|
||||
services: Vec<Service>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct EnvResponse {
|
||||
env: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct EnvExportResponse {
|
||||
content: String,
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Language Detection
|
||||
// =============================================================================
|
||||
|
|
@ -632,6 +799,8 @@ async fn make_request<T: for<'de> Deserialize<'de>>(
|
|||
let mut request = match method {
|
||||
"GET" => client.get(&url),
|
||||
"POST" => client.post(&url),
|
||||
"PATCH" => client.patch(&url),
|
||||
"PUT" => client.put(&url),
|
||||
"DELETE" => client.delete(&url),
|
||||
_ => client.get(&url),
|
||||
};
|
||||
|
|
@ -989,6 +1158,672 @@ pub async fn delete_snapshot(snapshot_id: &str, creds: &Credentials) -> Result<(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Lock a snapshot to prevent deletion.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `snapshot_id` - Snapshot ID to lock
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Snapshot information
|
||||
pub async fn lock_snapshot(snapshot_id: &str, creds: &Credentials) -> Result<Snapshot> {
|
||||
let path = format!("/snapshots/{}/lock", snapshot_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body)).await
|
||||
}
|
||||
|
||||
/// Unlock a snapshot to allow deletion.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `snapshot_id` - Snapshot ID to unlock
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Snapshot information
|
||||
pub async fn unlock_snapshot(snapshot_id: &str, creds: &Credentials) -> Result<Snapshot> {
|
||||
let path = format!("/snapshots/{}/unlock", snapshot_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body)).await
|
||||
}
|
||||
|
||||
/// Clone a snapshot to create a new snapshot with a different name.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `snapshot_id` - Snapshot ID to clone
|
||||
/// * `name` - Name for the new snapshot
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// New Snapshot information
|
||||
pub async fn clone_snapshot(snapshot_id: &str, name: &str, creds: &Credentials) -> Result<Snapshot> {
|
||||
let path = format!("/snapshots/{}/clone", snapshot_id);
|
||||
let body = serde_json::json!({
|
||||
"name": name
|
||||
});
|
||||
make_request("POST", &path, creds, Some(&body)).await
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Session API Functions
|
||||
// =============================================================================
|
||||
|
||||
/// List all sessions for the authenticated account.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Vector of Session information
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// let creds = resolve_credentials(None, None)?;
|
||||
/// let sessions = list_sessions(&creds).await?;
|
||||
/// for session in sessions {
|
||||
/// println!("{}: {} ({})", session.session_id, session.container_name, session.status);
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn list_sessions(creds: &Credentials) -> Result<Vec<Session>> {
|
||||
let response: SessionsListResponse = make_request("GET", "/sessions", creds, None::<&()>).await?;
|
||||
Ok(response.sessions)
|
||||
}
|
||||
|
||||
/// Get details of a specific session.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `session_id` - Session ID to retrieve
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Session information
|
||||
pub async fn get_session(session_id: &str, creds: &Credentials) -> Result<Session> {
|
||||
let path = format!("/sessions/{}", session_id);
|
||||
make_request("GET", &path, creds, None::<&()>).await
|
||||
}
|
||||
|
||||
/// Create a new interactive session.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `language` - Programming language/shell (e.g., "bash", "python")
|
||||
/// * `creds` - API credentials
|
||||
/// * `opts` - Optional session creation options
|
||||
///
|
||||
/// # Returns
|
||||
/// Created Session information
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// let creds = resolve_credentials(None, None)?;
|
||||
///
|
||||
/// // Create a basic bash session
|
||||
/// let session = create_session("bash", &creds, None).await?;
|
||||
///
|
||||
/// // Create a session with options
|
||||
/// let opts = SessionCreateOptions {
|
||||
/// network_mode: Some("semitrusted".to_string()),
|
||||
/// tmux: Some(true),
|
||||
/// ..Default::default()
|
||||
/// };
|
||||
/// let session = create_session("bash", &creds, Some(opts)).await?;
|
||||
/// ```
|
||||
pub async fn create_session(
|
||||
language: &str,
|
||||
creds: &Credentials,
|
||||
opts: Option<SessionCreateOptions>,
|
||||
) -> Result<Session> {
|
||||
let mut body = serde_json::json!({
|
||||
"language": language
|
||||
});
|
||||
|
||||
if let Some(opts) = opts {
|
||||
if let Some(network_mode) = opts.network_mode {
|
||||
body["network_mode"] = serde_json::json!(network_mode);
|
||||
}
|
||||
if let Some(shell) = opts.shell {
|
||||
body["shell"] = serde_json::json!(shell);
|
||||
}
|
||||
if let Some(vcpu) = opts.vcpu {
|
||||
body["vcpu"] = serde_json::json!(vcpu);
|
||||
}
|
||||
if let Some(tmux) = opts.tmux {
|
||||
body["tmux"] = serde_json::json!(tmux);
|
||||
}
|
||||
if let Some(screen) = opts.screen {
|
||||
body["screen"] = serde_json::json!(screen);
|
||||
}
|
||||
}
|
||||
|
||||
make_request("POST", "/sessions", creds, Some(&body)).await
|
||||
}
|
||||
|
||||
/// Delete (terminate) a session.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `session_id` - Session ID to delete
|
||||
/// * `creds` - API credentials
|
||||
pub async fn delete_session(session_id: &str, creds: &Credentials) -> Result<()> {
|
||||
let path = format!("/sessions/{}", session_id);
|
||||
let _: serde_json::Value = make_request("DELETE", &path, creds, None::<&()>).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Freeze a session to save resources while preserving state.
|
||||
///
|
||||
/// Frozen sessions can be unfrozen later to resume work.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `session_id` - Session ID to freeze
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Session information
|
||||
pub async fn freeze_session(session_id: &str, creds: &Credentials) -> Result<Session> {
|
||||
let path = format!("/sessions/{}/freeze", session_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body)).await
|
||||
}
|
||||
|
||||
/// Unfreeze a frozen session to resume work.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `session_id` - Session ID to unfreeze
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Session information
|
||||
pub async fn unfreeze_session(session_id: &str, creds: &Credentials) -> Result<Session> {
|
||||
let path = format!("/sessions/{}/unfreeze", session_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body)).await
|
||||
}
|
||||
|
||||
/// Boost a session's resources (increase vCPU and memory).
|
||||
///
|
||||
/// Memory is derived from vCPU: vcpu * 2048MB.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `session_id` - Session ID to boost
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Session information
|
||||
pub async fn boost_session(session_id: &str, creds: &Credentials) -> Result<Session> {
|
||||
let path = format!("/sessions/{}/boost", session_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body)).await
|
||||
}
|
||||
|
||||
/// Remove boost from a session (return to base resources).
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `session_id` - Session ID to unboost
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Session information
|
||||
pub async fn unboost_session(session_id: &str, creds: &Credentials) -> Result<Session> {
|
||||
let path = format!("/sessions/{}/unboost", session_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body)).await
|
||||
}
|
||||
|
||||
/// Execute a shell command in a session.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `session_id` - Session ID to execute command in
|
||||
/// * `command` - Command to execute
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// ShellResult with output and exit code
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// let creds = resolve_credentials(None, None)?;
|
||||
/// let result = shell_session("session-123", "ls -la", &creds).await?;
|
||||
/// println!("Output: {}", result.output);
|
||||
/// println!("Exit code: {}", result.exit_code);
|
||||
/// ```
|
||||
pub async fn shell_session(session_id: &str, command: &str, creds: &Credentials) -> Result<ShellResult> {
|
||||
let path = format!("/sessions/{}/shell", session_id);
|
||||
let body = serde_json::json!({
|
||||
"command": command
|
||||
});
|
||||
make_request("POST", &path, creds, Some(&body)).await
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Service API Functions
|
||||
// =============================================================================
|
||||
|
||||
/// List all services for the authenticated account.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Vector of Service information
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// let creds = resolve_credentials(None, None)?;
|
||||
/// let services = list_services(&creds).await?;
|
||||
/// for service in services {
|
||||
/// println!("{}: {} ({}) - {}", service.service_id, service.name, service.status, service.url);
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn list_services(creds: &Credentials) -> Result<Vec<Service>> {
|
||||
let response: ServicesListResponse = make_request("GET", "/services", creds, None::<&()>).await?;
|
||||
Ok(response.services)
|
||||
}
|
||||
|
||||
/// Create a new persistent service.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `name` - Service name (used in URL: name.on.unsandbox.com)
|
||||
/// * `ports` - Ports to expose
|
||||
/// * `bootstrap` - Bootstrap script content to run on startup
|
||||
/// * `creds` - API credentials
|
||||
/// * `opts` - Optional service creation options
|
||||
///
|
||||
/// # Returns
|
||||
/// Created Service information
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// let creds = resolve_credentials(None, None)?;
|
||||
///
|
||||
/// // Create a simple web service
|
||||
/// let service = create_service(
|
||||
/// "myapp",
|
||||
/// &[8080],
|
||||
/// "python3 -m http.server 8080",
|
||||
/// &creds,
|
||||
/// None
|
||||
/// ).await?;
|
||||
/// println!("Service URL: {}", service.url);
|
||||
///
|
||||
/// // Create with options
|
||||
/// let opts = ServiceCreateOptions {
|
||||
/// network_mode: Some("semitrusted".to_string()),
|
||||
/// vcpu: Some(2),
|
||||
/// domains: Some(vec!["example.com".to_string()]),
|
||||
/// ..Default::default()
|
||||
/// };
|
||||
/// let service = create_service("myapp", &[80, 443], bootstrap, &creds, Some(opts)).await?;
|
||||
/// ```
|
||||
pub async fn create_service(
|
||||
name: &str,
|
||||
ports: &[u16],
|
||||
bootstrap: &str,
|
||||
creds: &Credentials,
|
||||
opts: Option<ServiceCreateOptions>,
|
||||
) -> Result<Service> {
|
||||
let mut body = serde_json::json!({
|
||||
"name": name,
|
||||
"ports": ports,
|
||||
"bootstrap": bootstrap
|
||||
});
|
||||
|
||||
if let Some(opts) = opts {
|
||||
if let Some(network_mode) = opts.network_mode {
|
||||
body["network_mode"] = serde_json::json!(network_mode);
|
||||
}
|
||||
if let Some(vcpu) = opts.vcpu {
|
||||
body["vcpu"] = serde_json::json!(vcpu);
|
||||
}
|
||||
if let Some(domains) = opts.domains {
|
||||
body["domains"] = serde_json::json!(domains);
|
||||
}
|
||||
if let Some(bootstrap_url) = opts.bootstrap_url {
|
||||
body["bootstrap_url"] = serde_json::json!(bootstrap_url);
|
||||
}
|
||||
}
|
||||
|
||||
make_request("POST", "/services", creds, Some(&body)).await
|
||||
}
|
||||
|
||||
/// Get details of a specific service.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to retrieve
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Service information
|
||||
pub async fn get_service(service_id: &str, creds: &Credentials) -> Result<Service> {
|
||||
let path = format!("/services/{}", service_id);
|
||||
make_request("GET", &path, creds, None::<&()>).await
|
||||
}
|
||||
|
||||
/// Update a service's configuration.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to update
|
||||
/// * `creds` - API credentials
|
||||
/// * `opts` - Update options (name, ports, domains, vcpu)
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Service information
|
||||
pub async fn update_service(
|
||||
service_id: &str,
|
||||
creds: &Credentials,
|
||||
opts: ServiceUpdateOptions,
|
||||
) -> Result<Service> {
|
||||
let path = format!("/services/{}", service_id);
|
||||
let mut body = serde_json::Map::new();
|
||||
|
||||
if let Some(name) = opts.name {
|
||||
body.insert("name".to_string(), serde_json::json!(name));
|
||||
}
|
||||
if let Some(ports) = opts.ports {
|
||||
body.insert("ports".to_string(), serde_json::json!(ports));
|
||||
}
|
||||
if let Some(domains) = opts.domains {
|
||||
body.insert("domains".to_string(), serde_json::json!(domains));
|
||||
}
|
||||
if let Some(vcpu) = opts.vcpu {
|
||||
body.insert("vcpu".to_string(), serde_json::json!(vcpu));
|
||||
}
|
||||
|
||||
make_request("PATCH", &path, creds, Some(&serde_json::Value::Object(body))).await
|
||||
}
|
||||
|
||||
/// Delete (destroy) a service.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to delete
|
||||
/// * `creds` - API credentials
|
||||
pub async fn delete_service(service_id: &str, creds: &Credentials) -> Result<()> {
|
||||
let path = format!("/services/{}", service_id);
|
||||
let _: serde_json::Value = make_request("DELETE", &path, creds, None::<&()>).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Freeze a service to save resources while preserving state.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to freeze
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Service information
|
||||
pub async fn freeze_service(service_id: &str, creds: &Credentials) -> Result<Service> {
|
||||
let path = format!("/services/{}/freeze", service_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body)).await
|
||||
}
|
||||
|
||||
/// Unfreeze a frozen service to resume operation.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to unfreeze
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Service information
|
||||
pub async fn unfreeze_service(service_id: &str, creds: &Credentials) -> Result<Service> {
|
||||
let path = format!("/services/{}/unfreeze", service_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body)).await
|
||||
}
|
||||
|
||||
/// Lock a service to prevent modifications.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to lock
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Service information
|
||||
pub async fn lock_service(service_id: &str, creds: &Credentials) -> Result<Service> {
|
||||
let path = format!("/services/{}/lock", service_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body)).await
|
||||
}
|
||||
|
||||
/// Unlock a service to allow modifications.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to unlock
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Service information
|
||||
pub async fn unlock_service(service_id: &str, creds: &Credentials) -> Result<Service> {
|
||||
let path = format!("/services/{}/unlock", service_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body)).await
|
||||
}
|
||||
|
||||
/// Get bootstrap logs for a service.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to get logs for
|
||||
/// * `all` - If true, get all logs; if false, get last 9000 lines
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Log content as string
|
||||
pub async fn get_service_logs(service_id: &str, all: bool, creds: &Credentials) -> Result<String> {
|
||||
let path = if all {
|
||||
format!("/services/{}/logs?all=true", service_id)
|
||||
} else {
|
||||
format!("/services/{}/logs", service_id)
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct LogsResponse {
|
||||
#[serde(default)]
|
||||
logs: String,
|
||||
}
|
||||
|
||||
let response: LogsResponse = make_request("GET", &path, creds, None::<&()>).await?;
|
||||
Ok(response.logs)
|
||||
}
|
||||
|
||||
/// Get environment variables for a service.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to get env for
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// HashMap of environment variable key-value pairs
|
||||
pub async fn get_service_env(service_id: &str, creds: &Credentials) -> Result<HashMap<String, String>> {
|
||||
let path = format!("/services/{}/env", service_id);
|
||||
let response: EnvResponse = make_request("GET", &path, creds, None::<&()>).await?;
|
||||
Ok(response.env)
|
||||
}
|
||||
|
||||
/// Set environment variables for a service.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to set env for
|
||||
/// * `env` - HashMap of environment variable key-value pairs
|
||||
/// * `creds` - API credentials
|
||||
pub async fn set_service_env(
|
||||
service_id: &str,
|
||||
env: &HashMap<String, String>,
|
||||
creds: &Credentials,
|
||||
) -> Result<()> {
|
||||
let path = format!("/services/{}/env", service_id);
|
||||
|
||||
// Convert to .env format
|
||||
let content: String = env
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{}={}", k, v))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
// Use PUT with text/plain content type
|
||||
let client = Client::builder()
|
||||
.timeout(Duration::from_secs(120))
|
||||
.build()?;
|
||||
|
||||
let url = format!("{}{}", API_BASE, path);
|
||||
let timestamp = get_timestamp();
|
||||
let signature = sign_request(&creds.secret_key, timestamp, "PUT", &path, &content);
|
||||
|
||||
let response = client
|
||||
.put(&url)
|
||||
.header("Authorization", format!("Bearer {}", creds.public_key))
|
||||
.header("X-Timestamp", timestamp.to_string())
|
||||
.header("X-Signature", signature)
|
||||
.header("Content-Type", "text/plain")
|
||||
.header("User-Agent", "un-rust-async/2.0")
|
||||
.body(content)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let status = response.status().as_u16();
|
||||
if status < 200 || status >= 300 {
|
||||
let response_text = response.text().await?;
|
||||
return Err(UnsandboxError::ApiError {
|
||||
status,
|
||||
message: response_text,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete environment variables for a service.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to delete env for
|
||||
/// * `keys` - List of environment variable keys to delete
|
||||
/// * `creds` - API credentials
|
||||
pub async fn delete_service_env(
|
||||
service_id: &str,
|
||||
keys: &[&str],
|
||||
creds: &Credentials,
|
||||
) -> Result<()> {
|
||||
let path = format!("/services/{}/env", service_id);
|
||||
let body = serde_json::json!({
|
||||
"keys": keys
|
||||
});
|
||||
let _: serde_json::Value = make_request("DELETE", &path, creds, Some(&body)).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Export environment variables for a service in .env format.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to export env for
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Environment variables in .env format string
|
||||
pub async fn export_service_env(service_id: &str, creds: &Credentials) -> Result<String> {
|
||||
let path = format!("/services/{}/env/export", service_id);
|
||||
let body = serde_json::json!({});
|
||||
let response: EnvExportResponse = make_request("POST", &path, creds, Some(&body)).await?;
|
||||
Ok(response.content)
|
||||
}
|
||||
|
||||
/// Redeploy a service with a new bootstrap script.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to redeploy
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Service information
|
||||
pub async fn redeploy_service(service_id: &str, creds: &Credentials) -> Result<Service> {
|
||||
let path = format!("/services/{}/redeploy", service_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body)).await
|
||||
}
|
||||
|
||||
/// Execute a command in a service container.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to execute command in
|
||||
/// * `command` - Command to execute
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// ExecuteResult with output and exit code
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// let creds = resolve_credentials(None, None)?;
|
||||
/// let result = execute_in_service("service-123", "ls -la /app", &creds).await?;
|
||||
/// println!("Output: {}", result.output);
|
||||
/// ```
|
||||
pub async fn execute_in_service(
|
||||
service_id: &str,
|
||||
command: &str,
|
||||
creds: &Credentials,
|
||||
) -> Result<ExecuteResult> {
|
||||
let path = format!("/services/{}/execute", service_id);
|
||||
let body = serde_json::json!({
|
||||
"command": command,
|
||||
"timeout": 30000
|
||||
});
|
||||
make_request("POST", &path, creds, Some(&body)).await
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Key Validation API Functions
|
||||
// =============================================================================
|
||||
|
||||
/// Validate API keys.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `creds` - API credentials to validate
|
||||
///
|
||||
/// # Returns
|
||||
/// KeysValid with validation result and account info
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// let creds = resolve_credentials(None, None)?;
|
||||
/// let result = validate_keys(&creds).await?;
|
||||
/// if result.valid {
|
||||
/// println!("Keys valid for account: {}", result.account_id);
|
||||
/// } else {
|
||||
/// println!("Invalid keys: {}", result.error);
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn validate_keys(creds: &Credentials) -> Result<KeysValid> {
|
||||
// Note: This endpoint is on the portal, not the API
|
||||
let client = Client::builder()
|
||||
.timeout(Duration::from_secs(30))
|
||||
.build()?;
|
||||
|
||||
let url = "https://unsandbox.com/keys/validate";
|
||||
let path = "/keys/validate";
|
||||
let timestamp = get_timestamp();
|
||||
let body_str = "";
|
||||
let signature = sign_request(&creds.secret_key, timestamp, "POST", path, body_str);
|
||||
|
||||
let response = client
|
||||
.post(url)
|
||||
.header("Authorization", format!("Bearer {}", creds.public_key))
|
||||
.header("X-Timestamp", timestamp.to_string())
|
||||
.header("X-Signature", signature)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("User-Agent", "un-rust-async/2.0")
|
||||
.body("")
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let status = response.status().as_u16();
|
||||
let response_text = response.text().await?;
|
||||
|
||||
if status < 200 || status >= 300 {
|
||||
return Err(UnsandboxError::ApiError {
|
||||
status,
|
||||
message: response_text,
|
||||
});
|
||||
}
|
||||
|
||||
let result: KeysValid = serde_json::from_str(&response_text)?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Tests
|
||||
// =============================================================================
|
||||
|
|
|
|||
|
|
@ -245,6 +245,153 @@ pub struct RestoreResult {
|
|||
pub message: String,
|
||||
}
|
||||
|
||||
/// Session information
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Session {
|
||||
/// Session ID
|
||||
pub session_id: String,
|
||||
/// Container name (e.g., "unsb-vm-abc123")
|
||||
#[serde(default)]
|
||||
pub container_name: String,
|
||||
/// Session status: "running", "frozen", "stopped"
|
||||
#[serde(default)]
|
||||
pub status: String,
|
||||
/// Network mode: "zerotrust" or "semitrusted"
|
||||
#[serde(default)]
|
||||
pub network_mode: String,
|
||||
/// Shell type (e.g., "bash", "python3")
|
||||
#[serde(default)]
|
||||
pub shell: String,
|
||||
/// Number of vCPUs
|
||||
#[serde(default)]
|
||||
pub vcpu: u32,
|
||||
/// Memory in MB
|
||||
#[serde(default)]
|
||||
pub memory_mb: u32,
|
||||
/// Whether the session is boosted
|
||||
#[serde(default)]
|
||||
pub boosted: bool,
|
||||
/// Created timestamp
|
||||
#[serde(default)]
|
||||
pub created_at: String,
|
||||
/// Last activity timestamp
|
||||
#[serde(default)]
|
||||
pub last_activity: String,
|
||||
}
|
||||
|
||||
/// Service information
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Service {
|
||||
/// Service ID
|
||||
pub service_id: String,
|
||||
/// Service name
|
||||
#[serde(default)]
|
||||
pub name: String,
|
||||
/// Container name
|
||||
#[serde(default)]
|
||||
pub container_name: String,
|
||||
/// Service status: "running", "frozen", "stopped", "locked"
|
||||
#[serde(default)]
|
||||
pub status: String,
|
||||
/// Exposed ports
|
||||
#[serde(default)]
|
||||
pub ports: Vec<u16>,
|
||||
/// Custom domains
|
||||
#[serde(default)]
|
||||
pub domains: Vec<String>,
|
||||
/// Network mode
|
||||
#[serde(default)]
|
||||
pub network_mode: String,
|
||||
/// Number of vCPUs
|
||||
#[serde(default)]
|
||||
pub vcpu: u32,
|
||||
/// Memory in MB
|
||||
#[serde(default)]
|
||||
pub memory_mb: u32,
|
||||
/// Whether the service is locked (cannot be modified)
|
||||
#[serde(default)]
|
||||
pub locked: bool,
|
||||
/// Public URL for the service
|
||||
#[serde(default)]
|
||||
pub url: String,
|
||||
/// Created timestamp
|
||||
#[serde(default)]
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
/// Result of shell command execution in a session
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ShellResult {
|
||||
/// Command output (stdout + stderr)
|
||||
#[serde(default)]
|
||||
pub output: String,
|
||||
/// Exit code
|
||||
#[serde(default)]
|
||||
pub exit_code: i32,
|
||||
}
|
||||
|
||||
/// Result of validating API keys
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct KeysValid {
|
||||
/// Whether the keys are valid
|
||||
pub valid: bool,
|
||||
/// Account ID associated with the keys
|
||||
#[serde(default)]
|
||||
pub account_id: String,
|
||||
/// Account email (if available)
|
||||
#[serde(default)]
|
||||
pub email: String,
|
||||
/// Account plan/tier
|
||||
#[serde(default)]
|
||||
pub plan: String,
|
||||
/// Error message if invalid
|
||||
#[serde(default)]
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
/// Options for creating a session
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct SessionCreateOptions {
|
||||
/// Network mode: "zerotrust" (default) or "semitrusted"
|
||||
pub network_mode: Option<String>,
|
||||
/// Shell to use (e.g., "bash", "python3")
|
||||
pub shell: Option<String>,
|
||||
/// Number of vCPUs (default: 1)
|
||||
pub vcpu: Option<u32>,
|
||||
/// Whether to use tmux multiplexer
|
||||
pub tmux: Option<bool>,
|
||||
/// Whether to use screen multiplexer
|
||||
pub screen: Option<bool>,
|
||||
}
|
||||
|
||||
/// Options for creating a service
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ServiceCreateOptions {
|
||||
/// Network mode: "zerotrust" (default) or "semitrusted"
|
||||
pub network_mode: Option<String>,
|
||||
/// Number of vCPUs (default: 1)
|
||||
pub vcpu: Option<u32>,
|
||||
/// Custom domains for the service
|
||||
pub domains: Option<Vec<String>>,
|
||||
/// Bootstrap script content
|
||||
pub bootstrap: Option<String>,
|
||||
/// Bootstrap script URL
|
||||
pub bootstrap_url: Option<String>,
|
||||
}
|
||||
|
||||
/// Options for updating a service
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ServiceUpdateOptions {
|
||||
/// New service name
|
||||
pub name: Option<String>,
|
||||
/// New ports
|
||||
pub ports: Option<Vec<u16>>,
|
||||
/// New domains
|
||||
pub domains: Option<Vec<String>>,
|
||||
/// New vCPU count
|
||||
pub vcpu: Option<u32>,
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Internal Response Types
|
||||
// =============================================================================
|
||||
|
|
@ -299,6 +446,26 @@ struct LanguagesCache {
|
|||
timestamp: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SessionsListResponse {
|
||||
sessions: Vec<Session>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ServicesListResponse {
|
||||
services: Vec<Service>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct EnvResponse {
|
||||
env: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct EnvExportResponse {
|
||||
content: String,
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Language Detection
|
||||
// =============================================================================
|
||||
|
|
@ -549,6 +716,8 @@ fn make_request<T: for<'de> Deserialize<'de>>(
|
|||
let mut request = match method {
|
||||
"GET" => client.get(&url),
|
||||
"POST" => client.post(&url),
|
||||
"PATCH" => client.patch(&url),
|
||||
"PUT" => client.put(&url),
|
||||
"DELETE" => client.delete(&url),
|
||||
_ => client.get(&url),
|
||||
};
|
||||
|
|
@ -906,6 +1075,670 @@ pub fn delete_snapshot(snapshot_id: &str, creds: &Credentials) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Lock a snapshot to prevent deletion.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `snapshot_id` - Snapshot ID to lock
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Snapshot information
|
||||
pub fn lock_snapshot(snapshot_id: &str, creds: &Credentials) -> Result<Snapshot> {
|
||||
let path = format!("/snapshots/{}/lock", snapshot_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body))
|
||||
}
|
||||
|
||||
/// Unlock a snapshot to allow deletion.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `snapshot_id` - Snapshot ID to unlock
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Snapshot information
|
||||
pub fn unlock_snapshot(snapshot_id: &str, creds: &Credentials) -> Result<Snapshot> {
|
||||
let path = format!("/snapshots/{}/unlock", snapshot_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body))
|
||||
}
|
||||
|
||||
/// Clone a snapshot to create a new snapshot with a different name.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `snapshot_id` - Snapshot ID to clone
|
||||
/// * `name` - Name for the new snapshot
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// New Snapshot information
|
||||
pub fn clone_snapshot(snapshot_id: &str, name: &str, creds: &Credentials) -> Result<Snapshot> {
|
||||
let path = format!("/snapshots/{}/clone", snapshot_id);
|
||||
let body = serde_json::json!({
|
||||
"name": name
|
||||
});
|
||||
make_request("POST", &path, creds, Some(&body))
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Session API Functions
|
||||
// =============================================================================
|
||||
|
||||
/// List all sessions for the authenticated account.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Vector of Session information
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// let creds = resolve_credentials(None, None)?;
|
||||
/// let sessions = list_sessions(&creds)?;
|
||||
/// for session in sessions {
|
||||
/// println!("{}: {} ({})", session.session_id, session.container_name, session.status);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn list_sessions(creds: &Credentials) -> Result<Vec<Session>> {
|
||||
let response: SessionsListResponse = make_request("GET", "/sessions", creds, None::<&()>)?;
|
||||
Ok(response.sessions)
|
||||
}
|
||||
|
||||
/// Get details of a specific session.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `session_id` - Session ID to retrieve
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Session information
|
||||
pub fn get_session(session_id: &str, creds: &Credentials) -> Result<Session> {
|
||||
let path = format!("/sessions/{}", session_id);
|
||||
make_request("GET", &path, creds, None::<&()>)
|
||||
}
|
||||
|
||||
/// Create a new interactive session.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `language` - Programming language/shell (e.g., "bash", "python")
|
||||
/// * `creds` - API credentials
|
||||
/// * `opts` - Optional session creation options
|
||||
///
|
||||
/// # Returns
|
||||
/// Created Session information
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// let creds = resolve_credentials(None, None)?;
|
||||
///
|
||||
/// // Create a basic bash session
|
||||
/// let session = create_session("bash", &creds, None)?;
|
||||
///
|
||||
/// // Create a session with options
|
||||
/// let opts = SessionCreateOptions {
|
||||
/// network_mode: Some("semitrusted".to_string()),
|
||||
/// tmux: Some(true),
|
||||
/// ..Default::default()
|
||||
/// };
|
||||
/// let session = create_session("bash", &creds, Some(opts))?;
|
||||
/// ```
|
||||
pub fn create_session(
|
||||
language: &str,
|
||||
creds: &Credentials,
|
||||
opts: Option<SessionCreateOptions>,
|
||||
) -> Result<Session> {
|
||||
let mut body = serde_json::json!({
|
||||
"language": language
|
||||
});
|
||||
|
||||
if let Some(opts) = opts {
|
||||
if let Some(network_mode) = opts.network_mode {
|
||||
body["network_mode"] = serde_json::json!(network_mode);
|
||||
}
|
||||
if let Some(shell) = opts.shell {
|
||||
body["shell"] = serde_json::json!(shell);
|
||||
}
|
||||
if let Some(vcpu) = opts.vcpu {
|
||||
body["vcpu"] = serde_json::json!(vcpu);
|
||||
}
|
||||
if let Some(tmux) = opts.tmux {
|
||||
body["tmux"] = serde_json::json!(tmux);
|
||||
}
|
||||
if let Some(screen) = opts.screen {
|
||||
body["screen"] = serde_json::json!(screen);
|
||||
}
|
||||
}
|
||||
|
||||
make_request("POST", "/sessions", creds, Some(&body))
|
||||
}
|
||||
|
||||
/// Delete (terminate) a session.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `session_id` - Session ID to delete
|
||||
/// * `creds` - API credentials
|
||||
pub fn delete_session(session_id: &str, creds: &Credentials) -> Result<()> {
|
||||
let path = format!("/sessions/{}", session_id);
|
||||
let _: serde_json::Value = make_request("DELETE", &path, creds, None::<&()>)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Freeze a session to save resources while preserving state.
|
||||
///
|
||||
/// Frozen sessions can be unfrozen later to resume work.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `session_id` - Session ID to freeze
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Session information
|
||||
pub fn freeze_session(session_id: &str, creds: &Credentials) -> Result<Session> {
|
||||
let path = format!("/sessions/{}/freeze", session_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body))
|
||||
}
|
||||
|
||||
/// Unfreeze a frozen session to resume work.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `session_id` - Session ID to unfreeze
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Session information
|
||||
pub fn unfreeze_session(session_id: &str, creds: &Credentials) -> Result<Session> {
|
||||
let path = format!("/sessions/{}/unfreeze", session_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body))
|
||||
}
|
||||
|
||||
/// Boost a session's resources (increase vCPU and memory).
|
||||
///
|
||||
/// Memory is derived from vCPU: vcpu * 2048MB.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `session_id` - Session ID to boost
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Session information
|
||||
pub fn boost_session(session_id: &str, creds: &Credentials) -> Result<Session> {
|
||||
let path = format!("/sessions/{}/boost", session_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body))
|
||||
}
|
||||
|
||||
/// Remove boost from a session (return to base resources).
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `session_id` - Session ID to unboost
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Session information
|
||||
pub fn unboost_session(session_id: &str, creds: &Credentials) -> Result<Session> {
|
||||
let path = format!("/sessions/{}/unboost", session_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body))
|
||||
}
|
||||
|
||||
/// Execute a shell command in a session.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `session_id` - Session ID to execute command in
|
||||
/// * `command` - Command to execute
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// ShellResult with output and exit code
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// let creds = resolve_credentials(None, None)?;
|
||||
/// let result = shell_session("session-123", "ls -la", &creds)?;
|
||||
/// println!("Output: {}", result.output);
|
||||
/// println!("Exit code: {}", result.exit_code);
|
||||
/// ```
|
||||
pub fn shell_session(session_id: &str, command: &str, creds: &Credentials) -> Result<ShellResult> {
|
||||
let path = format!("/sessions/{}/shell", session_id);
|
||||
let body = serde_json::json!({
|
||||
"command": command
|
||||
});
|
||||
make_request("POST", &path, creds, Some(&body))
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Service API Functions
|
||||
// =============================================================================
|
||||
|
||||
/// List all services for the authenticated account.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Vector of Service information
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// let creds = resolve_credentials(None, None)?;
|
||||
/// let services = list_services(&creds)?;
|
||||
/// for service in services {
|
||||
/// println!("{}: {} ({}) - {}", service.service_id, service.name, service.status, service.url);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn list_services(creds: &Credentials) -> Result<Vec<Service>> {
|
||||
let response: ServicesListResponse = make_request("GET", "/services", creds, None::<&()>)?;
|
||||
Ok(response.services)
|
||||
}
|
||||
|
||||
/// Create a new persistent service.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `name` - Service name (used in URL: name.on.unsandbox.com)
|
||||
/// * `ports` - Ports to expose
|
||||
/// * `bootstrap` - Bootstrap script content to run on startup
|
||||
/// * `creds` - API credentials
|
||||
/// * `opts` - Optional service creation options
|
||||
///
|
||||
/// # Returns
|
||||
/// Created Service information
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// let creds = resolve_credentials(None, None)?;
|
||||
///
|
||||
/// // Create a simple web service
|
||||
/// let service = create_service(
|
||||
/// "myapp",
|
||||
/// &[8080],
|
||||
/// "python3 -m http.server 8080",
|
||||
/// &creds,
|
||||
/// None
|
||||
/// )?;
|
||||
/// println!("Service URL: {}", service.url);
|
||||
///
|
||||
/// // Create with options
|
||||
/// let opts = ServiceCreateOptions {
|
||||
/// network_mode: Some("semitrusted".to_string()),
|
||||
/// vcpu: Some(2),
|
||||
/// domains: Some(vec!["example.com".to_string()]),
|
||||
/// ..Default::default()
|
||||
/// };
|
||||
/// let service = create_service("myapp", &[80, 443], bootstrap, &creds, Some(opts))?;
|
||||
/// ```
|
||||
pub fn create_service(
|
||||
name: &str,
|
||||
ports: &[u16],
|
||||
bootstrap: &str,
|
||||
creds: &Credentials,
|
||||
opts: Option<ServiceCreateOptions>,
|
||||
) -> Result<Service> {
|
||||
let mut body = serde_json::json!({
|
||||
"name": name,
|
||||
"ports": ports,
|
||||
"bootstrap": bootstrap
|
||||
});
|
||||
|
||||
if let Some(opts) = opts {
|
||||
if let Some(network_mode) = opts.network_mode {
|
||||
body["network_mode"] = serde_json::json!(network_mode);
|
||||
}
|
||||
if let Some(vcpu) = opts.vcpu {
|
||||
body["vcpu"] = serde_json::json!(vcpu);
|
||||
}
|
||||
if let Some(domains) = opts.domains {
|
||||
body["domains"] = serde_json::json!(domains);
|
||||
}
|
||||
if let Some(bootstrap_url) = opts.bootstrap_url {
|
||||
body["bootstrap_url"] = serde_json::json!(bootstrap_url);
|
||||
}
|
||||
}
|
||||
|
||||
make_request("POST", "/services", creds, Some(&body))
|
||||
}
|
||||
|
||||
/// Get details of a specific service.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to retrieve
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Service information
|
||||
pub fn get_service(service_id: &str, creds: &Credentials) -> Result<Service> {
|
||||
let path = format!("/services/{}", service_id);
|
||||
make_request("GET", &path, creds, None::<&()>)
|
||||
}
|
||||
|
||||
/// Update a service's configuration.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to update
|
||||
/// * `creds` - API credentials
|
||||
/// * `opts` - Update options (name, ports, domains, vcpu)
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Service information
|
||||
pub fn update_service(
|
||||
service_id: &str,
|
||||
creds: &Credentials,
|
||||
opts: ServiceUpdateOptions,
|
||||
) -> Result<Service> {
|
||||
let path = format!("/services/{}", service_id);
|
||||
let mut body = serde_json::Map::new();
|
||||
|
||||
if let Some(name) = opts.name {
|
||||
body.insert("name".to_string(), serde_json::json!(name));
|
||||
}
|
||||
if let Some(ports) = opts.ports {
|
||||
body.insert("ports".to_string(), serde_json::json!(ports));
|
||||
}
|
||||
if let Some(domains) = opts.domains {
|
||||
body.insert("domains".to_string(), serde_json::json!(domains));
|
||||
}
|
||||
if let Some(vcpu) = opts.vcpu {
|
||||
body.insert("vcpu".to_string(), serde_json::json!(vcpu));
|
||||
}
|
||||
|
||||
make_request("PATCH", &path, creds, Some(&serde_json::Value::Object(body)))
|
||||
}
|
||||
|
||||
/// Delete (destroy) a service.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to delete
|
||||
/// * `creds` - API credentials
|
||||
pub fn delete_service(service_id: &str, creds: &Credentials) -> Result<()> {
|
||||
let path = format!("/services/{}", service_id);
|
||||
let _: serde_json::Value = make_request("DELETE", &path, creds, None::<&()>)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Freeze a service to save resources while preserving state.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to freeze
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Service information
|
||||
pub fn freeze_service(service_id: &str, creds: &Credentials) -> Result<Service> {
|
||||
let path = format!("/services/{}/freeze", service_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body))
|
||||
}
|
||||
|
||||
/// Unfreeze a frozen service to resume operation.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to unfreeze
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Service information
|
||||
pub fn unfreeze_service(service_id: &str, creds: &Credentials) -> Result<Service> {
|
||||
let path = format!("/services/{}/unfreeze", service_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body))
|
||||
}
|
||||
|
||||
/// Lock a service to prevent modifications.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to lock
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Service information
|
||||
pub fn lock_service(service_id: &str, creds: &Credentials) -> Result<Service> {
|
||||
let path = format!("/services/{}/lock", service_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body))
|
||||
}
|
||||
|
||||
/// Unlock a service to allow modifications.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to unlock
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Service information
|
||||
pub fn unlock_service(service_id: &str, creds: &Credentials) -> Result<Service> {
|
||||
let path = format!("/services/{}/unlock", service_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body))
|
||||
}
|
||||
|
||||
/// Get bootstrap logs for a service.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to get logs for
|
||||
/// * `all` - If true, get all logs; if false, get last 9000 lines
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Log content as string
|
||||
pub fn get_service_logs(service_id: &str, all: bool, creds: &Credentials) -> Result<String> {
|
||||
let path = if all {
|
||||
format!("/services/{}/logs?all=true", service_id)
|
||||
} else {
|
||||
format!("/services/{}/logs", service_id)
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct LogsResponse {
|
||||
#[serde(default)]
|
||||
logs: String,
|
||||
}
|
||||
|
||||
let response: LogsResponse = make_request("GET", &path, creds, None::<&()>)?;
|
||||
Ok(response.logs)
|
||||
}
|
||||
|
||||
/// Get environment variables for a service.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to get env for
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// HashMap of environment variable key-value pairs
|
||||
pub fn get_service_env(service_id: &str, creds: &Credentials) -> Result<HashMap<String, String>> {
|
||||
let path = format!("/services/{}/env", service_id);
|
||||
let response: EnvResponse = make_request("GET", &path, creds, None::<&()>)?;
|
||||
Ok(response.env)
|
||||
}
|
||||
|
||||
/// Set environment variables for a service.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to set env for
|
||||
/// * `env` - HashMap of environment variable key-value pairs
|
||||
/// * `creds` - API credentials
|
||||
pub fn set_service_env(
|
||||
service_id: &str,
|
||||
env: &HashMap<String, String>,
|
||||
creds: &Credentials,
|
||||
) -> Result<()> {
|
||||
let path = format!("/services/{}/env", service_id);
|
||||
|
||||
// Convert to .env format
|
||||
let content: String = env
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{}={}", k, v))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
|
||||
// Use PUT with text/plain content type
|
||||
let client = reqwest::blocking::Client::builder()
|
||||
.timeout(Duration::from_secs(120))
|
||||
.build()?;
|
||||
|
||||
let url = format!("{}{}", API_BASE, path);
|
||||
let timestamp = get_timestamp();
|
||||
let signature = sign_request(&creds.secret_key, timestamp, "PUT", &path, &content);
|
||||
|
||||
let response = client
|
||||
.put(&url)
|
||||
.header("Authorization", format!("Bearer {}", creds.public_key))
|
||||
.header("X-Timestamp", timestamp.to_string())
|
||||
.header("X-Signature", signature)
|
||||
.header("Content-Type", "text/plain")
|
||||
.header("User-Agent", "un-rust-sync/2.0")
|
||||
.body(content)
|
||||
.send()?;
|
||||
|
||||
let status = response.status().as_u16();
|
||||
if status < 200 || status >= 300 {
|
||||
let response_text = response.text()?;
|
||||
return Err(UnsandboxError::ApiError {
|
||||
status,
|
||||
message: response_text,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete environment variables for a service.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to delete env for
|
||||
/// * `keys` - List of environment variable keys to delete
|
||||
/// * `creds` - API credentials
|
||||
pub fn delete_service_env(
|
||||
service_id: &str,
|
||||
keys: &[&str],
|
||||
creds: &Credentials,
|
||||
) -> Result<()> {
|
||||
let path = format!("/services/{}/env", service_id);
|
||||
let body = serde_json::json!({
|
||||
"keys": keys
|
||||
});
|
||||
let _: serde_json::Value = make_request("DELETE", &path, creds, Some(&body))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Export environment variables for a service in .env format.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to export env for
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Environment variables in .env format string
|
||||
pub fn export_service_env(service_id: &str, creds: &Credentials) -> Result<String> {
|
||||
let path = format!("/services/{}/env/export", service_id);
|
||||
let body = serde_json::json!({});
|
||||
let response: EnvExportResponse = make_request("POST", &path, creds, Some(&body))?;
|
||||
Ok(response.content)
|
||||
}
|
||||
|
||||
/// Redeploy a service with a new bootstrap script.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to redeploy
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// Updated Service information
|
||||
pub fn redeploy_service(service_id: &str, creds: &Credentials) -> Result<Service> {
|
||||
let path = format!("/services/{}/redeploy", service_id);
|
||||
let body = serde_json::json!({});
|
||||
make_request("POST", &path, creds, Some(&body))
|
||||
}
|
||||
|
||||
/// Execute a command in a service container.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `service_id` - Service ID to execute command in
|
||||
/// * `command` - Command to execute
|
||||
/// * `creds` - API credentials
|
||||
///
|
||||
/// # Returns
|
||||
/// ExecuteResult with output and exit code
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// let creds = resolve_credentials(None, None)?;
|
||||
/// let result = execute_in_service("service-123", "ls -la /app", &creds)?;
|
||||
/// println!("Output: {}", result.output);
|
||||
/// ```
|
||||
pub fn execute_in_service(
|
||||
service_id: &str,
|
||||
command: &str,
|
||||
creds: &Credentials,
|
||||
) -> Result<ExecuteResult> {
|
||||
let path = format!("/services/{}/execute", service_id);
|
||||
let body = serde_json::json!({
|
||||
"command": command,
|
||||
"timeout": 30000
|
||||
});
|
||||
make_request("POST", &path, creds, Some(&body))
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Key Validation API Functions
|
||||
// =============================================================================
|
||||
|
||||
/// Validate API keys.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `creds` - API credentials to validate
|
||||
///
|
||||
/// # Returns
|
||||
/// KeysValid with validation result and account info
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// let creds = resolve_credentials(None, None)?;
|
||||
/// let result = validate_keys(&creds)?;
|
||||
/// if result.valid {
|
||||
/// println!("Keys valid for account: {}", result.account_id);
|
||||
/// } else {
|
||||
/// println!("Invalid keys: {}", result.error);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn validate_keys(creds: &Credentials) -> Result<KeysValid> {
|
||||
// Note: This endpoint is on the portal, not the API
|
||||
let client = reqwest::blocking::Client::builder()
|
||||
.timeout(Duration::from_secs(30))
|
||||
.build()?;
|
||||
|
||||
let url = "https://unsandbox.com/keys/validate";
|
||||
let path = "/keys/validate";
|
||||
let timestamp = get_timestamp();
|
||||
let body_str = "";
|
||||
let signature = sign_request(&creds.secret_key, timestamp, "POST", path, body_str);
|
||||
|
||||
let response = client
|
||||
.post(url)
|
||||
.header("Authorization", format!("Bearer {}", creds.public_key))
|
||||
.header("X-Timestamp", timestamp.to_string())
|
||||
.header("X-Signature", signature)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("User-Agent", "un-rust-sync/2.0")
|
||||
.body("")
|
||||
.send()?;
|
||||
|
||||
let status = response.status().as_u16();
|
||||
let response_text = response.text()?;
|
||||
|
||||
if status < 200 || status >= 300 {
|
||||
return Err(UnsandboxError::ApiError {
|
||||
status,
|
||||
message: response_text,
|
||||
});
|
||||
}
|
||||
|
||||
let result: KeysValid = serde_json::from_str(&response_text)?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Tests
|
||||
// =============================================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue