CLI
Cliente de linha de comando rápido para execução de código e sessões interativas. Mais de 42 linguagens, mais de 30 shells/REPLs.
Documentação Oficial OpenAPI Swagger ↗Início Rápido — Julia
# Download + setup
curl -O https://git.unturf.com/engineering/unturf/un-inception/-/raw/main/clients/julia/sync/src/un.jl && chmod +x un.jl && ln -sf un.jl un
export UNSANDBOX_PUBLIC_KEY="unsb-pk-xxxx-xxxx-xxxx-xxxx"
export UNSANDBOX_SECRET_KEY="unsb-sk-xxxxx-xxxxx-xxxxx-xxxxx"
# Run code
./un script.julia
Baixar
Guia de Instalação →Características:
- 42+ languages - Python, JS, Go, Rust, C++, Java...
- Sessions - 30+ shells/REPLs, tmux persistence
- Files - Upload files, collect artifacts
- Services - Persistent containers with domains
- Snapshots - Point-in-time backups
- Images - Publish, share, transfer
Início Rápido de Integração ⚡
Adicione superpoderes unsandbox ao seu app Julia existente:
curl -O https://git.unturf.com/engineering/unturf/un-inception/-/raw/main/clients/julia/sync/src/un.jl
# Option A: Environment variables
export UNSANDBOX_PUBLIC_KEY="unsb-pk-xxxx-xxxx-xxxx-xxxx"
export UNSANDBOX_SECRET_KEY="unsb-sk-xxxxx-xxxxx-xxxxx-xxxxx"
# Option B: Config file (persistent)
mkdir -p ~/.unsandbox
echo "unsb-pk-xxxx-xxxx-xxxx-xxxx,unsb-sk-xxxxx-xxxxx-xxxxx-xxxxx" > ~/.unsandbox/accounts.csv
# In your Julia app:
include("un.jl")
result = execute_code("julia", "println(\"Hello from Julia running on unsandbox!\")")
println(result["stdout"]) # Hello from Julia running on unsandbox!
julia myapp.jl
29712cc76971f59208d41a07f26a7f66
SHA256: 17cc035a3c49a9d44d650fc4f6e26e8b20577ed19ee2b55cf9a6fdaed7d0a2a2
#!/usr/bin/env julia
# PUBLIC DOMAIN - NO LICENSE, NO WARRANTY
#
# This is free public domain software for the public good of a permacomputer hosted
# at permacomputer.com - an always-on computer by the people, for the people. One
# which is durable, easy to repair, and distributed like tap water for machine
# learning intelligence.
#
# The permacomputer is community-owned infrastructure optimized around four values:
#
# TRUTH - First principles, math & science, open source code freely distributed
# FREEDOM - Voluntary partnerships, freedom from tyranny & corporate control
# HARMONY - Minimal waste, self-renewing systems with diverse thriving connections
# LOVE - Be yourself without hurting others, cooperation through natural law
#
# This software contributes to that vision by enabling code execution across 42+
# programming languages through a unified interface, accessible to all. Code is
# seeds to sprout on any abandoned technology.
#
# Learn more: https://www.permacomputer.com
#
# Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
# software, either in source code form or as a compiled binary, for any purpose,
# commercial or non-commercial, and by any means.
#
# NO WARRANTY. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
#
# That said, our permacomputer's digital membrane stratum continuously runs unit,
# integration, and functional tests on all of it's own software - with our
# permacomputer monitoring itself, repairing itself, with minimal human in the
# loop guidance. Our agents do their best.
#
# Copyright 2025 TimeHexOn & foxhop & russell@unturf
# https://www.timehexon.com
# https://www.foxhop.net
# https://www.unturf.com/software
#!/usr/bin/env julia
using HTTP
using JSON
using Base64
using ArgParse
using Printf
using SHA
# Extension to language mapping
const EXT_MAP = Dict(
".jl" => "julia", ".r" => "r", ".cr" => "crystal",
".f90" => "fortran", ".cob" => "cobol", ".pro" => "prolog",
".forth" => "forth", ".4th" => "forth", ".py" => "python",
".js" => "javascript", ".ts" => "typescript", ".rb" => "ruby",
".php" => "php", ".pl" => "perl", ".lua" => "lua", ".sh" => "bash",
".go" => "go", ".rs" => "rust", ".c" => "c", ".cpp" => "cpp",
".cc" => "cpp", ".cxx" => "cpp", ".java" => "java", ".kt" => "kotlin",
".cs" => "csharp", ".fs" => "fsharp", ".hs" => "haskell",
".ml" => "ocaml", ".clj" => "clojure", ".scm" => "scheme",
".lisp" => "commonlisp", ".erl" => "erlang", ".ex" => "elixir",
".exs" => "elixir", ".d" => "d", ".nim" => "nim", ".zig" => "zig",
".v" => "v", ".dart" => "dart", ".groovy" => "groovy",
".scala" => "scala", ".tcl" => "tcl", ".raku" => "raku", ".m" => "objc"
)
# ANSI color codes
const BLUE = "\033[34m"
const RED = "\033[31m"
const GREEN = "\033[32m"
const YELLOW = "\033[33m"
const RESET = "\033[0m"
const API_BASE = "https://api.unsandbox.com"
const PORTAL_BASE = "https://unsandbox.com"
const LANGUAGES_CACHE_TTL = 3600
function detect_language(filename::String)::String
ext = lowercase(match(r"\.[^.]+$", filename).match)
return get(EXT_MAP, ext, "unknown")
end
function load_accounts_csv(path::String)::Vector{Tuple{String,String}}
accounts = Tuple{String,String}[]
isfile(path) || return accounts
try
for line in eachline(path)
trimmed = strip(line)
isempty(trimmed) && continue
startswith(trimmed, "#") && continue
parts = split(trimmed, ","; limit=2)
length(parts) >= 2 || continue
pk = strip(parts[1])
sk = strip(parts[2])
if startswith(pk, "unsb-pk-") && startswith(sk, "unsb-sk-")
push!(accounts, (pk, sk))
end
end
catch
end
return accounts
end
function get_credentials(; account_index::Int=-1)::Tuple{String,String}
# Priority 2: --account N => accounts.csv row N (bypasses env vars)
if account_index >= 0
for path in [joinpath(homedir(), ".unsandbox", "accounts.csv"), "accounts.csv"]
accts = load_accounts_csv(path)
if account_index < length(accts)
return accts[account_index + 1]
end
end
println(stderr, "$(RED)Error: No account at index $account_index in accounts.csv$(RESET)")
exit(1)
end
# Priority 3: Environment variables
public_key = get(ENV, "UNSANDBOX_PUBLIC_KEY", "")
secret_key = get(ENV, "UNSANDBOX_SECRET_KEY", "")
if !isempty(public_key) && !isempty(secret_key)
return (public_key, secret_key)
end
# Priority 4: ~/.unsandbox/accounts.csv row 0 (or UNSANDBOX_ACCOUNT env)
default_idx = tryparse(Int, get(ENV, "UNSANDBOX_ACCOUNT", "0"))
default_idx = something(default_idx, 0)
for path in [joinpath(homedir(), ".unsandbox", "accounts.csv"), "accounts.csv"]
accts = load_accounts_csv(path)
if default_idx < length(accts)
return accts[default_idx + 1]
end
end
# Legacy fallback
old_key = get(ENV, "UNSANDBOX_API_KEY", "")
if !isempty(old_key)
return (old_key, old_key)
end
println(stderr, "$(RED)Error: UNSANDBOX_PUBLIC_KEY/UNSANDBOX_SECRET_KEY or UNSANDBOX_API_KEY not set$(RESET)")
exit(1)
end
function get_api_keys(args_key=nothing; account_index::Int=-1)::Tuple{String,String}
# Priority 1: explicit -k flag
if args_key !== nothing && !isempty(string(args_key))
public_key = string(args_key)
secret_key = get(ENV, "UNSANDBOX_SECRET_KEY", "")
if !isempty(secret_key)
return (public_key, secret_key)
end
end
return get_credentials(account_index=account_index)
end
function hmac_sha256_hex(key::String, message::String)::String
h = hmac_sha256(Vector{UInt8}(key), Vector{UInt8}(message))
return bytes2hex(h)
end
function compute_signature(secret_key::String, timestamp::Int64, method::String, path::String, body::String)::String
message = "$(timestamp):$(method):$(path):$(body)"
return hmac_sha256_hex(secret_key, message)
end
function api_request(endpoint::String, public_key::String, secret_key::String; method="GET", data=nothing, sudo_otp=nothing, sudo_challenge=nothing)
url = API_BASE * endpoint
# Prepare body
body = data !== nothing ? JSON.json(data) : ""
# Generate timestamp and signature
timestamp = Int64(floor(time()))
signature = compute_signature(secret_key, timestamp, method, endpoint, body)
headers = [
"Authorization" => "Bearer $public_key",
"X-Timestamp" => string(timestamp),
"X-Signature" => signature,
"Content-Type" => "application/json"
]
# Add sudo headers if provided
if sudo_otp !== nothing
push!(headers, "X-Sudo-OTP" => sudo_otp)
end
if sudo_challenge !== nothing
push!(headers, "X-Sudo-Challenge" => sudo_challenge)
end
try
if method == "GET"
response = HTTP.get(url, headers, readtimeout=300)
elseif method == "POST"
response = HTTP.post(url, headers, body, readtimeout=300)
elseif method == "DELETE"
response = HTTP.delete(url, headers, readtimeout=300)
else
error("Unsupported method: $method")
end
return JSON.parse(String(response.body))
catch e
if isa(e, HTTP.ExceptionRequest.StatusError)
error_body = String(e.response.body)
if e.status == 401 && occursin("timestamp", lowercase(error_body))
println(stderr, "$(RED)Error: Request timestamp expired (must be within 5 minutes of server time)$(RESET)")
println(stderr, "$(YELLOW)Your computer's clock may have drifted.$(RESET)")
println(stderr, "Check your system time and sync with NTP if needed:")
println(stderr, " Linux: sudo ntpdate -s time.nist.gov")
println(stderr, " macOS: sudo sntp -sS time.apple.com")
println(stderr, " Windows: w32tm /resync")
else
println(stderr, "$(RED)Error: HTTP $(e.status) - $(error_body)$(RESET)")
end
else
println(stderr, "$(RED)Error: Request failed: $e$(RESET)")
end
exit(1)
end
end
# Handle 428 sudo OTP challenge - prompts user for OTP and retries the request
function handle_sudo_challenge(endpoint::String, public_key::String, secret_key::String, method::String, data, response_body::String)
# Extract challenge_id from response
parsed = JSON.parse(response_body)
challenge_id = get(parsed, "challenge_id", nothing)
println(stderr, "$(YELLOW)Confirmation required. Check your email for a one-time code.$(RESET)")
print(stderr, "Enter OTP: ")
otp = readline()
if isempty(strip(otp))
println(stderr, "$(RED)Error: Operation cancelled$(RESET)")
exit(1)
end
# Retry the request with sudo headers
return api_request(endpoint, public_key, secret_key, method=method, data=data, sudo_otp=strip(otp), sudo_challenge=challenge_id)
end
# API request that handles 428 sudo challenges for destructive operations
function api_request_with_sudo(endpoint::String, public_key::String, secret_key::String; method="DELETE", data=nothing)
url = API_BASE * endpoint
# Prepare body
body = data !== nothing ? JSON.json(data) : ""
# Generate timestamp and signature
timestamp = Int64(floor(time()))
signature = compute_signature(secret_key, timestamp, method, endpoint, body)
headers = [
"Authorization" => "Bearer $public_key",
"X-Timestamp" => string(timestamp),
"X-Signature" => signature,
"Content-Type" => "application/json"
]
try
if method == "GET"
response = HTTP.get(url, headers, readtimeout=300, status_exception=false)
elseif method == "POST"
response = HTTP.post(url, headers, body, readtimeout=300, status_exception=false)
elseif method == "DELETE"
response = HTTP.delete(url, headers, readtimeout=300, status_exception=false)
else
error("Unsupported method: $method")
end
response_body = String(response.body)
# Handle 428 - sudo OTP required
if response.status == 428
return handle_sudo_challenge(endpoint, public_key, secret_key, method, data, response_body)
end
# Handle other errors
if response.status >= 400
if response.status == 401 && occursin("timestamp", lowercase(response_body))
println(stderr, "$(RED)Error: Request timestamp expired (must be within 5 minutes of server time)$(RESET)")
println(stderr, "$(YELLOW)Your computer's clock may have drifted.$(RESET)")
println(stderr, "Check your system time and sync with NTP if needed:")
println(stderr, " Linux: sudo ntpdate -s time.nist.gov")
println(stderr, " macOS: sudo sntp -sS time.apple.com")
println(stderr, " Windows: w32tm /resync")
else
println(stderr, "$(RED)Error: HTTP $(response.status) - $(response_body)$(RESET)")
end
exit(1)
end
return JSON.parse(response_body)
catch e
println(stderr, "$(RED)Error: Request failed: $e$(RESET)")
exit(1)
end
end
function api_request_patch(endpoint::String, public_key::String, secret_key::String; data=nothing)
url = API_BASE * endpoint
# Prepare body
body = data !== nothing ? JSON.json(data) : ""
# Generate timestamp and signature
timestamp = Int64(floor(time()))
signature = compute_signature(secret_key, timestamp, "PATCH", endpoint, body)
headers = [
"Authorization" => "Bearer $public_key",
"X-Timestamp" => string(timestamp),
"X-Signature" => signature,
"Content-Type" => "application/json"
]
try
response = HTTP.request("PATCH", url, headers, body, readtimeout=300)
return JSON.parse(String(response.body))
catch e
if isa(e, HTTP.ExceptionRequest.StatusError)
error_body = String(e.response.body)
if e.status == 401 && occursin("timestamp", lowercase(error_body))
println(stderr, "$(RED)Error: Request timestamp expired (must be within 5 minutes of server time)$(RESET)")
println(stderr, "$(YELLOW)Your computer's clock may have drifted.$(RESET)")
println(stderr, "Check your system time and sync with NTP if needed:")
println(stderr, " Linux: sudo ntpdate -s time.nist.gov")
println(stderr, " macOS: sudo sntp -sS time.apple.com")
println(stderr, " Windows: w32tm /resync")
else
println(stderr, "$(RED)Error: HTTP $(e.status) - $(error_body)$(RESET)")
end
else
println(stderr, "$(RED)Error: Request failed: $e$(RESET)")
end
exit(1)
end
end
function api_request_text(endpoint::String, public_key::String, secret_key::String, body::String)::Bool
url = API_BASE * endpoint
timestamp = Int64(floor(time()))
signature = compute_signature(secret_key, timestamp, "PUT", endpoint, body)
headers = [
"Authorization" => "Bearer $public_key",
"X-Timestamp" => string(timestamp),
"X-Signature" => signature,
"Content-Type" => "text/plain"
]
try
response = HTTP.put(url, headers, body, readtimeout=300)
return response.status >= 200 && response.status < 300
catch e
return false
end
end
const MAX_ENV_CONTENT_SIZE = 65536
function read_env_file(path::String)::String
if !isfile(path)
println(stderr, "$(RED)Error: Env file not found: $path$(RESET)")
exit(1)
end
return read(path, String)
end
function build_env_content(envs::Vector{String}, env_file::Union{String,Nothing})::String
lines = copy(envs)
if env_file !== nothing
content = read_env_file(env_file)
for line in split(content, '\n')
trimmed = strip(line)
if !isempty(trimmed) && !startswith(trimmed, "#")
push!(lines, trimmed)
end
end
end
return join(lines, "\n")
end
function service_env_status(service_id::String, public_key::String, secret_key::String)
return api_request("/services/$service_id/env", public_key, secret_key)
end
function service_env_set(service_id::String, env_content::String, public_key::String, secret_key::String)::Bool
if length(env_content) > MAX_ENV_CONTENT_SIZE
println(stderr, "$(RED)Error: Env content exceeds maximum size of 64KB$(RESET)")
return false
end
return api_request_text("/services/$service_id/env", public_key, secret_key, env_content)
end
function service_env_export(service_id::String, public_key::String, secret_key::String)
return api_request("/services/$service_id/env/export", public_key, secret_key, method="POST", data=Dict())
end
function service_env_delete(service_id::String, public_key::String, secret_key::String)::Bool
try
api_request("/services/$service_id/env", public_key, secret_key, method="DELETE")
return true
catch
return false
end
end
function cmd_service_env(args)
(public_key, secret_key) = get_api_keys(args["api-key"]; account_index=something(get(args, "account", nothing), -1))
action = get(args, "env-action", nothing)
target = get(args, "env-target", nothing)
if action == "status"
if target === nothing
println(stderr, "$(RED)Error: service env status requires service ID$(RESET)")
exit(1)
end
result = service_env_status(target, public_key, secret_key)
has_vault = get(result, "has_vault", false)
if has_vault
println("$(GREEN)Vault: configured$(RESET)")
env_count = get(result, "env_count", nothing)
if env_count !== nothing
println("Variables: $env_count")
end
updated_at = get(result, "updated_at", nothing)
if updated_at !== nothing
println("Updated: $updated_at")
end
else
println("$(YELLOW)Vault: not configured$(RESET)")
end
elseif action == "set"
if target === nothing
println(stderr, "$(RED)Error: service env set requires service ID$(RESET)")
exit(1)
end
envs = something(args["vault-env"], String[])
env_file = get(args, "env-file", nothing)
if isempty(envs) && env_file === nothing
println(stderr, "$(RED)Error: service env set requires -e or --env-file$(RESET)")
exit(1)
end
env_content = build_env_content(envs, env_file)
if service_env_set(target, env_content, public_key, secret_key)
println("$(GREEN)Vault updated for service $target$(RESET)")
else
println(stderr, "$(RED)Error: Failed to update vault$(RESET)")
exit(1)
end
elseif action == "export"
if target === nothing
println(stderr, "$(RED)Error: service env export requires service ID$(RESET)")
exit(1)
end
result = service_env_export(target, public_key, secret_key)
content = get(result, "content", nothing)
if content !== nothing
print(content)
end
elseif action == "delete"
if target === nothing
println(stderr, "$(RED)Error: service env delete requires service ID$(RESET)")
exit(1)
end
if service_env_delete(target, public_key, secret_key)
println("$(GREEN)Vault deleted for service $target$(RESET)")
else
println(stderr, "$(RED)Error: Failed to delete vault$(RESET)")
exit(1)
end
else
println(stderr, "$(RED)Error: Unknown env action: $action$(RESET)")
println(stderr, "Usage: un.jl service env <status|set|export|delete> <service_id>")
exit(1)
end
end
function cmd_execute(args)
(public_key, secret_key) = get_api_keys(args["api-key"]; account_index=something(get(args, "account", nothing), -1))
filename = args["source_file"]
if !isfile(filename)
println(stderr, "$(RED)Error: File not found: $filename$(RESET)")
exit(1)
end
language = detect_language(filename)
if language == "unknown"
println(stderr, "$(RED)Error: Cannot detect language for $filename$(RESET)")
exit(1)
end
code = read(filename, String)
# Build request payload
payload = Dict("language" => language, "code" => code)
# Add environment variables
if args["env"] !== nothing
env_vars = Dict{String,String}()
for e in args["env"]
if occursin('=', e)
k, v = split(e, '=', limit=2)
env_vars[k] = v
end
end
if !isempty(env_vars)
payload["env"] = env_vars
end
end
# Add input files
if args["files"] !== nothing
input_files = []
for filepath in args["files"]
if !isfile(filepath)
println(stderr, "$(RED)Error: Input file not found: $filepath$(RESET)")
exit(1)
end
content = base64encode(read(filepath))
push!(input_files, Dict(
"filename" => basename(filepath),
"content_base64" => content
))
end
if !isempty(input_files)
payload["input_files"] = input_files
end
end
# Add options
if args["artifacts"]
payload["return_artifacts"] = true
end
if args["network"] !== nothing
payload["network"] = args["network"]
end
# Execute
result = api_request("/execute", public_key, secret_key, method="POST", data=payload)
# Print output
if haskey(result, "stdout") && !isempty(result["stdout"])
print(BLUE, result["stdout"], RESET)
end
if haskey(result, "stderr") && !isempty(result["stderr"])
print(RED, result["stderr"], RESET)
end
# Save artifacts
if args["artifacts"] && haskey(result, "artifacts")
out_dir = something(args["output-dir"], ".")
mkpath(out_dir)
for artifact in result["artifacts"]
filename = get(artifact, "filename", "artifact")
content = base64decode(artifact["content_base64"])
path = joinpath(out_dir, filename)
write(path, content)
chmod(path, 0o755)
println(stderr, "$(GREEN)Saved: $path$(RESET)")
end
end
exit_code = get(result, "exit_code", 0)
exit(exit_code)
end
function cmd_session(args)
(public_key, secret_key) = get_api_keys(args["api-key"]; account_index=something(get(args, "account", nothing), -1))
if args["list"]
result = api_request("/sessions", public_key, secret_key)
sessions = get(result, "sessions", [])
if isempty(sessions)
println("No active sessions")
else
@printf("%-40s %-10s %-10s %s\n", "ID", "Shell", "Status", "Created")
for s in sessions
@printf("%-40s %-10s %-10s %s\n",
get(s, "id", "N/A"),
get(s, "shell", "N/A"),
get(s, "status", "N/A"),
get(s, "created_at", "N/A"))
end
end
return
end
if args["kill"] !== nothing
api_request("/sessions/$(args["kill"])", public_key, secret_key, method="DELETE")
println("$(GREEN)Session terminated: $(args["kill"])$(RESET)")
return
end
# Create new session
payload = Dict("shell" => "bash")
if args["network"] !== nothing
payload["network"] = args["network"]
end
# Add input files
if args["files"] !== nothing
input_files = []
for filepath in args["files"]
if !isfile(filepath)
println(stderr, "$(RED)Error: Input file not found: $filepath$(RESET)")
exit(1)
end
content = base64encode(read(filepath))
push!(input_files, Dict(
"filename" => basename(filepath),
"content_base64" => content
))
end
if !isempty(input_files)
payload["input_files"] = input_files
end
end
println("$(YELLOW)Creating session...$(RESET)")
result = api_request("/sessions", public_key, secret_key, method="POST", data=payload)
println("$(GREEN)Session created: $(get(result, "id", "N/A"))$(RESET)")
println("$(YELLOW)(Interactive sessions require WebSocket - use un2 for full support)$(RESET)")
end
function cmd_service(args)
(public_key, secret_key) = get_api_keys(args["api-key"]; account_index=something(get(args, "account", nothing), -1))
# Handle env subcommand
if get(args, "env-action", nothing) !== nothing
cmd_service_env(args)
return
end
if args["list"]
result = api_request("/services", public_key, secret_key)
services = get(result, "services", [])
if isempty(services)
println("No services")
else
@printf("%-20s %-15s %-10s %-15s %s\n", "ID", "Name", "Status", "Ports", "Domains")
for s in services
ports = join(get(s, "ports", []), ',')
domains = join(get(s, "domains", []), ',')
@printf("%-20s %-15s %-10s %-15s %s\n",
get(s, "id", "N/A"),
get(s, "name", "N/A"),
get(s, "status", "N/A"),
ports, domains)
end
end
return
end
if args["info"] !== nothing
result = api_request("/services/$(args["info"])", public_key, secret_key)
println(JSON.json(result, 2))
return
end
if args["logs"] !== nothing
result = api_request("/services/$(args["logs"])/logs", public_key, secret_key)
println(get(result, "logs", ""))
return
end
if args["sleep"] !== nothing
api_request("/services/$(args["sleep"])/freeze", public_key, secret_key, method="POST")
println("$(GREEN)Service frozen: $(args["sleep"])$(RESET)")
return
end
if args["wake"] !== nothing
api_request("/services/$(args["wake"])/unfreeze", public_key, secret_key, method="POST")
println("$(GREEN)Service unfreezing: $(args["wake"])$(RESET)")
return
end
if args["destroy"] !== nothing
api_request_with_sudo("/services/$(args["destroy"])", public_key, secret_key, method="DELETE")
println("$(GREEN)Service destroyed: $(args["destroy"])$(RESET)")
return
end
if args["resize"] !== nothing
vcpu = args["vcpu"]
if vcpu === nothing || vcpu <= 0
println(stderr, "$(RED)Error: --resize requires --vcpu N (1-8)$(RESET)")
exit(1)
end
api_request_patch("/services/$(args["resize"])", public_key, secret_key, data=Dict("vcpu" => vcpu))
ram = vcpu * 2
println("$(GREEN)Service resized to $(vcpu) vCPU, $(ram) GB RAM$(RESET)")
return
end
if args["unfreeze-on-demand"] !== nothing
enabled = args["unfreeze-on-demand-value"]
if enabled === nothing
println(stderr, "$(RED)Error: --unfreeze-on-demand requires true or false$(RESET)")
exit(1)
end
api_request_patch("/services/$(args["unfreeze-on-demand"])", public_key, secret_key, data=Dict("unfreeze_on_demand" => enabled))
println("$(GREEN)Service unfreeze_on_demand set to $(enabled): $(args["unfreeze-on-demand"])$(RESET)")
return
end
if args["dump-bootstrap"] !== nothing
println(stderr, "Fetching bootstrap script from $(args["dump-bootstrap"])...")
payload = Dict("command" => "cat /tmp/bootstrap.sh")
result = api_request("/services/$(args["dump-bootstrap"])/execute", public_key, secret_key, method="POST", data=payload)
if haskey(result, "stdout") && !isempty(result["stdout"])
bootstrap = result["stdout"]
if args["dump-file"] !== nothing
# Write to file
try
write(args["dump-file"], bootstrap)
chmod(args["dump-file"], 0o755)
println("Bootstrap saved to $(args["dump-file"])")
catch e
println(stderr, "$(RED)Error: Could not write to $(args["dump-file"]): $e$(RESET)")
exit(1)
end
else
# Print to stdout
print(bootstrap)
end
else
println(stderr, "$(RED)Error: Failed to fetch bootstrap (service not running or no bootstrap file)$(RESET)")
exit(1)
end
return
end
# Create new service
if args["name"] !== nothing
payload = Dict("name" => args["name"])
if args["ports"] !== nothing
ports = [parse(Int, strip(p)) for p in split(args["ports"], ',')]
payload["ports"] = ports
end
if args["domains"] !== nothing
domains = [strip(d) for d in split(args["domains"], ',')]
payload["domains"] = domains
end
if args["type"] !== nothing
payload["service_type"] = args["type"]
end
if args["bootstrap"] !== nothing
payload["bootstrap"] = args["bootstrap"]
end
if args["bootstrap-file"] !== nothing
bootstrap_file = args["bootstrap-file"]
if isfile(bootstrap_file)
payload["bootstrap_content"] = read(bootstrap_file, String)
else
println(stderr, "$(RED)Error: Bootstrap file not found: $bootstrap_file$(RESET)")
exit(1)
end
end
if args["network"] !== nothing
payload["network"] = args["network"]
end
if args["vcpu"] !== nothing
payload["vcpu"] = args["vcpu"]
end
# Add input files
if args["files"] !== nothing
input_files = []
for filepath in args["files"]
if !isfile(filepath)
println(stderr, "$(RED)Error: Input file not found: $filepath$(RESET)")
exit(1)
end
content = base64encode(read(filepath))
push!(input_files, Dict(
"filename" => basename(filepath),
"content_base64" => content
))
end
if !isempty(input_files)
payload["input_files"] = input_files
end
end
result = api_request("/services", public_key, secret_key, method="POST", data=payload)
service_id = get(result, "id", nothing)
println("$(GREEN)Service created: $(something(service_id, "N/A"))$(RESET)")
println("Name: $(get(result, "name", "N/A"))")
if haskey(result, "url")
println("URL: $(result["url"])")
end
# Auto-set vault if env vars were provided
vault_envs = something(args["vault-env"], String[])
vault_env_file = get(args, "env-file", nothing)
if service_id !== nothing && (!isempty(vault_envs) || vault_env_file !== nothing)
env_content = build_env_content(vault_envs, vault_env_file)
if !isempty(env_content)
if service_env_set(service_id, env_content, public_key, secret_key)
println("$(GREEN)Vault configured with environment variables$(RESET)")
else
println("$(YELLOW)Warning: Failed to set vault$(RESET)")
end
end
end
return
end
println(stderr, "$(RED)Error: Use --name to create, or --list, --info, --logs, --freeze, --unfreeze, --destroy$(RESET)")
exit(1)
end
function validate_key(api_key::String)
url = PORTAL_BASE * "/keys/validate"
headers = [
"Authorization" => "Bearer $api_key",
"Content-Type" => "application/json"
]
try
response = HTTP.post(url, headers, "{}", readtimeout=300)
data = JSON.parse(String(response.body))
# Check if valid
if get(data, "valid", false)
# Print valid key info
println("$(GREEN)Valid$(RESET)\n")
println(@sprintf("%-20s %s", "Public Key:", get(data, "public_key", "N/A")))
println(@sprintf("%-20s %s", "Tier:", get(data, "tier", "N/A")))
println(@sprintf("%-20s %s", "Status:", get(data, "status", "N/A")))
println(@sprintf("%-20s %s", "Expires:", get(data, "valid_through_datetime", "N/A")))
println(@sprintf("%-20s %s", "Time Remaining:", get(data, "valid_for_human", "N/A")))
println(@sprintf("%-20s %s/min", "Rate Limit:", get(data, "rate_per_minute", "N/A")))
println(@sprintf("%-20s %s", "Burst:", get(data, "burst", "N/A")))
println(@sprintf("%-20s %s", "Concurrency:", get(data, "concurrency", "N/A")))
return 0
else
# Handle invalid response
reason = get(data, "reason", "unknown")
if reason == "expired"
println("$(RED)Expired$(RESET)\n")
println(@sprintf("%-20s %s", "Public Key:", get(data, "public_key", "N/A")))
println(@sprintf("%-20s %s", "Tier:", get(data, "tier", "N/A")))
expired_at = get(data, "expired_at_datetime", "N/A")
expired_ago = get(data, "expired_ago", "")
if !isempty(expired_ago)
println(@sprintf("%-20s %s (%s)", "Expired:", expired_at, expired_ago))
else
println(@sprintf("%-20s %s", "Expired:", expired_at))
end
renew_url = get(data, "renew_url", "https://unsandbox.com/pricing")
println("\n$(YELLOW)To renew:$(RESET) Visit $renew_url")
elseif reason == "invalid_key"
println("$(RED)Invalid$(RESET): key not found")
elseif reason == "suspended"
println("$(RED)Suspended$(RESET): key has been suspended")
else
println("$(RED)Invalid$(RESET): $reason")
end
return 1
end
catch e
if isa(e, HTTP.ExceptionRequest.StatusError)
# Parse error response from body
try
data = JSON.parse(String(e.response.body))
reason = get(data, "reason", "unknown")
if reason == "expired"
println("$(RED)Expired$(RESET)\n")
println(@sprintf("%-20s %s", "Public Key:", get(data, "public_key", "N/A")))
println(@sprintf("%-20s %s", "Tier:", get(data, "tier", "N/A")))
expired_at = get(data, "expired_at_datetime", "N/A")
expired_ago = get(data, "expired_ago", "")
if !isempty(expired_ago)
println(@sprintf("%-20s %s (%s)", "Expired:", expired_at, expired_ago))
else
println(@sprintf("%-20s %s", "Expired:", expired_at))
end
renew_url = get(data, "renew_url", "https://unsandbox.com/pricing")
println("\n$(YELLOW)To renew:$(RESET) Visit $renew_url")
elseif reason == "invalid_key"
println("$(RED)Invalid$(RESET): key not found")
elseif reason == "suspended"
println("$(RED)Suspended$(RESET): key has been suspended")
else
println("$(RED)Invalid$(RESET): $reason")
end
catch
println(stderr, "$(RED)Error: HTTP $(e.status)$(RESET)")
end
return 1
else
println(stderr, "$(RED)Error: Request failed: $e$(RESET)")
return 1
end
end
end
function get_languages_cache_path()::String
home = get(ENV, "HOME", ".")
return joinpath(home, ".unsandbox", "languages.json")
end
function load_languages_cache()::Union{Vector{String}, Nothing}
cache_path = get_languages_cache_path()
if !isfile(cache_path)
return nothing
end
try
content = read(cache_path, String)
data = JSON.parse(content)
timestamp = get(data, "timestamp", 0)
now = Int64(floor(time()))
if now - timestamp < LANGUAGES_CACHE_TTL
langs = get(data, "languages", [])
return [string(l) for l in langs]
else
return nothing
end
catch
return nothing
end
end
function save_languages_cache(languages::Vector)
cache_path = get_languages_cache_path()
cache_dir = dirname(cache_path)
# Ensure directory exists
mkpath(cache_dir)
timestamp = Int64(floor(time()))
data = Dict("languages" => languages, "timestamp" => timestamp)
try
open(cache_path, "w") do f
write(f, JSON.json(data))
end
catch
# Ignore write errors
end
end
function cmd_languages(args)
# Try to load from cache first
langs = load_languages_cache()
if langs === nothing
# Cache miss or expired, fetch from API
(public_key, secret_key) = get_api_keys(args["api-key"]; account_index=something(get(args, "account", nothing), -1))
result = api_request("/languages", public_key, secret_key)
langs = get(result, "languages", [])
save_languages_cache(langs)
end
if args["json"]
println(JSON.json(langs))
else
for lang in langs
println(lang)
end
end
end
function cmd_key(args)
(public_key, secret_key) = get_api_keys(args["api-key"]; account_index=something(get(args, "account", nothing), -1))
# For portal validation, we still use public_key as bearer token
api_key = public_key
# Handle --extend flag
if args["extend"]
# Validate key to get public key
url = PORTAL_BASE * "/keys/validate"
headers = [
"Authorization" => "Bearer $api_key",
"Content-Type" => "application/json"
]
try
response = HTTP.post(url, headers, "{}", readtimeout=300)
data = JSON.parse(String(response.body))
public_key = get(data, "public_key", nothing)
if public_key === nothing
println(stderr, "$(RED)Error: Invalid key or could not retrieve public key$(RESET)")
exit(1)
end
# Build extend URL
extend_url = "$(PORTAL_BASE)/keys/extend?pk=$(public_key)"
println("Opening extension page in browser...")
println("If browser doesn't open, visit: $extend_url")
# Try to open browser
if Sys.isapple()
run(`open $extend_url`)
elseif Sys.islinux()
try
run(`xdg-open $extend_url`)
catch
try
run(`sensible-browser $extend_url`)
catch
# Already printed the URL
end
end
elseif Sys.iswindows()
run(`cmd /c start $extend_url`)
end
exit(0)
catch e
println(stderr, "$(RED)Error: Failed to validate key: $e$(RESET)")
exit(1)
end
end
# Default: validate and display key info
exit(validate_key(api_key))
end
function main()
s = ArgParseSettings(description="Unsandbox CLI - Execute code in secure sandboxes")
@add_arg_table! s begin
"source_file"
help = "Source file to execute"
required = false
"--api-key", "-k"
help = "API key (or set UNSANDBOX_API_KEY)"
"--account"
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
arg_type = Int
"--network", "-n"
help = "Network mode"
arg_type = String
range_tester = x -> x in ["zerotrust", "semitrusted"]
"--env", "-e"
help = "Set environment variable (KEY=VALUE)"
action = :append_arg
"--files", "-f"
help = "Add input file"
action = :append_arg
"--artifacts", "-a"
help = "Return artifacts"
action = :store_true
"--output-dir", "-o"
help = "Output directory for artifacts"
"session"
help = "Manage interactive sessions"
action = :command
"service"
help = "Manage persistent services"
action = :command
"languages"
help = "List available programming languages"
action = :command
"key"
help = "Check API key validity and expiration"
action = :command
"image"
help = "Manage images"
action = :command
"snapshot"
help = "Manage snapshots"
action = :command
end
@add_arg_table! s["snapshot"] begin
"--list", "-l"
help = "List all snapshots"
action = :store_true
"--info"
help = "Get snapshot details"
"--delete"
help = "Delete a snapshot"
"--lock"
help = "Lock snapshot to prevent deletion"
"--unlock"
help = "Unlock snapshot"
"--restore"
help = "Restore from snapshot"
"--clone"
help = "Clone snapshot to session/service (requires --type)"
"--type"
help = "Clone type: session or service"
"--name"
help = "Name for cloned resource"
"--shell"
help = "Shell for cloned session"
"--ports"
help = "Comma-separated ports for cloned service"
"--api-key", "-k"
help = "API key"
"--account"
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
arg_type = Int
end
@add_arg_table! s["session"] begin
"--list", "-l"
help = "List active sessions"
action = :store_true
"--kill"
help = "Terminate session"
"--files", "-f"
help = "Add input file"
action = :append_arg
"--network", "-n"
help = "Network mode"
arg_type = String
range_tester = x -> x in ["zerotrust", "semitrusted"]
"--api-key", "-k"
help = "API key"
"--account"
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
arg_type = Int
end
@add_arg_table! s["service"] begin
"--name"
help = "Service name"
"--ports"
help = "Comma-separated ports"
"--domains"
help = "Comma-separated custom domains"
"--type"
help = "Service type for SRV records (minecraft, mumble, teamspeak, source, tcp, udp)"
"--bootstrap"
help = "Bootstrap command or URI"
"--bootstrap-file"
help = "Upload local file as bootstrap script"
"--files", "-f"
help = "Add input file"
action = :append_arg
"--vault-env", "-e"
help = "Environment variable for vault (KEY=VALUE)"
action = :append_arg
"--env-file"
help = "Load vault variables from file"
"--network", "-n"
help = "Network mode"
arg_type = String
range_tester = x -> x in ["zerotrust", "semitrusted"]
"--vcpu", "-v"
help = "vCPU count (1-8)"
arg_type = Int
range_tester = x -> x >= 1 && x <= 8
"--list", "-l"
help = "List services"
action = :store_true
"--info"
help = "Get service details"
"--logs"
help = "Get all logs"
"--freeze"
help = "Freeze service"
"--unfreeze"
help = "Unfreeze service"
"--destroy"
help = "Destroy service"
"--resize"
help = "Resize service (requires --vcpu N)"
"--unfreeze-on-demand"
help = "Service ID to set unfreeze_on_demand for"
"--unfreeze-on-demand-value"
help = "Enable/disable unfreeze_on_demand (true or false)"
arg_type = Bool
"--dump-bootstrap"
help = "Dump bootstrap script from service"
"--dump-file"
help = "File to save bootstrap (with --dump-bootstrap)"
"--env-action"
help = "Env action (status, set, export, delete)"
"--env-target"
help = "Service ID for env commands"
"--api-key", "-k"
help = "API key"
"--account"
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
arg_type = Int
"env"
help = "Manage service environment vault"
action = :command
end
@add_arg_table! s["service"]["env"] begin
"action"
help = "Env action: status, set, export, delete"
required = true
"service_id"
help = "Service ID"
required = false
"-e"
help = "Environment variable (KEY=VALUE)"
action = :append_arg
dest_name = "vault-env"
"--env-file"
help = "Load vault variables from file"
"--api-key", "-k"
help = "API key"
"--account"
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
arg_type = Int
end
@add_arg_table! s["key"] begin
"--extend"
help = "Open browser to extend/renew key"
action = :store_true
"--api-key", "-k"
help = "API key"
"--account"
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
arg_type = Int
end
@add_arg_table! s["languages"] begin
"--json"
help = "Output as JSON array"
action = :store_true
"--api-key", "-k"
help = "API key"
"--account"
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
arg_type = Int
end
@add_arg_table! s["image"] begin
"--list", "-l"
help = "List all images"
action = :store_true
"--info"
help = "Get image details"
"--delete"
help = "Delete an image"
"--lock"
help = "Lock image to prevent deletion"
"--unlock"
help = "Unlock image"
"--publish"
help = "Publish image from service/snapshot (requires --source-type)"
"--source-type"
help = "Source type: service or snapshot"
"--visibility"
help = "Image ID to set visibility for"
"--visibility-mode"
help = "Visibility mode: private, unlisted, or public"
"--spawn"
help = "Spawn new service from image"
"--clone"
help = "Clone an image"
"--name"
help = "Name for spawned service or cloned image"
"--ports"
help = "Comma-separated ports for spawned service"
"--api-key", "-k"
help = "API key"
"--account"
help = "Account index in ~/.unsandbox/accounts.csv (bypasses env vars)"
arg_type = Int
end
args = parse_args(ARGS, s)
if args["%COMMAND%"] == "session"
cmd_session(args["session"])
elseif args["%COMMAND%"] == "service"
service_args = args["service"]
# Check if env subcommand was used
if get(service_args, "%COMMAND%", nothing) == "env"
env_args = service_args["env"]
# Copy env args to service args
service_args["env-action"] = get(env_args, "action", nothing)
service_args["env-target"] = get(env_args, "service_id", nothing)
service_args["vault-env"] = get(env_args, "vault-env", nothing)
service_args["env-file"] = get(env_args, "env-file", nothing)
service_args["api-key"] = get(env_args, "api-key", nothing)
service_args["account"] = get(env_args, "account", nothing)
end
cmd_service(service_args)
elseif args["%COMMAND%"] == "languages"
cmd_languages(args["languages"])
elseif args["%COMMAND%"] == "key"
cmd_key(args["key"])
elseif args["%COMMAND%"] == "image"
cmd_image(args["image"])
elseif args["%COMMAND%"] == "snapshot"
cmd_snapshot(args["snapshot"])
elseif args["source_file"] !== nothing
cmd_execute(args)
else
println(stderr, "$(RED)Error: Provide source_file or use 'session'/'service'/'snapshot'/'languages'/'key'/'image' subcommand$(RESET)")
exit(1)
end
end
function cmd_image(args)
(public_key, secret_key) = get_api_keys(args["api-key"]; account_index=something(get(args, "account", nothing), -1))
if args["list"]
result = api_request("/images", public_key, secret_key)
println(JSON.json(result, 2))
return
end
if args["info"] !== nothing
result = api_request("/images/$(args["info"])", public_key, secret_key)
println(JSON.json(result, 2))
return
end
if args["delete"] !== nothing
api_request_with_sudo("/images/$(args["delete"])", public_key, secret_key, method="DELETE")
println("$(GREEN)Image deleted: $(args["delete"])$(RESET)")
return
end
if args["lock"] !== nothing
api_request("/images/$(args["lock"])/lock", public_key, secret_key, method="POST")
println("$(GREEN)Image locked: $(args["lock"])$(RESET)")
return
end
if args["unlock"] !== nothing
api_request_with_sudo("/images/$(args["unlock"])/unlock", public_key, secret_key, method="POST", data=Dict())
println("$(GREEN)Image unlocked: $(args["unlock"])$(RESET)")
return
end
if args["publish"] !== nothing
source_type = args["source-type"]
if source_type === nothing
println(stderr, "$(RED)Error: --source-type required (service or snapshot)$(RESET)")
exit(1)
end
payload = Dict("source_type" => source_type, "source_id" => args["publish"])
if args["name"] !== nothing
payload["name"] = args["name"]
end
result = api_request("/images/publish", public_key, secret_key, method="POST", data=payload)
println("$(GREEN)Image published$(RESET)")
println(JSON.json(result, 2))
return
end
if args["visibility"] !== nothing
mode = args["visibility-mode"]
if mode === nothing
println(stderr, "$(RED)Error: --visibility requires MODE (private, unlisted, or public)$(RESET)")
exit(1)
end
payload = Dict("visibility" => mode)
api_request("/images/$(args["visibility"])/visibility", public_key, secret_key, method="POST", data=payload)
println("$(GREEN)Image visibility set to $(mode): $(args["visibility"])$(RESET)")
return
end
if args["spawn"] !== nothing
payload = Dict()
if args["name"] !== nothing
payload["name"] = args["name"]
end
if args["ports"] !== nothing
ports = [parse(Int, strip(p)) for p in split(args["ports"], ',')]
payload["ports"] = ports
end
result = api_request("/images/$(args["spawn"])/spawn", public_key, secret_key, method="POST", data=payload)
println("$(GREEN)Service spawned from image$(RESET)")
println(JSON.json(result, 2))
return
end
if args["clone"] !== nothing
payload = Dict()
if args["name"] !== nothing
payload["name"] = args["name"]
end
result = api_request("/images/$(args["clone"])/clone", public_key, secret_key, method="POST", data=payload)
println("$(GREEN)Image cloned$(RESET)")
println(JSON.json(result, 2))
return
end
println(stderr, "$(RED)Error: Use --list, --info ID, --delete ID, --lock ID, --unlock ID, --publish ID, --visibility ID, --spawn ID, or --clone ID$(RESET)")
exit(1)
end
function cmd_snapshot(args)
(public_key, secret_key) = get_api_keys(args["api-key"]; account_index=something(get(args, "account", nothing), -1))
if args["list"]
result = api_request("/snapshots", public_key, secret_key)
snapshots = get(result, "snapshots", [])
if isempty(snapshots)
println("No snapshots found")
else
@printf("%-40s %-20s %-12s %-30s %s\n", "ID", "Name", "Type", "Source ID", "Size")
for s in snapshots
@printf("%-40s %-20s %-12s %-30s %s\n",
get(s, "id", "N/A"),
get(s, "name", "-"),
get(s, "source_type", "N/A"),
get(s, "source_id", "N/A"),
get(s, "size", "N/A"))
end
end
return
end
if args["info"] !== nothing
result = api_request("/snapshots/$(args["info"])", public_key, secret_key)
println(JSON.json(result, 2))
return
end
if args["delete"] !== nothing
api_request_with_sudo("/snapshots/$(args["delete"])", public_key, secret_key, method="DELETE")
println("$(GREEN)Snapshot deleted: $(args["delete"])$(RESET)")
return
end
if args["lock"] !== nothing
api_request("/snapshots/$(args["lock"])/lock", public_key, secret_key, method="POST")
println("$(GREEN)Snapshot locked: $(args["lock"])$(RESET)")
return
end
if args["unlock"] !== nothing
api_request_with_sudo("/snapshots/$(args["unlock"])/unlock", public_key, secret_key, method="POST", data=Dict())
println("$(GREEN)Snapshot unlocked: $(args["unlock"])$(RESET)")
return
end
if args["restore"] !== nothing
api_request("/snapshots/$(args["restore"])/restore", public_key, secret_key, method="POST", data=Dict())
println("$(GREEN)Snapshot restored: $(args["restore"])$(RESET)")
return
end
if args["clone"] !== nothing
clone_type = args["type"]
if clone_type === nothing
println(stderr, "$(RED)Error: --type required for --clone (session or service)$(RESET)")
exit(1)
end
payload = Dict("type" => clone_type)
if args["name"] !== nothing
payload["name"] = args["name"]
end
if args["shell"] !== nothing
payload["shell"] = args["shell"]
end
if args["ports"] !== nothing
ports = [parse(Int, strip(p)) for p in split(args["ports"], ',')]
payload["ports"] = ports
end
result = api_request("/snapshots/$(args["clone"])/clone", public_key, secret_key, method="POST", data=payload)
println("$(GREEN)Cloned from snapshot$(RESET)")
println(JSON.json(result, 2))
return
end
println(stderr, "$(RED)Error: Use --list, --info ID, --delete ID, --lock ID, --unlock ID, --restore ID, or --clone ID$(RESET)")
exit(1)
end
# =============================================================================
# Library API Functions (for import/use as a module)
# =============================================================================
const VERSION = "1.0.0"
"""
execute(language::String, code::String; kwargs...) -> Dict
Execute code synchronously and return the result.
# Arguments
- `language`: Programming language (e.g., "python", "javascript")
- `code`: Source code to execute
# Keyword Arguments
- `env::Dict{String,String}`: Environment variables
- `network::String`: Network mode ("zerotrust" or "semitrusted")
- `public_key::String`: API public key (optional)
- `secret_key::String`: API secret key (optional)
# Returns
Dict with `stdout`, `stderr`, `exit_code`, `success`
# Example
```julia
result = execute("python", "print('Hello, World!')")
println(result["stdout"])
```
"""
function execute(language::String, code::String;
env::Union{Dict{String,String}, Nothing}=nothing,
network::String="zerotrust",
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
payload = Dict("language" => language, "code" => code)
if env !== nothing
payload["env"] = env
end
if network != "zerotrust"
payload["network"] = network
end
return api_request("/execute", pk, sk, method="POST", data=payload)
end
"""
execute_async(language::String, code::String; kwargs...) -> String
Execute code asynchronously and return job ID.
# Returns
Job ID string for polling with `wait_job` or `get_job`.
"""
function execute_async(language::String, code::String;
env::Union{Dict{String,String}, Nothing}=nothing,
network::String="zerotrust",
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
payload = Dict("language" => language, "code" => code, "async" => true)
if env !== nothing
payload["env"] = env
end
if network != "zerotrust"
payload["network"] = network
end
result = api_request("/execute", pk, sk, method="POST", data=payload)
return get(result, "job_id", "")
end
"""
wait_job(job_id::String; kwargs...) -> Dict
Wait for an async job to complete and return results.
"""
function wait_job(job_id::String;
poll_interval::Int=1,
max_wait::Int=300,
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
start_time = time()
while true
result = get_job(job_id, public_key=pk, secret_key=sk)
status = get(result, "status", "")
if status in ["completed", "failed", "timeout", "cancelled"]
return result
end
if time() - start_time >= max_wait
error("Job $job_id did not complete within $max_wait seconds")
end
sleep(poll_interval)
end
end
"""
get_job(job_id::String; kwargs...) -> Dict
Get the status of an async job.
"""
function get_job(job_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
return api_request("/jobs/$job_id", pk, sk)
end
"""
cancel_job(job_id::String; kwargs...) -> Bool
Cancel a running job.
"""
function cancel_job(job_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request("/jobs/$job_id", pk, sk, method="DELETE")
return true
end
"""
list_jobs(; kwargs...) -> Vector{Dict}
List all jobs for the account.
"""
function list_jobs(;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
result = api_request("/jobs", pk, sk)
return get(result, "jobs", [])
end
"""
get_languages(; kwargs...) -> Vector{String}
Get list of supported programming languages.
"""
function get_languages(;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
result = api_request("/languages", pk, sk)
return get(result, "languages", [])
end
"""
session_list(; kwargs...) -> Vector{Dict}
List all active sessions.
"""
function session_list(;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
result = api_request("/sessions", pk, sk)
return get(result, "sessions", [])
end
"""
session_get(session_id::String; kwargs...) -> Dict
Get details of a session.
"""
function session_get(session_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
return api_request("/sessions/$session_id", pk, sk)
end
"""
session_create(; kwargs...) -> Dict
Create a new interactive session.
"""
function session_create(;
shell::String="bash",
network::String="zerotrust",
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
payload = Dict("shell" => shell)
if network != "zerotrust"
payload["network"] = network
end
return api_request("/sessions", pk, sk, method="POST", data=payload)
end
"""
session_destroy(session_id::String; kwargs...) -> Bool
Destroy a session.
"""
function session_destroy(session_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request("/sessions/$session_id", pk, sk, method="DELETE")
return true
end
"""
session_freeze(session_id::String; kwargs...) -> Bool
Freeze (pause) a session.
"""
function session_freeze(session_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request("/sessions/$session_id/freeze", pk, sk, method="POST")
return true
end
"""
session_unfreeze(session_id::String; kwargs...) -> Bool
Unfreeze (resume) a session.
"""
function session_unfreeze(session_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request("/sessions/$session_id/unfreeze", pk, sk, method="POST")
return true
end
"""
session_boost(session_id::String, vcpu::Int; kwargs...) -> Bool
Boost session resources.
"""
function session_boost(session_id::String, vcpu::Int;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request_patch("/sessions/$session_id", pk, sk, data=Dict("vcpu" => vcpu))
return true
end
"""
session_unboost(session_id::String; kwargs...) -> Bool
Remove session boost.
"""
function session_unboost(session_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request_patch("/sessions/$session_id", pk, sk, data=Dict("vcpu" => 1))
return true
end
"""
session_execute(session_id::String, command::String; kwargs...) -> Dict
Execute a command in a session.
"""
function session_execute(session_id::String, command::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
return api_request("/sessions/$session_id/execute", pk, sk, method="POST", data=Dict("command" => command))
end
"""
service_list(; kwargs...) -> Vector{Dict}
List all services.
"""
function service_list(;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
result = api_request("/services", pk, sk)
return get(result, "services", [])
end
"""
service_get(service_id::String; kwargs...) -> Dict
Get details of a service.
"""
function service_get(service_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
return api_request("/services/$service_id", pk, sk)
end
"""
service_create(name::String; kwargs...) -> Dict
Create a new service.
"""
function service_create(name::String;
ports::Union{Vector{Int}, Nothing}=nothing,
domains::Union{Vector{String}, Nothing}=nothing,
bootstrap::Union{String, Nothing}=nothing,
network::String="semitrusted",
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
payload = Dict("name" => name)
if ports !== nothing
payload["ports"] = ports
end
if domains !== nothing
payload["domains"] = domains
end
if bootstrap !== nothing
payload["bootstrap"] = bootstrap
end
if network != "semitrusted"
payload["network"] = network
end
return api_request("/services", pk, sk, method="POST", data=payload)
end
"""
service_destroy(service_id::String; kwargs...) -> Bool
Destroy a service.
"""
function service_destroy(service_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request_with_sudo("/services/$service_id", pk, sk, method="DELETE")
return true
end
"""
service_freeze(service_id::String; kwargs...) -> Bool
Freeze (pause) a service.
"""
function service_freeze(service_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request("/services/$service_id/freeze", pk, sk, method="POST")
return true
end
"""
service_unfreeze(service_id::String; kwargs...) -> Bool
Unfreeze (resume) a service.
"""
function service_unfreeze(service_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request("/services/$service_id/unfreeze", pk, sk, method="POST")
return true
end
"""
service_lock(service_id::String; kwargs...) -> Bool
Lock a service to prevent deletion.
"""
function service_lock(service_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request("/services/$service_id/lock", pk, sk, method="POST")
return true
end
"""
service_unlock(service_id::String; kwargs...) -> Bool
Unlock a service.
"""
function service_unlock(service_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request_with_sudo("/services/$service_id/unlock", pk, sk, method="POST", data=Dict())
return true
end
"""
service_set_unfreeze_on_demand(service_id::String, enabled::Bool; kwargs...) -> Bool
Set unfreeze-on-demand for a service.
"""
function service_set_unfreeze_on_demand(service_id::String, enabled::Bool;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request_patch("/services/$service_id", pk, sk, data=Dict("unfreeze_on_demand" => enabled))
return true
end
"""
service_redeploy(service_id::String; kwargs...) -> Bool
Redeploy a service (re-run bootstrap).
"""
function service_redeploy(service_id::String;
bootstrap::Union{String, Nothing}=nothing,
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
payload = bootstrap !== nothing ? Dict("bootstrap" => bootstrap) : Dict()
api_request("/services/$service_id/redeploy", pk, sk, method="POST", data=payload)
return true
end
"""
service_logs(service_id::String; kwargs...) -> String
Get service logs.
"""
function service_logs(service_id::String;
all_logs::Bool=false,
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
endpoint = all_logs ? "/services/$service_id/logs?all=true" : "/services/$service_id/logs"
result = api_request(endpoint, pk, sk)
return get(result, "logs", "")
end
"""
service_execute(service_id::String, command::String; kwargs...) -> Dict
Execute a command in a service.
"""
function service_execute(service_id::String, command::String;
timeout_ms::Int=30000,
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
return api_request("/services/$service_id/execute", pk, sk, method="POST",
data=Dict("command" => command, "timeout_ms" => timeout_ms))
end
"""
service_resize(service_id::String, vcpu::Int; kwargs...) -> Bool
Resize a service.
"""
function service_resize(service_id::String, vcpu::Int;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request_patch("/services/$service_id", pk, sk, data=Dict("vcpu" => vcpu))
return true
end
"""
service_env_get(service_id::String; kwargs...) -> String
Get service environment variables.
"""
function service_env_get(service_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
result = api_request("/services/$service_id/env", pk, sk)
return get(result, "env", "")
end
"""
service_env_set(service_id::String, env_content::String; kwargs...) -> Bool
Set service environment variables.
"""
function service_env_set(service_id::String, env_content::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request("/services/$service_id/env", pk, sk, method="POST", data=Dict("env" => env_content))
return true
end
"""
service_env_delete(service_id::String; kwargs...) -> Bool
Delete service environment variables.
"""
function service_env_delete(service_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request("/services/$service_id/env", pk, sk, method="DELETE")
return true
end
"""
service_env_export(service_id::String; kwargs...) -> String
Export service environment variables as shell format.
"""
function service_env_export(service_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
result = api_request("/services/$service_id/env/export", pk, sk)
return get(result, "export", "")
end
"""
snapshot_list(; kwargs...) -> Vector{Dict}
List all snapshots.
"""
function snapshot_list(;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
result = api_request("/snapshots", pk, sk)
return get(result, "snapshots", [])
end
"""
snapshot_get(snapshot_id::String; kwargs...) -> Dict
Get details of a snapshot.
"""
function snapshot_get(snapshot_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
return api_request("/snapshots/$snapshot_id", pk, sk)
end
"""
snapshot_session(session_id::String; kwargs...) -> String
Create a snapshot from a session. Returns snapshot ID.
"""
function snapshot_session(session_id::String;
name::Union{String, Nothing}=nothing,
hot::Bool=false,
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
payload = Dict()
if name !== nothing
payload["name"] = name
end
if hot
payload["hot"] = true
end
result = api_request("/sessions/$session_id/snapshot", pk, sk, method="POST", data=payload)
return get(result, "id", "")
end
"""
snapshot_service(service_id::String; kwargs...) -> String
Create a snapshot from a service. Returns snapshot ID.
"""
function snapshot_service(service_id::String;
name::Union{String, Nothing}=nothing,
hot::Bool=false,
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
payload = Dict()
if name !== nothing
payload["name"] = name
end
if hot
payload["hot"] = true
end
result = api_request("/services/$service_id/snapshot", pk, sk, method="POST", data=payload)
return get(result, "id", "")
end
"""
snapshot_restore(snapshot_id::String; kwargs...) -> Bool
Restore from a snapshot.
"""
function snapshot_restore(snapshot_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request("/snapshots/$snapshot_id/restore", pk, sk, method="POST", data=Dict())
return true
end
"""
snapshot_delete(snapshot_id::String; kwargs...) -> Bool
Delete a snapshot.
"""
function snapshot_delete(snapshot_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request_with_sudo("/snapshots/$snapshot_id", pk, sk, method="DELETE")
return true
end
"""
snapshot_lock(snapshot_id::String; kwargs...) -> Bool
Lock a snapshot to prevent deletion.
"""
function snapshot_lock(snapshot_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request("/snapshots/$snapshot_id/lock", pk, sk, method="POST")
return true
end
"""
snapshot_unlock(snapshot_id::String; kwargs...) -> Bool
Unlock a snapshot.
"""
function snapshot_unlock(snapshot_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request_with_sudo("/snapshots/$snapshot_id/unlock", pk, sk, method="POST", data=Dict())
return true
end
"""
snapshot_clone(snapshot_id::String, clone_type::String; kwargs...) -> String
Clone a snapshot to a new session or service. Returns the new resource ID.
"""
function snapshot_clone(snapshot_id::String, clone_type::String;
name::Union{String, Nothing}=nothing,
ports::Union{Vector{Int}, Nothing}=nothing,
shell::Union{String, Nothing}=nothing,
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
payload = Dict("type" => clone_type)
if name !== nothing
payload["name"] = name
end
if ports !== nothing
payload["ports"] = ports
end
if shell !== nothing
payload["shell"] = shell
end
result = api_request("/snapshots/$snapshot_id/clone", pk, sk, method="POST", data=payload)
return get(result, "id", "")
end
"""
image_list(; kwargs...) -> Vector{Dict}
List all images.
"""
function image_list(;
filter::Union{String, Nothing}=nothing,
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
endpoint = filter !== nothing ? "/images?filter=$filter" : "/images"
result = api_request(endpoint, pk, sk)
return get(result, "images", [])
end
"""
image_get(image_id::String; kwargs...) -> Dict
Get details of an image.
"""
function image_get(image_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
return api_request("/images/$image_id", pk, sk)
end
"""
image_publish(source_type::String, source_id::String; kwargs...) -> String
Publish an image from a service or snapshot. Returns image ID.
"""
function image_publish(source_type::String, source_id::String;
name::Union{String, Nothing}=nothing,
description::Union{String, Nothing}=nothing,
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
payload = Dict("source_type" => source_type, "source_id" => source_id)
if name !== nothing
payload["name"] = name
end
if description !== nothing
payload["description"] = description
end
result = api_request("/images/publish", pk, sk, method="POST", data=payload)
return get(result, "id", "")
end
"""
image_delete(image_id::String; kwargs...) -> Bool
Delete an image.
"""
function image_delete(image_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request_with_sudo("/images/$image_id", pk, sk, method="DELETE")
return true
end
"""
image_lock(image_id::String; kwargs...) -> Bool
Lock an image to prevent deletion.
"""
function image_lock(image_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request("/images/$image_id/lock", pk, sk, method="POST")
return true
end
"""
image_unlock(image_id::String; kwargs...) -> Bool
Unlock an image.
"""
function image_unlock(image_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request_with_sudo("/images/$image_id/unlock", pk, sk, method="POST", data=Dict())
return true
end
"""
image_set_visibility(image_id::String, visibility::String; kwargs...) -> Bool
Set image visibility (private, unlisted, or public).
"""
function image_set_visibility(image_id::String, visibility::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request("/images/$image_id/visibility", pk, sk, method="POST", data=Dict("visibility" => visibility))
return true
end
"""
image_grant_access(image_id::String, trusted_api_key::String; kwargs...) -> Bool
Grant access to an image.
"""
function image_grant_access(image_id::String, trusted_api_key::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request("/images/$image_id/access", pk, sk, method="POST", data=Dict("trusted_api_key" => trusted_api_key))
return true
end
"""
image_revoke_access(image_id::String, trusted_api_key::String; kwargs...) -> Bool
Revoke access to an image.
"""
function image_revoke_access(image_id::String, trusted_api_key::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request("/images/$image_id/access/$trusted_api_key", pk, sk, method="DELETE")
return true
end
"""
image_list_trusted(image_id::String; kwargs...) -> Vector{String}
List trusted API keys for an image.
"""
function image_list_trusted(image_id::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
result = api_request("/images/$image_id/access", pk, sk)
return get(result, "trusted_keys", [])
end
"""
image_transfer(image_id::String, to_api_key::String; kwargs...) -> Bool
Transfer ownership of an image.
"""
function image_transfer(image_id::String, to_api_key::String;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
api_request("/images/$image_id/transfer", pk, sk, method="POST", data=Dict("to_api_key" => to_api_key))
return true
end
"""
image_spawn(image_id::String; kwargs...) -> String
Spawn a new service from an image. Returns service ID.
"""
function image_spawn(image_id::String;
name::Union{String, Nothing}=nothing,
ports::Union{Vector{Int}, Nothing}=nothing,
bootstrap::Union{String, Nothing}=nothing,
network::Union{String, Nothing}=nothing,
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
payload = Dict()
if name !== nothing
payload["name"] = name
end
if ports !== nothing
payload["ports"] = ports
end
if bootstrap !== nothing
payload["bootstrap"] = bootstrap
end
if network !== nothing
payload["network"] = network
end
result = api_request("/images/$image_id/spawn", pk, sk, method="POST", data=payload)
return get(result, "id", "")
end
"""
image_clone(image_id::String; kwargs...) -> String
Clone an image. Returns new image ID.
"""
function image_clone(image_id::String;
name::Union{String, Nothing}=nothing,
description::Union{String, Nothing}=nothing,
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
payload = Dict()
if name !== nothing
payload["name"] = name
end
if description !== nothing
payload["description"] = description
end
result = api_request("/images/$image_id/clone", pk, sk, method="POST", data=payload)
return get(result, "id", "")
end
"""
logs_fetch(source::String; kwargs...) -> String
Fetch PaaS logs.
"""
function logs_fetch(source::String;
lines::Int=100,
since::String="1h",
grep::Union{String, Nothing}=nothing,
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
endpoint = "/logs?source=$source&lines=$lines&since=$since"
if grep !== nothing
endpoint *= "&grep=$grep"
end
result = api_request(endpoint, pk, sk)
return get(result, "logs", "")
end
"""
validate_keys(; kwargs...) -> Dict
Validate API keys and return account info.
"""
function validate_keys(;
public_key::Union{String,Nothing}=nothing,
secret_key::Union{String,Nothing}=nothing)
(pk, sk) = if public_key !== nothing && secret_key !== nothing
(public_key, secret_key)
else
get_api_keys(nothing)
end
url = PORTAL_BASE * "/keys/validate"
timestamp = Int64(floor(time()))
body = "{}"
signature = compute_signature(sk, timestamp, "POST", "/keys/validate", body)
headers = [
"Authorization" => "Bearer $pk",
"X-Timestamp" => string(timestamp),
"X-Signature" => signature,
"Content-Type" => "application/json"
]
try
response = HTTP.post(url, headers, body, readtimeout=30)
return JSON.parse(String(response.body))
catch e
return Dict("valid" => false, "error" => string(e))
end
end
"""
health_check() -> Bool
Check if the API is healthy.
"""
function health_check()
try
response = HTTP.get("$API_BASE/health", readtimeout=10)
return response.status == 200
catch
return false
end
end
"""
version() -> String
Get SDK version.
"""
function version()
return VERSION
end
# Thread-local error storage
const _last_error = Ref{String}("")
"""
last_error() -> String
Get the last error message.
"""
function last_error()
return _last_error[]
end
"""
set_last_error(msg::String)
Set the last error message (internal use).
"""
function set_last_error(msg::String)
_last_error[] = msg
end
"""
hmac_sign(secret_key::String, message::String) -> String
Compute HMAC-SHA256 signature.
"""
function hmac_sign(secret_key::String, message::String)
return bytes2hex(SHA.hmac_sha256(Vector{UInt8}(secret_key), Vector{UInt8}(message)))
end
main()
Esclarecimentos de documentação
Dependências
C Binary (un1) — requer libcurl e libwebsockets:
sudo apt install build-essential libcurl4-openssl-dev libwebsockets-dev
wget unsandbox.com/downloads/un.c && gcc -O2 -o un un.c -lcurl -lwebsockets
Implementações SDK — a maioria usa apenas stdlib (Ruby, JS, Go, etc). Alguns requerem dependências mínimas:
pip install requests # Python
Executar Código
Executar um script
./un hello.py
./un app.js
./un main.rs
Com variáveis de ambiente
./un -e DEBUG=1 -e NAME=World script.py
Com arquivos de entrada (teletransportar arquivos para sandbox)
./un -f data.csv -f config.json process.py
Obter binário compilado
./un -a -o ./bin main.c
Sessões interativas
Iniciar uma sessão de shell
# Default bash shell
./un session
# Choose your shell
./un session --shell zsh
./un session --shell fish
# Jump into a REPL
./un session --shell python3
./un session --shell node
./un session --shell julia
Sessão com acesso à rede
./un session -n semitrusted
Auditoria de sessão (gravação completa do terminal)
# Record everything (including vim, interactive programs)
./un session --audit -o ./logs
# Replay session later
zcat session.log*.gz | less -R
Coletar artefatos da sessão
# Files in /tmp/artifacts/ are collected on exit
./un session -a -o ./outputs
Persistência de sessão (tmux/screen)
# Default: session terminates on disconnect (clean exit)
./un session
# With tmux: session persists, can reconnect later
./un session --tmux
# Press Ctrl+b then d to detach
# With screen: alternative multiplexer
./un session --screen
# Press Ctrl+a then d to detach
Listar Trabalhos Ativos
./un session --list
# Output:
# Active sessions: 2
#
# SESSION ID CONTAINER SHELL TTL STATUS
# abc123... unsb-vm-12345 python3 45m30s active
# def456... unsb-vm-67890 bash 1h2m active
Reconectar à sessão existente
# Reconnect by container name (requires --tmux or --screen)
./un session --attach unsb-vm-12345
# Use exit to terminate session, or detach to keep it running
Encerrar uma sessão
./un session --kill unsb-vm-12345
Shells e REPLs disponíveis
Shells: bash, dash, sh, zsh, fish, ksh, tcsh, csh, elvish, xonsh, ash
REPLs: python3, bpython, ipython # Python
node # JavaScript
ruby, irb # Ruby
lua # Lua
php # PHP
perl # Perl
guile, scheme # Scheme
ghci # Haskell
erl, iex # Erlang/Elixir
sbcl, clisp # Common Lisp
r # R
julia # Julia
clojure # Clojure
Gerenciamento de Chave API
Verificar Status do Pagamento
# Check if your API key is valid
./un key
# Output:
# Valid: key expires in 30 days
Estender Chave Expirada
# Open the portal to extend an expired key
./un key --extend
# This opens the unsandbox.com portal where you can
# add more credits to extend your key's expiration
Autenticação
As credenciais são carregadas em ordem de prioridade (maior primeiro):
# 1. CLI flags (highest priority)
./un -p unsb-pk-xxxx -k unsb-sk-xxxxx script.py
# 2. Environment variables
export UNSANDBOX_PUBLIC_KEY=unsb-pk-xxxx-xxxx-xxxx-xxxx
export UNSANDBOX_SECRET_KEY=unsb-sk-xxxxx-xxxxx-xxxxx-xxxxx
./un script.py
# 3. Config file (lowest priority)
# ~/.unsandbox/accounts.csv format: public_key,secret_key
mkdir -p ~/.unsandbox
echo "unsb-pk-xxxx-xxxx-xxxx-xxxx,unsb-sk-xxxxx-xxxxx-xxxxx-xxxxx" > ~/.unsandbox/accounts.csv
./un script.py
As requisições são assinadas com HMAC-SHA256. O token bearer contém apenas a chave pública; a chave secreta calcula a assinatura (nunca é transmitida).
Escalonamento de Recursos
Definir Quantidade de vCPU
# Default: 1 vCPU, 2GB RAM
./un script.py
# Scale up: 4 vCPUs, 8GB RAM
./un -v 4 script.py
# Maximum: 8 vCPUs, 16GB RAM
./un --vcpu 8 heavy_compute.py
Reforço de Sessão Ao Vivo
# Boost a running session to 2 vCPU, 4GB RAM
./un session --boost sandbox-abc
# Boost to specific vCPU count (4 vCPU, 8GB RAM)
./un session --boost sandbox-abc --boost-vcpu 4
# Return to base resources (1 vCPU, 2GB RAM)
./un session --unboost sandbox-abc
Congelar/Descongelar Sessão
Congelar e Descongelar Sessões
# Freeze a session (stop billing, preserve state)
./un session --freeze sandbox-abc
# Unfreeze a frozen session
./un session --unfreeze sandbox-abc
# Note: Requires --tmux or --screen for persistence
Serviços Persistentes
Criar um Serviço
# Web server with ports
./un service --name web --ports 80,443 --bootstrap "python -m http.server 80"
# With custom domains
./un service --name blog --ports 8000 --domains blog.example.com
# Game server with SRV records
./un service --name mc --type minecraft --bootstrap ./setup.sh
# Deploy app tarball with bootstrap script
./un service --name app --ports 8000 -f app.tar.gz --bootstrap-file ./setup.sh
# setup.sh: cd /tmp && tar xzf app.tar.gz && ./app/start.sh
Gerenciar Serviços
# List all services
./un service --list
# Get service details
./un service --info abc123
# View bootstrap logs
./un service --logs abc123
./un service --tail abc123 # last 9000 lines
# Execute command in running service
./un service --execute abc123 'journalctl -u myapp -n 50'
# Dump bootstrap script (for migrations)
./un service --dump-bootstrap abc123
./un service --dump-bootstrap abc123 backup.sh
# Freeze/unfreeze service
./un service --freeze abc123
./un service --unfreeze abc123
# Service settings (auto-wake, freeze page display)
./un service --auto-unfreeze abc123 # enable auto-wake on HTTP
./un service --no-auto-unfreeze abc123 # disable auto-wake
./un service --show-freeze-page abc123 # show HTML payment page (default)
./un service --no-show-freeze-page abc123 # return JSON error instead
# Redeploy with new bootstrap
./un service --redeploy abc123 --bootstrap ./new-setup.sh
# Destroy service
./un service --destroy abc123
Snapshots
Listar Snapshots
./un snapshot --list
# Output:
# Snapshots: 3
#
# SNAPSHOT ID NAME SOURCE SIZE CREATED
# unsb-snapshot-a1b2-c3d4-e5f6-g7h8 before-upgrade session 512 MB 2h ago
# unsb-snapshot-i9j0-k1l2-m3n4-o5p6 stable-v1.0 service 1.2 GB 1d ago
Criar Snapshot da Sessão
# Snapshot with name
./un session --snapshot unsb-vm-12345 --name "before upgrade"
# Quick snapshot (auto-generated name)
./un session --snapshot unsb-vm-12345
Criar Snapshot do Serviço
# Standard snapshot (pauses container briefly)
./un service --snapshot unsb-service-abc123 --name "stable v1.0"
# Hot snapshot (no pause, may be inconsistent)
./un service --snapshot unsb-service-abc123 --hot
Restaurar a partir do Snapshot
# Restore session from snapshot
./un session --restore unsb-snapshot-a1b2-c3d4-e5f6-g7h8
# Restore service from snapshot
./un service --restore unsb-snapshot-i9j0-k1l2-m3n4-o5p6
Excluir Snapshot
./un snapshot --delete unsb-snapshot-a1b2-c3d4-e5f6-g7h8
Imagens
Imagens são imagens de container independentes e transferíveis que sobrevivem à exclusão do container. Diferente dos snapshots (que permanecem com seu container), imagens podem ser compartilhadas com outros usuários, transferidas entre chaves de API ou tornadas públicas no marketplace.
Listar Imagens
# List all images (owned + shared + public)
./un image --list
# List only your images
./un image --list owned
# List images shared with you
./un image --list shared
# List public marketplace images
./un image --list public
# Get image details
./un image --info unsb-image-xxxx-xxxx-xxxx-xxxx
Publicar Imagens
# Publish from a stopped or frozen service
./un image --publish-service unsb-service-abc123 \
--name "My App v1.0" --description "Production snapshot"
# Publish from a snapshot
./un image --publish-snapshot unsb-snapshot-xxxx-xxxx-xxxx-xxxx \
--name "Stable Release"
# Note: Cannot publish from running containers - stop or freeze first
Criar Serviços a partir de Imagens
# Spawn a new service from an image
./un image --spawn unsb-image-xxxx-xxxx-xxxx-xxxx \
--name new-service --ports 80,443
# Clone an image (creates a copy you own)
./un image --clone unsb-image-xxxx-xxxx-xxxx-xxxx
Proteção de Imagem
# Lock image to prevent accidental deletion
./un image --lock unsb-image-xxxx-xxxx-xxxx-xxxx
# Unlock image to allow deletion
./un image --unlock unsb-image-xxxx-xxxx-xxxx-xxxx
# Delete image (must be unlocked)
./un image --delete unsb-image-xxxx-xxxx-xxxx-xxxx
Visibilidade e Compartilhamento
# Set visibility level
./un image --visibility unsb-image-xxxx-xxxx-xxxx-xxxx private # owner only (default)
./un image --visibility unsb-image-xxxx-xxxx-xxxx-xxxx unlisted # can be shared
./un image --visibility unsb-image-xxxx-xxxx-xxxx-xxxx public # marketplace
# Share with specific user
./un image --grant unsb-image-xxxx-xxxx-xxxx-xxxx \
--key unsb-pk-friend-friend-friend-friend
# Revoke access
./un image --revoke unsb-image-xxxx-xxxx-xxxx-xxxx \
--key unsb-pk-friend-friend-friend-friend
# List who has access
./un image --trusted unsb-image-xxxx-xxxx-xxxx-xxxx
Transferir Propriedade
# Transfer image to another API key
./un image --transfer unsb-image-xxxx-xxxx-xxxx-xxxx \
--to unsb-pk-newowner-newowner-newowner-newowner
Referência de uso
Usage: ./un [options] <source_file>
./un session [options]
./un service [options]
./un snapshot [options]
./un image [options]
./un key
Commands:
(default) Execute source file in sandbox
session Open interactive shell/REPL session
service Manage persistent services
snapshot Manage container snapshots
image Manage container images (publish, share, transfer)
key Check API key validity and expiration
Options:
-e KEY=VALUE Set environment variable (can use multiple times)
-f FILE Add input file (can use multiple times)
-a Return and save artifacts from /tmp/artifacts/
-o DIR Output directory for artifacts (default: current dir)
-p KEY Public key (or set UNSANDBOX_PUBLIC_KEY env var)
-k KEY Secret key (or set UNSANDBOX_SECRET_KEY env var)
-n MODE Network mode: zerotrust (default) or semitrusted
-v N, --vcpu N vCPU count 1-8, each vCPU gets 2GB RAM (default: 1)
-y Skip confirmation for large uploads (>1GB)
-h Show this help
Authentication (priority order):
1. -p and -k flags (public and secret key)
2. UNSANDBOX_PUBLIC_KEY + UNSANDBOX_SECRET_KEY env vars
3. ~/.unsandbox/accounts.csv (format: public_key,secret_key per line)
Session options:
-s, --shell SHELL Shell/REPL to use (default: bash)
-l, --list List active sessions
--attach ID Reconnect to existing session (ID or container name)
--kill ID Terminate a session (ID or container name)
--freeze ID Freeze a session (requires --tmux/--screen)
--unfreeze ID Unfreeze a frozen session
--boost ID Boost session resources (2 vCPU, 4GB RAM)
--boost-vcpu N Specify vCPU count for boost (1-8)
--unboost ID Return to base resources
--audit Record full session for auditing
--tmux Enable session persistence with tmux (allows reconnect)
--screen Enable session persistence with screen (allows reconnect)
Service options:
--name NAME Service name (creates new service)
--ports PORTS Comma-separated ports (e.g., 80,443)
--domains DOMAINS Custom domains (e.g., example.com,www.example.com)
--type TYPE Service type: minecraft, mumble, teamspeak, source, tcp, udp
--bootstrap CMD Bootstrap command/file/URL to run on startup
-f FILE Upload file to /tmp/ (can use multiple times)
-l, --list List all services
--info ID Get service details
--tail ID Get last 9000 lines of bootstrap logs
--logs ID Get all bootstrap logs
--freeze ID Freeze a service
--unfreeze ID Unfreeze a service
--auto-unfreeze ID Enable auto-wake on HTTP request
--no-auto-unfreeze ID Disable auto-wake on HTTP request
--show-freeze-page ID Show HTML payment page when frozen (default)
--no-show-freeze-page ID Return JSON error when frozen
--destroy ID Destroy a service
--redeploy ID Re-run bootstrap script (requires --bootstrap)
--execute ID CMD Run a command in a running service
--dump-bootstrap ID [FILE] Dump bootstrap script (for migrations)
--snapshot ID Create snapshot of session or service
--snapshot-name User-friendly name for snapshot
--hot Create snapshot without pausing (may be inconsistent)
--restore ID Restore session/service from snapshot ID
Snapshot options:
-l, --list List all snapshots
--info ID Get snapshot details
--delete ID Delete a snapshot permanently
Image options:
-l, --list [owned|shared|public] List images (all, owned, shared, or public)
--info ID Get image details
--publish-service ID Publish image from stopped/frozen service
--publish-snapshot ID Publish image from snapshot
--name NAME Name for published image
--description DESC Description for published image
--delete ID Delete image (must be unlocked)
--clone ID Clone image (creates copy you own)
--spawn ID Create service from image (requires --name)
--lock ID Lock image to prevent deletion
--unlock ID Unlock image to allow deletion
--visibility ID LEVEL Set visibility (private|unlisted|public)
--grant ID --key KEY Grant access to another API key
--revoke ID --key KEY Revoke access from API key
--transfer ID --to KEY Transfer ownership to API key
--trusted ID List API keys with access
Key options:
(no options) Check API key validity
--extend Open portal to extend an expired key
Examples:
./un script.py # execute Python script
./un -e DEBUG=1 script.py # with environment variable
./un -f data.csv process.py # with input file
./un -a -o ./bin main.c # save compiled artifacts
./un -v 4 heavy.py # with 4 vCPUs, 8GB RAM
./un session # interactive bash session
./un session --tmux # bash with reconnect support
./un session --list # list active sessions
./un session --attach unsb-vm-12345 # reconnect to session
./un session --kill unsb-vm-12345 # terminate a session
./un session --freeze unsb-vm-12345 # freeze session
./un session --unfreeze unsb-vm-12345 # unfreeze session
./un session --boost unsb-vm-12345 # boost resources
./un session --unboost unsb-vm-12345 # return to base
./un session --shell python3 # Python REPL
./un session --shell node # Node.js REPL
./un session -n semitrusted # session with network access
./un session --audit -o ./logs # record session for auditing
./un service --name web --ports 80 # create web service
./un service --list # list all services
./un service --logs abc123 # view bootstrap logs
./un key # check API key
./un key --extend # extend expired key
./un snapshot --list # list all snapshots
./un session --snapshot unsb-vm-123 # snapshot a session
./un service --snapshot abc123 # snapshot a service
./un session --restore unsb-snapshot-xxxx # restore from snapshot
./un image --list # list all images
./un image --list owned # list your images
./un image --publish-service abc # publish image from service
./un image --spawn img123 --name x # create service from image
./un image --grant img --key pk # share image with user
CLI Inception
O UN CLI foi implementado em 42 linguagens de programação, demonstrando que a API do unsandbox pode ser acessada de praticamente qualquer ambiente.
Ver Todas as 42 Implementações →
Licença
DOMÍNIO PÚBLICO - SEM LICENÇA, SEM GARANTIA
Este é software gratuito de domínio público para o bem público de um permacomputador hospedado
em permacomputer.com - um computador sempre ativo pelo povo, para o povo. Um que é
durável, fácil de reparar e distribuído como água da torneira para inteligência de
aprendizado de máquina.
O permacomputador é infraestrutura de propriedade comunitária otimizada em torno de quatro valores:
VERDADE - Primeiros princípios, matemática & ciência, código aberto distribuído livremente
LIBERDADE - Parcerias voluntárias, liberdade da tirania e controle corporativo
HARMONIA - Desperdício mínimo, sistemas auto-renováveis com diversas conexões prósperas
AMOR - Seja você mesmo sem ferir os outros, cooperação através da lei natural
Este software contribui para essa visão ao permitir a execução de código em mais de 42
linguagens de programação através de uma interface unificada, acessível a todos. Código são
sementes que brotam em qualquer tecnologia abandonada.
Saiba mais: https://www.permacomputer.com
Qualquer pessoa é livre para copiar, modificar, publicar, usar, compilar, vender ou distribuir
este software, seja em forma de código-fonte ou como binário compilado, para qualquer propósito,
comercial ou não comercial, e por qualquer meio.
SEM GARANTIA. O SOFTWARE É FORNECIDO "COMO ESTÁ" SEM GARANTIA DE QUALQUER TIPO.
Dito isso, a camada de membrana digital do nosso permacomputador executa continuamente testes
unitários, de integração e funcionais em todo o seu próprio software - com nosso permacomputador
monitorando a si mesmo, reparando a si mesmo, com orientação humana mínima no ciclo.
Nossos agentes fazem o seu melhor.
Copyright 2025 TimeHexOn & foxhop & russell@unturf
https://www.timehexon.com
https://www.foxhop.net
https://www.unturf.com/software