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 — V
# Download + setup
curl -O https://git.unturf.com/engineering/unturf/un-inception/-/raw/main/clients/v/sync/src/un.v
export UNSANDBOX_PUBLIC_KEY="unsb-pk-xxxx-xxxx-xxxx-xxxx"
export UNSANDBOX_SECRET_KEY="unsb-sk-xxxxx-xxxxx-xxxxx-xxxxx"
# Run code
./un script.v
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 V existente:
curl -O https://git.unturf.com/engineering/unturf/un-inception/-/raw/main/clients/v/sync/src/un.v
# 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 V app:
import un
fn main() {
result := un.execute_code('v', "println('Hello from V running on unsandbox!')")
println(result['stdout']) // Hello from V running on unsandbox!
}
v run main.v
54d79cd0c300799571bdfe7494cccbc3
SHA256: 48bb0d962a59891ab5338e135ece7f5752c40f95a1c418e69af25a6d62433cc2
// 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
// UN CLI - V Implementation (using curl subprocess for simplicity)
// Compile: v un.v -o un_v
// Usage:
// un_v script.py
// un_v -e KEY=VALUE script.py
// un_v session --list
// un_v service --name web --ports 8080
import os
const api_base = 'https://api.unsandbox.com'
const portal_base = 'https://unsandbox.com'
const max_env_content_size = 65536
const languages_cache_ttl = 3600 // 1 hour in seconds
const blue = '\x1b[34m'
const red = '\x1b[31m'
const green = '\x1b[32m'
const yellow = '\x1b[33m'
const reset = '\x1b[0m'
fn detect_language(filename string) !string {
ext := os.file_ext(filename)
lang_map := {
'.py': 'python'
'.js': 'javascript'
'.ts': 'typescript'
'.go': 'go'
'.rs': 'rust'
'.c': 'c'
'.cpp': 'cpp'
'.d': 'd'
'.zig': 'zig'
'.nim': 'nim'
'.v': 'v'
'.rb': 'ruby'
'.php': 'php'
'.sh': 'bash'
}
if lang := lang_map[ext] {
return lang
}
return error('Cannot detect language from file extension')
}
fn escape_json(s string) string {
mut result := ''
for c in s {
match c {
`"` { result += '\\"' }
`\\` { result += '\\\\' }
`\n` { result += '\\n' }
`\r` { result += '\\r' }
`\t` { result += '\\t' }
else { result += c.ascii_str() }
}
}
return result
}
fn base64_encode_file(filename string) string {
cmd := "base64 -w0 '${filename}'"
result := os.execute(cmd)
return result.output.trim_space()
}
fn build_input_files_json(files []string) string {
if files.len == 0 {
return ''
}
mut entries := []string{}
for f in files {
basename := os.file_name(f)
content := base64_encode_file(f)
entries << '{"filename":"${basename}","content":"${content}"}'
}
return ',"input_files":[' + entries.join(',') + ']'
}
fn exec_curl(cmd string) string {
result := os.execute(cmd)
output := result.output
// Check for timestamp authentication errors
if output.contains('timestamp') &&
(output.contains('401') || output.contains('expired') || output.contains('invalid')) {
eprintln('${red}Error: Request timestamp expired (must be within 5 minutes of server time)${reset}')
eprintln('${yellow}Your computer\'s clock may have drifted.${reset}')
eprintln('Check your system time and sync with NTP if needed:')
eprintln(' Linux: sudo ntpdate -s time.nist.gov')
eprintln(' macOS: sudo sntp -sS time.apple.com')
eprintln(' Windows: w32tm /resync')
exit(1)
}
return output
}
fn extract_json_string(json string, key string) string {
search := '"${key}":"'
start_idx := json.index(search) or { return '' }
start := start_idx + search.len
mut end := start
for end < json.len {
if json[end] == `"` && (end == 0 || json[end - 1] != `\\`) {
break
}
end++
}
if end > start {
raw := json[start..end]
// Unescape JSON string
return raw.replace('\\n', '\n')
.replace('\\r', '\r')
.replace('\\t', '\t')
.replace('\\"', '"')
.replace('\\\\', '\\')
}
return ''
}
fn handle_sudo_challenge(response_data string, meth string, endpoint string, body string, public_key string, secret_key string) bool {
challenge_id := extract_json_string(response_data, 'challenge_id')
eprintln('${yellow}Confirmation required. Check your email for a one-time code.${reset}')
eprint('Enter OTP: ')
// Read OTP from stdin
mut otp := ''
mut buf := []u8{len: 64}
n := C.read(0, buf.data, buf.len)
if n > 0 {
otp = buf[..n].bytestr().trim_space()
}
if otp.len == 0 {
eprintln('${red}Error: Operation cancelled${reset}')
return false
}
// Build sudo headers
mut otp_header := "-H 'X-Sudo-OTP: ${otp}'"
mut challenge_header := ''
if challenge_id != '' {
challenge_header = " -H 'X-Sudo-Challenge: ${challenge_id}'"
}
// Retry with sudo headers
mut cmd := ''
if meth == 'DELETE' {
cmd = "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:DELETE:${endpoint}:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -o /dev/null -w '%{http_code}' -X DELETE '${api_base}${endpoint}' -H 'Authorization: Bearer ${public_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" ${otp_header}${challenge_header}"
} else if meth == 'POST' {
cmd = "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:${endpoint}:${body}\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -o /dev/null -w '%{http_code}' -X POST '${api_base}${endpoint}' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${public_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" ${otp_header}${challenge_header} -d '${body}'"
} else {
return false
}
result := os.execute(cmd)
status := result.output.trim_space().int()
if status >= 200 && status < 300 {
println('${green}Operation completed successfully${reset}')
return true
}
eprintln('${red}Error: HTTP ${status}${reset}')
return false
}
fn exec_curl_delete_with_sudo(endpoint string, public_key string, secret_key string) int {
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:DELETE:${endpoint}:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -w '\\n%{http_code}' -X DELETE '${api_base}${endpoint}' -H 'Authorization: Bearer ${public_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
result := os.execute(cmd)
output := result.output.trim_space()
// Parse response body and status code
lines := output.split('\n')
if lines.len < 1 {
return 500
}
status_line := lines[lines.len - 1]
response_body := if lines.len > 1 { lines[..lines.len - 1].join('\n') } else { '' }
status := status_line.int()
if status == 428 {
if handle_sudo_challenge(response_body, 'DELETE', endpoint, '', public_key, secret_key) {
return 200
}
return 428
}
return status
}
fn exec_curl_post_with_sudo(endpoint string, body string, public_key string, secret_key string) int {
cmd := "BODY='${body}'; TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:${endpoint}:\$BODY\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -w '\\n%{http_code}' -X POST '${api_base}${endpoint}' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${public_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" -d \"\$BODY\""
result := os.execute(cmd)
output := result.output.trim_space()
// Parse response body and status code
lines := output.split('\n')
if lines.len < 1 {
return 500
}
status_line := lines[lines.len - 1]
response_body := if lines.len > 1 { lines[..lines.len - 1].join('\n') } else { '' }
status := status_line.int()
if status == 428 {
if handle_sudo_challenge(response_body, 'POST', endpoint, body, public_key, secret_key) {
return 200
}
return 428
}
return status
}
fn read_env_file(filename string) string {
content := os.read_file(filename) or {
eprintln('${red}Error: Cannot read env file: ${filename}${reset}')
return ''
}
return content
}
fn build_env_content(envs []string, env_file string) string {
mut result := ''
// Add -e flags
for env in envs {
result += env + '\n'
}
// Add content from env file
if env_file != '' {
file_content := read_env_file(env_file)
for line in file_content.split('\n') {
trimmed := line.trim_space()
if trimmed.len == 0 || trimmed.starts_with('#') {
continue
}
result += trimmed + '\n'
}
}
return result
}
fn exec_curl_put(endpoint string, body string, public_key string, secret_key string) bool {
// Write body to temp file to avoid shell escaping issues
body_file := '/tmp/unsandbox_env_body.txt'
os.write_file(body_file, body) or {
eprintln('${red}Error: Cannot write temp file${reset}')
return false
}
defer {
os.rm(body_file) or {}
}
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:PUT:${endpoint}:${body}\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X PUT '${api_base}${endpoint}' -H 'Content-Type: text/plain' -H 'Authorization: Bearer ${public_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" --data-binary @${body_file}"
result := os.execute(cmd)
return result.exit_code == 0
}
fn service_env_set(service_id string, content string, public_key string, secret_key string) bool {
endpoint := '/services/${service_id}/env'
return exec_curl_put(endpoint, content, public_key, secret_key)
}
fn cmd_service_env(action string, target string, svc_envs []string, svc_env_file string, api_key string) {
pub_key := get_public_key()
secret_key := get_secret_key()
match action {
'status' {
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:GET:/services/${target}/env:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X GET '${api_base}/services/${target}/env' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
println(exec_curl(cmd))
}
'set' {
if svc_envs.len == 0 && svc_env_file == '' {
eprintln('${red}Error: No environment variables specified. Use -e KEY=VALUE or --env-file FILE${reset}')
return
}
content := build_env_content(svc_envs, svc_env_file)
if content.len > max_env_content_size {
eprintln('${red}Error: Environment content exceeds 64KB limit${reset}')
return
}
if service_env_set(target, content, pub_key, secret_key) {
println('${green}Vault updated for service ${target}${reset}')
}
}
'export' {
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:/services/${target}/env/export:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X POST '${api_base}/services/${target}/env/export' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
println(exec_curl(cmd))
}
'delete' {
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:DELETE:/services/${target}/env:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X DELETE '${api_base}/services/${target}/env' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
exec_curl(cmd)
println('${green}Vault deleted for service ${target}${reset}')
}
else {
eprintln('${red}Error: Unknown env action: ${action}${reset}')
eprintln('Usage: un service env <status|set|export|delete> <service_id>')
}
}
}
fn cmd_key(extend bool, api_key string) {
pub_key := get_public_key()
secret_key := get_secret_key()
body := '{}'
cmd := "BODY='${body}'; TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:/keys/validate:\$BODY\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X POST '${portal_base}/keys/validate' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" -d \"\$BODY\""
result := exec_curl(cmd)
public_key := extract_json_string(result, 'public_key')
tier := extract_json_string(result, 'tier')
status := extract_json_string(result, 'status')
expires_at := extract_json_string(result, 'expires_at')
time_remaining := extract_json_string(result, 'time_remaining')
rate_limit := extract_json_string(result, 'rate_limit')
burst := extract_json_string(result, 'burst')
concurrency := extract_json_string(result, 'concurrency')
expired := extract_json_string(result, 'expired')
if extend && public_key != '' {
url := '${portal_base}/keys/extend?pk=${public_key}'
println('${blue}Opening browser to extend key...${reset}')
// Try xdg-open (Linux), open (macOS), or start (Windows)
mut opened := false
xdg_result := os.execute('xdg-open "${url}"')
if xdg_result.exit_code == 0 {
opened = true
}
if !opened {
mac_result := os.execute('open "${url}"')
if mac_result.exit_code == 0 {
opened = true
}
}
if !opened {
win_result := os.execute('cmd /c start "${url}"')
if win_result.exit_code != 0 {
eprintln('${red}Error: Could not open browser${reset}')
}
}
return
}
if expired == 'true' {
println('${red}Expired${reset}')
println('Public Key: ${public_key}')
println('Tier: ${tier}')
if expires_at != '' {
println('Expired: ${expires_at}')
}
println('${yellow}To renew: Visit https://unsandbox.com/keys/extend${reset}')
exit(1)
}
// Valid key
println('${green}Valid${reset}')
println('Public Key: ${public_key}')
if tier != '' {
println('Tier: ${tier}')
}
if status != '' {
println('Status: ${status}')
}
if expires_at != '' {
println('Expires: ${expires_at}')
}
if time_remaining != '' {
println('Time Remaining: ${time_remaining}')
}
if rate_limit != '' {
println('Rate Limit: ${rate_limit}')
}
if burst != '' {
println('Burst: ${burst}')
}
if concurrency != '' {
println('Concurrency: ${concurrency}')
}
}
fn cmd_execute(source_file string, envs []string, artifacts bool, network string, vcpu int, api_key string) {
lang := detect_language(source_file) or {
eprintln('${red}Error: ${err}${reset}')
exit(1)
}
code := os.read_file(source_file) or {
eprintln('${red}Error reading file: ${err}${reset}')
exit(1)
}
mut json := '{"language":"${lang}","code":"${escape_json(code)}"'
if envs.len > 0 {
json += ',"env":{'
for i, e in envs {
parts := e.split_nth('=', 2)
if parts.len == 2 {
if i > 0 {
json += ','
}
json += '"${parts[0]}":"${escape_json(parts[1])}"'
}
}
json += '}'
}
if artifacts {
json += ',"return_artifacts":true'
}
if network != '' {
json += ',"network":"${network}"'
}
if vcpu > 0 {
json += ',"vcpu":${vcpu}'
}
json += '}'
pub_key := get_public_key()
secret_key := get_secret_key()
cmd := "BODY='${json}'; TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:/execute:\$BODY\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X POST '${api_base}/execute' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" -d \"\$BODY\""
println(exec_curl(cmd))
}
fn cmd_session(list bool, kill string, shell string, network string, vcpu int, tmux bool, screen bool, input_files []string, api_key string) {
pub_key := get_public_key()
secret_key := get_secret_key()
if list {
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:GET:/sessions:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X GET '${api_base}/sessions' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
println(exec_curl(cmd))
return
}
if kill != '' {
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:DELETE:/sessions/${kill}:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X DELETE '${api_base}/sessions/${kill}' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
exec_curl(cmd)
println('${green}Session terminated: ${kill}${reset}')
return
}
sh := if shell != '' { shell } else { 'bash' }
mut json := '{"shell":"${sh}"'
if network != '' {
json += ',"network":"${network}"'
}
if vcpu > 0 {
json += ',"vcpu":${vcpu}'
}
if tmux {
json += ',"persistence":"tmux"'
}
if screen {
json += ',"persistence":"screen"'
}
json += build_input_files_json(input_files)
json += '}'
println('${yellow}Creating session...${reset}')
cmd := "BODY='${json}'; TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:/sessions:\$BODY\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X POST '${api_base}/sessions' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" -d \"\$BODY\""
println(exec_curl(cmd))
}
fn cmd_service(name string, ports string, service_type string, bootstrap string, bootstrap_file string, list bool, info string, logs string, tail string, sleep string, wake string, destroy string, resize string, execute string, command string, dump_bootstrap string, dump_file string, network string, vcpu int, input_files []string, svc_envs []string, svc_env_file string, unfreeze_on_demand bool, set_unfreeze_on_demand_id string, set_unfreeze_on_demand_enabled string, api_key string) {
pub_key := get_public_key()
secret_key := get_secret_key()
if list {
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:GET:/services:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X GET '${api_base}/services' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
println(exec_curl(cmd))
return
}
if info != '' {
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:GET:/services/${info}:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X GET '${api_base}/services/${info}' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
println(exec_curl(cmd))
return
}
if logs != '' {
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:GET:/services/${logs}/logs:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X GET '${api_base}/services/${logs}/logs' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
print(exec_curl(cmd))
return
}
if tail != '' {
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:GET:/services/${tail}/logs:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X GET '${api_base}/services/${tail}/logs?lines=9000' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
print(exec_curl(cmd))
return
}
if sleep != '' {
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:/services/${sleep}/freeze:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X POST '${api_base}/services/${sleep}/freeze' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
exec_curl(cmd)
println('${green}Service frozen: ${sleep}${reset}')
return
}
if wake != '' {
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:/services/${wake}/unfreeze:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X POST '${api_base}/services/${wake}/unfreeze' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
exec_curl(cmd)
println('${green}Service unfreezing: ${wake}${reset}')
return
}
if destroy != '' {
endpoint := '/services/${destroy}'
status := exec_curl_delete_with_sudo(endpoint, pub_key, secret_key)
if status >= 200 && status < 300 {
println('${green}Service destroyed: ${destroy}${reset}')
} else if status != 428 {
eprintln('${red}Error: Failed to destroy service${reset}')
exit(1)
}
return
}
if resize != '' {
if vcpu < 1 || vcpu > 8 {
eprintln('${red}Error: --resize requires --vcpu N (1-8)${reset}')
exit(1)
}
json := '{"vcpu":${vcpu}}'
cmd := "BODY='${json}'; TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:PATCH:/services/${resize}:\$BODY\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X PATCH '${api_base}/services/${resize}' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" -d \"\$BODY\""
exec_curl(cmd)
ram := vcpu * 2
println('${green}Service resized to ${vcpu} vCPU, ${ram} GB RAM${reset}')
return
}
if execute != '' {
json := '{"command":"${escape_json(command)}"}'
cmd := "BODY='${json}'; TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:/services/${execute}/execute:\$BODY\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X POST '${api_base}/services/${execute}/execute' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" -d \"\$BODY\""
result := exec_curl(cmd)
stdout_str := extract_json_string(result, 'stdout')
stderr_str := extract_json_string(result, 'stderr')
if stdout_str != '' {
print(stdout_str)
}
if stderr_str != '' {
eprint(stderr_str)
}
return
}
// Handle set_unfreeze_on_demand
if set_unfreeze_on_demand_id != '' {
enabled := set_unfreeze_on_demand_enabled == 'true' || set_unfreeze_on_demand_enabled == '1'
enabled_str := if enabled { 'true' } else { 'false' }
json := '{"unfreeze_on_demand":${enabled_str}}'
cmd := "BODY='${json}'; TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:PATCH:/services/${set_unfreeze_on_demand_id}:\$BODY\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X PATCH '${api_base}/services/${set_unfreeze_on_demand_id}' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" -d \"\$BODY\""
exec_curl(cmd)
if enabled {
println('${green}Unfreeze-on-demand enabled for service ${set_unfreeze_on_demand_id}${reset}')
} else {
println('${green}Unfreeze-on-demand disabled for service ${set_unfreeze_on_demand_id}${reset}')
}
return
}
if dump_bootstrap != '' {
eprintln('Fetching bootstrap script from ${dump_bootstrap}...')
json := '{"command":"cat /tmp/bootstrap.sh"}'
cmd := "BODY='${json}'; TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:/services/${dump_bootstrap}/execute:\$BODY\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X POST '${api_base}/services/${dump_bootstrap}/execute' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" -d \"\$BODY\""
result := exec_curl(cmd)
bootstrap_script := extract_json_string(result, 'stdout')
if bootstrap_script != '' {
if dump_file != '' {
os.write_file(dump_file, bootstrap_script) or {
eprintln('${red}Error: Could not write to ${dump_file}: ${err}${reset}')
exit(1)
}
os.chmod(dump_file, 0o755) or {}
println('Bootstrap saved to ${dump_file}')
} else {
print(bootstrap_script)
}
} else {
eprintln('${red}Error: Failed to fetch bootstrap (service not running or no bootstrap file)${reset}')
exit(1)
}
return
}
if name != '' {
mut json := '{"name":"${name}"'
if ports != '' {
json += ',"ports":[${ports}]'
}
if service_type != '' {
json += ',"service_type":"${service_type}"'
}
if bootstrap != '' {
json += ',"bootstrap":"${escape_json(bootstrap)}"'
}
if bootstrap_file != '' {
if os.exists(bootstrap_file) {
boot_code := os.read_file(bootstrap_file) or {
eprintln('${red}Error: Could not read bootstrap file: ${bootstrap_file}${reset}')
exit(1)
}
json += ',"bootstrap_content":"${escape_json(boot_code)}"'
} else {
eprintln('${red}Error: Bootstrap file not found: ${bootstrap_file}${reset}')
exit(1)
}
}
if network != '' {
json += ',"network":"${network}"'
}
if vcpu > 0 {
json += ',"vcpu":${vcpu}'
}
if unfreeze_on_demand {
json += ',"unfreeze_on_demand":true'
}
json += build_input_files_json(input_files)
json += '}'
println('${yellow}Creating service...${reset}')
cmd := "BODY='${json}'; TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:/services:\$BODY\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X POST '${api_base}/services' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" -d \"\$BODY\""
result := exec_curl(cmd)
println(result)
// Auto-set vault if -e or --env-file provided
if svc_envs.len > 0 || svc_env_file != '' {
service_id := extract_json_string(result, 'service_id')
if service_id != '' {
env_content := build_env_content(svc_envs, svc_env_file)
if env_content.len > 0 {
if service_env_set(service_id, env_content, pub_key, secret_key) {
println('${green}Vault configured for service ${service_id}${reset}')
}
}
}
}
return
}
eprintln('${red}Error: Specify --name to create a service${reset}')
exit(1)
}
// Load a row from an accounts.csv file (format: public_key,secret_key per line).
// Lines starting with '#' and blank lines are skipped. Returns the Nth data row.
fn load_accounts_csv(path string, index int) ?(string, string) {
content := os.read_file(path) or { return none }
lines := content.split('\n')
mut row := 0
for raw_line in lines {
line := raw_line.trim_space()
if line == '' || line.starts_with('#') {
continue
}
if row == index {
idx := line.index(',') or { return none }
return line[..idx], line[idx + 1..]
}
row++
}
return none
}
fn get_public_key() string {
pub_key := os.getenv('UNSANDBOX_PUBLIC_KEY')
if pub_key != '' {
return pub_key
}
api_key := os.getenv('UNSANDBOX_API_KEY')
if api_key != '' {
return api_key
}
eprintln('${red}Error: UNSANDBOX_PUBLIC_KEY or UNSANDBOX_API_KEY environment variable not set${reset}')
exit(1)
}
fn get_secret_key() string {
sec_key := os.getenv('UNSANDBOX_SECRET_KEY')
if sec_key != '' {
return sec_key
}
api_key := os.getenv('UNSANDBOX_API_KEY')
if api_key != '' {
return api_key
}
return ''
}
fn cmd_snapshot(list bool, info string, session string, service string, restore string, delete string, lock string, unlock string, clone string, clone_type string, name string, ports string, hot bool, api_key string) {
pub_key := get_public_key()
secret_key := get_secret_key()
if list {
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:GET:/snapshots:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X GET '${api_base}/snapshots' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
println(exec_curl(cmd))
return
}
if info != '' {
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:GET:/snapshots/${info}:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X GET '${api_base}/snapshots/${info}' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
println(exec_curl(cmd))
return
}
if session != '' {
mut json := '{'
mut has_content := false
if name != '' {
json += '"name":"${name}"'
has_content = true
}
if hot {
if has_content { json += ',' }
json += '"hot":true'
}
json += '}'
cmd := "BODY='${json}'; TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:/sessions/${session}/snapshot:\$BODY\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X POST '${api_base}/sessions/${session}/snapshot' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" -d \"\$BODY\""
result := exec_curl(cmd)
println('${green}Snapshot created${reset}')
println(result)
return
}
if service != '' {
mut json := '{'
mut has_content := false
if name != '' {
json += '"name":"${name}"'
has_content = true
}
if hot {
if has_content { json += ',' }
json += '"hot":true'
}
json += '}'
cmd := "BODY='${json}'; TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:/services/${service}/snapshot:\$BODY\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X POST '${api_base}/services/${service}/snapshot' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" -d \"\$BODY\""
result := exec_curl(cmd)
println('${green}Snapshot created${reset}')
println(result)
return
}
if restore != '' {
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:/snapshots/${restore}/restore:{}\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X POST '${api_base}/snapshots/${restore}/restore' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" -d '{}'"
result := exec_curl(cmd)
println('${green}Snapshot restored${reset}')
println(result)
return
}
if delete != '' {
endpoint := '/snapshots/${delete}'
status := exec_curl_delete_with_sudo(endpoint, pub_key, secret_key)
if status >= 200 && status < 300 {
println('${green}Snapshot deleted: ${delete}${reset}')
} else if status != 428 {
eprintln('${red}Error: Failed to delete snapshot${reset}')
exit(1)
}
return
}
if lock != '' {
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:/snapshots/${lock}/lock:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X POST '${api_base}/snapshots/${lock}/lock' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
exec_curl(cmd)
println('${green}Snapshot locked: ${lock}${reset}')
return
}
if unlock != '' {
endpoint := '/snapshots/${unlock}/unlock'
status := exec_curl_post_with_sudo(endpoint, '{}', pub_key, secret_key)
if status >= 200 && status < 300 {
println('${green}Snapshot unlocked: ${unlock}${reset}')
} else if status != 428 {
eprintln('${red}Error: Failed to unlock snapshot${reset}')
exit(1)
}
return
}
if clone != '' {
ct := if clone_type != '' { clone_type } else { 'session' }
mut json := '{"clone_type":"${ct}"'
if name != '' { json += ',"name":"${name}"' }
if ports != '' { json += ',"ports":[${ports}]' }
json += '}'
cmd := "BODY='${json}'; TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:/snapshots/${clone}/clone:\$BODY\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X POST '${api_base}/snapshots/${clone}/clone' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" -d \"\$BODY\""
result := exec_curl(cmd)
println('${green}Snapshot cloned${reset}')
println(result)
return
}
eprintln('${red}Error: No snapshot action specified. Use --list, --info, --session, --service, --restore, --delete, --lock, --unlock, or --clone${reset}')
exit(1)
}
fn cmd_logs(source string, lines int, since string, grep string, follow bool, api_key string) {
pub_key := get_public_key()
secret_key := get_secret_key()
src := if source != '' { source } else { 'all' }
ln := if lines > 0 { lines } else { 100 }
sn := if since != '' { since } else { '1h' }
if follow {
mut endpoint := '/paas/logs/stream?source=${src}'
if grep != '' { endpoint += '&grep=${grep}' }
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:GET:${endpoint}:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -N -X GET '${portal_base}${endpoint}' -H 'Accept: text/event-stream' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
println(exec_curl(cmd))
} else {
mut endpoint := '/paas/logs?source=${src}&lines=${ln}&since=${sn}'
if grep != '' { endpoint += '&grep=${grep}' }
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:GET:${endpoint}:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X GET '${portal_base}${endpoint}' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
println(exec_curl(cmd))
}
}
fn cmd_health() {
cmd := "curl -s -X GET '${api_base}/health'"
result := os.execute(cmd)
if result.output.contains('"status":"healthy"') || result.output.contains('"ok":true') {
println('${green}API is healthy${reset}')
} else {
println('${red}API may be unhealthy${reset}')
}
println(result.output)
}
fn cmd_version() {
println('un.v version 1.0.0')
println('API: ${api_base}')
println('Portal: ${portal_base}')
}
fn cmd_image(list bool, info string, delete string, lock string, unlock string, publish string, source_type string, visibility_id string, visibility_mode string, spawn string, clone string, name string, ports string, grant string, revoke string, trusted string, trusted_key string, transfer string, to_key string, api_key string) {
pub_key := get_public_key()
secret_key := get_secret_key()
if list {
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:GET:/images:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X GET '${api_base}/images' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
println(exec_curl(cmd))
return
}
if info != '' {
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:GET:/images/${info}:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X GET '${api_base}/images/${info}' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
println(exec_curl(cmd))
return
}
if delete != '' {
endpoint := '/images/${delete}'
status := exec_curl_delete_with_sudo(endpoint, pub_key, secret_key)
if status >= 200 && status < 300 {
println('${green}Image deleted: ${delete}${reset}')
} else if status != 428 {
eprintln('${red}Error: Failed to delete image${reset}')
exit(1)
}
return
}
if lock != '' {
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:/images/${lock}/lock:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X POST '${api_base}/images/${lock}/lock' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
exec_curl(cmd)
println('${green}Image locked: ${lock}${reset}')
return
}
if unlock != '' {
endpoint := '/images/${unlock}/unlock'
status := exec_curl_post_with_sudo(endpoint, '{}', pub_key, secret_key)
if status >= 200 && status < 300 {
println('${green}Image unlocked: ${unlock}${reset}')
} else if status != 428 {
eprintln('${red}Error: Failed to unlock image${reset}')
exit(1)
}
return
}
if publish != '' {
if source_type == '' {
eprintln('${red}Error: --publish requires --source-type (service or snapshot)${reset}')
exit(1)
}
mut json := '{"source_type":"${source_type}","source_id":"${publish}"'
if name != '' {
json += ',"name":"${name}"'
}
json += '}'
cmd := "BODY='${json}'; TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:/images/publish:\$BODY\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X POST '${api_base}/images/publish' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" -d \"\$BODY\""
result := exec_curl(cmd)
println(result)
return
}
if visibility_id != '' {
if visibility_mode == '' {
eprintln('${red}Error: --visibility requires a mode (private, unlisted, or public)${reset}')
exit(1)
}
json := '{"visibility":"${visibility_mode}"}'
cmd := "BODY='${json}'; TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:/images/${visibility_id}/visibility:\$BODY\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X POST '${api_base}/images/${visibility_id}/visibility' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" -d \"\$BODY\""
exec_curl(cmd)
println('${green}Image visibility set to ${visibility_mode}: ${visibility_id}${reset}')
return
}
if spawn != '' {
mut json := '{"name":"${name}"'
if ports != '' {
json += ',"ports":[${ports}]'
}
json += '}'
cmd := "BODY='${json}'; TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:/images/${spawn}/spawn:\$BODY\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X POST '${api_base}/images/${spawn}/spawn' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" -d \"\$BODY\""
result := exec_curl(cmd)
println(result)
return
}
if clone != '' {
mut json := '{'
if name != '' {
json += '"name":"${name}"'
}
json += '}'
cmd := "BODY='${json}'; TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:/images/${clone}/clone:\$BODY\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X POST '${api_base}/images/${clone}/clone' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" -d \"\$BODY\""
result := exec_curl(cmd)
println(result)
return
}
if grant != '' {
if trusted_key == '' {
eprintln('${red}Error: --grant requires --trusted-key${reset}')
exit(1)
}
json := '{"trusted_api_key":"${trusted_key}"}'
cmd := "BODY='${json}'; TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:/images/${grant}/grant:\$BODY\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X POST '${api_base}/images/${grant}/grant' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" -d \"\$BODY\""
exec_curl(cmd)
println('${green}Access granted to ${trusted_key}${reset}')
return
}
if revoke != '' {
if trusted_key == '' {
eprintln('${red}Error: --revoke requires --trusted-key${reset}')
exit(1)
}
json := '{"trusted_api_key":"${trusted_key}"}'
cmd := "BODY='${json}'; TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:POST:/images/${revoke}/revoke:\$BODY\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X POST '${api_base}/images/${revoke}/revoke' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" -d \"\$BODY\""
exec_curl(cmd)
println('${green}Access revoked from ${trusted_key}${reset}')
return
}
if trusted != '' {
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:GET:/images/${trusted}/trusted:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X GET '${api_base}/images/${trusted}/trusted' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
println(exec_curl(cmd))
return
}
if transfer != '' {
if to_key == '' {
eprintln('${red}Error: --transfer requires --to-key${reset}')
exit(1)
}
json := '{"to_api_key":"${to_key}"}'
endpoint := '/images/${transfer}/transfer'
status := exec_curl_post_with_sudo(endpoint, json, pub_key, secret_key)
if status >= 200 && status < 300 {
println('${green}Image transferred to ${to_key}${reset}')
} else if status != 428 {
eprintln('${red}Error: Failed to transfer image${reset}')
exit(1)
}
return
}
eprintln('${red}Error: No image action specified. Use --list, --info, --delete, --publish, --grant, --revoke, --trusted, --transfer, etc.${reset}')
exit(1)
}
fn get_languages_cache_path() string {
home := os.getenv('HOME')
cache_dir := '${home}/.unsandbox'
if !os.exists(cache_dir) {
os.mkdir(cache_dir) or {}
}
return '${cache_dir}/languages.json'
}
fn get_cached_languages() ?[]string {
cache_path := get_languages_cache_path()
if !os.exists(cache_path) {
return none
}
content := os.read_file(cache_path) or { return none }
// Extract timestamp
timestamp_str := extract_json_string(content, 'timestamp')
if timestamp_str == '' {
return none
}
// Parse timestamp (it's stored as a number, not a string)
// Look for "timestamp": followed by digits
ts_search := '"timestamp":'
ts_idx := content.index(ts_search) or { return none }
ts_start := ts_idx + ts_search.len
mut ts_end := ts_start
for ts_end < content.len && (content[ts_end].is_digit() || content[ts_end] == ` `) {
ts_end++
}
timestamp_num := content[ts_start..ts_end].trim_space()
cache_time := timestamp_num.i64()
// Get current time
current_time_cmd := 'date +%s'
current_time_result := os.execute(current_time_cmd)
current_time := current_time_result.output.trim_space().i64()
// Check if cache is still valid
if current_time - cache_time >= languages_cache_ttl {
return none
}
// Extract languages array
start_idx := content.index('"languages":[') or { return none }
arr_start := content.index_after('[', start_idx)
if arr_start < 0 {
return none
}
arr_end := content.index_after(']', arr_start)
if arr_end < 0 {
return none
}
arr_content := content[arr_start + 1..arr_end]
mut languages := []string{}
for item in arr_content.split(',') {
lang := item.trim_space().trim('"')
if lang.len > 0 {
languages << lang
}
}
return languages
}
fn save_languages_cache(languages []string) {
cache_path := get_languages_cache_path()
// Get current timestamp
timestamp_cmd := 'date +%s'
timestamp_result := os.execute(timestamp_cmd)
timestamp := timestamp_result.output.trim_space()
// Build JSON array
mut lang_json := '['
for i, lang in languages {
if i > 0 {
lang_json += ','
}
lang_json += '"${lang}"'
}
lang_json += ']'
cache_content := '{"languages":${lang_json},"timestamp":${timestamp}}'
os.write_file(cache_path, cache_content) or {}
}
fn cmd_languages(json_output bool, api_key string) {
// Check cache first
cached := get_cached_languages()
if cached != none {
languages := cached or { []string{} }
if json_output {
mut lang_json := '['
for i, lang in languages {
if i > 0 {
lang_json += ','
}
lang_json += '"${lang}"'
}
lang_json += ']'
println(lang_json)
} else {
for lang in languages {
println(lang)
}
}
return
}
// Fetch from API
pub_key := get_public_key()
secret_key := get_secret_key()
cmd := "TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:GET:/languages:\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X GET '${api_base}/languages' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\""
result := exec_curl(cmd)
// Parse and cache the languages
start_idx := result.index('"languages":[') or {
println(result)
return
}
arr_start := result.index_after('[', start_idx)
if arr_start < 0 {
println(result)
return
}
arr_end := result.index_after(']', arr_start)
if arr_end < 0 {
println(result)
return
}
// Extract languages for caching
arr_content := result[arr_start + 1..arr_end]
mut languages := []string{}
for item in arr_content.split(',') {
lang := item.trim_space().trim('"')
if lang.len > 0 {
languages << lang
}
}
// Save to cache
if languages.len > 0 {
save_languages_cache(languages)
}
if json_output {
println(result[arr_start..arr_end + 1])
} else {
for lang in languages {
println(lang)
}
}
}
fn resolve_credentials(account_index int, explicit_pub_key string) (string, string) {
if account_index >= 0 {
// --account N: load from ~/.unsandbox/accounts.csv, bypassing env vars
home := os.getenv('HOME')
csv_path := (if home != '' { home } else { '.' }) + '/.unsandbox/accounts.csv'
if creds := load_accounts_csv(csv_path, account_index) {
pk := if explicit_pub_key != '' { explicit_pub_key } else { creds.0 }
return pk, creds.1
}
// fall back to ./accounts.csv
if creds := load_accounts_csv('accounts.csv', account_index) {
pk := if explicit_pub_key != '' { explicit_pub_key } else { creds.0 }
return pk, creds.1
}
return explicit_pub_key, ''
}
mut pub_key := if explicit_pub_key != '' { explicit_pub_key } else { os.getenv('UNSANDBOX_PUBLIC_KEY') }
mut sec_key := os.getenv('UNSANDBOX_SECRET_KEY')
if pub_key == '' {
api_key := os.getenv('UNSANDBOX_API_KEY')
if api_key != '' {
pub_key = api_key
}
}
// Try UNSANDBOX_ACCOUNT env var to pick a row
mut env_account := -1
env_acct := os.getenv('UNSANDBOX_ACCOUNT')
if env_acct != '' {
env_account = env_acct.int()
}
if pub_key == '' {
home := os.getenv('HOME')
csv_path := (if home != '' { home } else { '.' }) + '/.unsandbox/accounts.csv'
row := if env_account >= 0 { env_account } else { 0 }
if creds := load_accounts_csv(csv_path, row) {
pub_key = creds.0
sec_key = creds.1
}
if pub_key == '' {
if creds2 := load_accounts_csv('accounts.csv', row) {
pub_key = creds2.0
sec_key = creds2.1
}
}
}
if pub_key == '' {
eprintln('${red}Error: UNSANDBOX_PUBLIC_KEY or UNSANDBOX_API_KEY environment variable not set${reset}')
exit(1)
}
return pub_key, sec_key
}
fn main() {
// First pass: scan for --account N and -p flags
mut account_index := -1
mut explicit_pub_key := ''
for i := 1; i < os.args.len; i++ {
if os.args[i] == '--account' && i + 1 < os.args.len {
account_index = os.args[i + 1].int()
i++
} else if os.args[i] == '-p' && i + 1 < os.args.len {
explicit_pub_key = os.args[i + 1]
i++
}
}
pub_key, sec_key := resolve_credentials(account_index, explicit_pub_key)
// Inject resolved credentials into env so get_public_key/get_secret_key pick them up
os.setenv('UNSANDBOX_PUBLIC_KEY', pub_key, true)
os.setenv('UNSANDBOX_SECRET_KEY', sec_key, true)
mut api_key := pub_key
if os.args.len < 2 {
eprintln('Usage: ${os.args[0]} [options] <source_file>')
eprintln(' ${os.args[0]} session [options]')
eprintln(' ${os.args[0]} service [options]')
eprintln(' ${os.args[0]} service env <action> <service_id> [options]')
eprintln(' ${os.args[0]} image [options]')
eprintln(' ${os.args[0]} key [--extend]')
eprintln(' ${os.args[0]} languages [--json]')
eprintln('')
eprintln('Vault commands:')
eprintln(' service env status <id> Check vault status')
eprintln(' service env set <id> Set vault (-e KEY=VAL or --env-file FILE)')
eprintln(' service env export <id> Export vault contents')
eprintln(' service env delete <id> Delete vault')
eprintln('')
eprintln('Image commands:')
eprintln(' image --list List all images')
eprintln(' image --info ID Get image details')
eprintln(' image --delete ID Delete an image')
eprintln(' image --lock ID Lock image to prevent deletion')
eprintln(' image --unlock ID Unlock image')
eprintln(' image --publish ID --source-type TYPE Publish from service/snapshot')
eprintln(' image --visibility ID MODE Set visibility (private/unlisted/public)')
eprintln(' image --spawn ID --name NAME Spawn service from image')
eprintln(' image --clone ID --name NAME Clone an image')
exit(1)
}
if os.args[1] == 'session' {
mut list := false
mut kill := ''
mut shell := ''
mut network := ''
mut vcpu := 0
mut tmux := false
mut screen := false
mut input_files := []string{}
mut i := 2
for i < os.args.len {
match os.args[i] {
'--list' { list = true }
'--kill' {
i++
kill = os.args[i]
}
'--shell' {
i++
shell = os.args[i]
}
'-n' {
i++
network = os.args[i]
}
'-v' {
i++
vcpu = os.args[i].int()
}
'--tmux' { tmux = true }
'--screen' { screen = true }
'-k' {
i++
api_key = os.args[i]
}
'--account' {
i++ // already handled in first pass
}
'-f' {
i++
f := os.args[i]
if os.exists(f) {
input_files << f
} else {
eprintln('Error: File not found: ${f}')
exit(1)
}
}
else {}
}
i++
}
cmd_session(list, kill, shell, network, vcpu, tmux, screen, input_files, api_key)
return
}
if os.args[1] == 'service' {
mut name := ''
mut ports := ''
mut service_type := ''
mut bootstrap := ''
mut bootstrap_file := ''
mut list := false
mut info := ''
mut logs := ''
mut tail := ''
mut sleep := ''
mut wake := ''
mut destroy := ''
mut resize := ''
mut execute := ''
mut command := ''
mut dump_bootstrap := ''
mut dump_file := ''
mut network := ''
mut vcpu := 0
mut input_files := []string{}
mut svc_envs := []string{}
mut svc_env_file := ''
mut env_action := ''
mut env_target := ''
mut unfreeze_on_demand := false
mut set_unfreeze_on_demand_id := ''
mut set_unfreeze_on_demand_enabled := ''
mut i := 2
for i < os.args.len {
match os.args[i] {
'env' {
// service env <action> <service_id>
if i + 2 < os.args.len {
i++
env_action = os.args[i]
i++
env_target = os.args[i]
}
}
'--name' {
i++
name = os.args[i]
}
'--ports' {
i++
ports = os.args[i]
}
'--type' {
i++
service_type = os.args[i]
}
'--bootstrap' {
i++
bootstrap = os.args[i]
}
'--bootstrap-file' {
i++
bootstrap_file = os.args[i]
}
'--list' { list = true }
'--info' {
i++
info = os.args[i]
}
'--logs' {
i++
logs = os.args[i]
}
'--tail' {
i++
tail = os.args[i]
}
'--freeze' {
i++
sleep = os.args[i]
}
'--unfreeze' {
i++
wake = os.args[i]
}
'--destroy' {
i++
destroy = os.args[i]
}
'--resize' {
i++
resize = os.args[i]
}
'--execute' {
i++
execute = os.args[i]
}
'--command' {
i++
command = os.args[i]
}
'--dump-bootstrap' {
i++
dump_bootstrap = os.args[i]
}
'--dump-file' {
i++
dump_file = os.args[i]
}
'-e' {
i++
svc_envs << os.args[i]
}
'--env-file' {
i++
svc_env_file = os.args[i]
}
'--unfreeze-on-demand' { unfreeze_on_demand = true }
'--set-unfreeze-on-demand' {
i++
set_unfreeze_on_demand_id = os.args[i]
i++
if i < os.args.len {
set_unfreeze_on_demand_enabled = os.args[i]
}
}
'-n' {
i++
network = os.args[i]
}
'-v' {
i++
vcpu = os.args[i].int()
}
'-k' {
i++
api_key = os.args[i]
}
'--account' {
i++ // already handled in first pass
}
'-f' {
i++
f := os.args[i]
if os.exists(f) {
input_files << f
} else {
eprintln('Error: File not found: ${f}')
exit(1)
}
}
else {}
}
i++
}
// Handle env subcommand
if env_action != '' && env_target != '' {
cmd_service_env(env_action, env_target, svc_envs, svc_env_file, api_key)
return
}
cmd_service(name, ports, service_type, bootstrap, bootstrap_file, list, info, logs, tail, sleep, wake, destroy, resize, execute, command, dump_bootstrap, dump_file, network,
vcpu, input_files, svc_envs, svc_env_file, unfreeze_on_demand, set_unfreeze_on_demand_id, set_unfreeze_on_demand_enabled, api_key)
return
}
if os.args[1] == 'key' {
mut extend := false
mut i := 2
for i < os.args.len {
match os.args[i] {
'--extend' { extend = true }
'-k' {
i++
api_key = os.args[i]
}
'--account' {
i++ // already handled in first pass
}
else {}
}
i++
}
cmd_key(extend, api_key)
return
}
if os.args[1] == 'languages' {
mut json_output := false
mut i := 2
for i < os.args.len {
match os.args[i] {
'--json' { json_output = true }
'-k' {
i++
api_key = os.args[i]
}
'--account' {
i++ // already handled in first pass
}
else {}
}
i++
}
cmd_languages(json_output, api_key)
return
}
if os.args[1] == 'image' {
mut list := false
mut info := ''
mut delete := ''
mut lock := ''
mut unlock := ''
mut publish := ''
mut source_type := ''
mut visibility_id := ''
mut visibility_mode := ''
mut spawn := ''
mut clone := ''
mut name := ''
mut ports := ''
mut grant := ''
mut revoke := ''
mut trusted := ''
mut trusted_key := ''
mut transfer := ''
mut to_key := ''
mut i := 2
for i < os.args.len {
match os.args[i] {
'--list', '-l' { list = true }
'--info' {
i++
info = os.args[i]
}
'--delete' {
i++
delete = os.args[i]
}
'--lock' {
i++
lock = os.args[i]
}
'--unlock' {
i++
unlock = os.args[i]
}
'--publish' {
i++
publish = os.args[i]
}
'--source-type' {
i++
source_type = os.args[i]
}
'--visibility' {
i++
visibility_id = os.args[i]
i++
if i < os.args.len {
visibility_mode = os.args[i]
}
}
'--spawn' {
i++
spawn = os.args[i]
}
'--clone' {
i++
clone = os.args[i]
}
'--name' {
i++
name = os.args[i]
}
'--ports' {
i++
ports = os.args[i]
}
'--grant' {
i++
grant = os.args[i]
}
'--revoke' {
i++
revoke = os.args[i]
}
'--trusted' {
i++
trusted = os.args[i]
}
'--trusted-key' {
i++
trusted_key = os.args[i]
}
'--transfer' {
i++
transfer = os.args[i]
}
'--to-key' {
i++
to_key = os.args[i]
}
'-k' {
i++
api_key = os.args[i]
}
'--account' {
i++ // already handled in first pass
}
else {}
}
i++
}
cmd_image(list, info, delete, lock, unlock, publish, source_type, visibility_id, visibility_mode, spawn, clone, name, ports, grant, revoke, trusted, trusted_key, transfer, to_key, api_key)
return
}
if os.args[1] == 'snapshot' {
mut list := false
mut info := ''
mut session := ''
mut service := ''
mut restore := ''
mut delete := ''
mut lock := ''
mut unlock := ''
mut clone := ''
mut clone_type := ''
mut name := ''
mut ports := ''
mut hot := false
mut i := 2
for i < os.args.len {
match os.args[i] {
'--list', '-l' { list = true }
'--info' { i++; info = os.args[i] }
'--session' { i++; session = os.args[i] }
'--service' { i++; service = os.args[i] }
'--restore' { i++; restore = os.args[i] }
'--delete' { i++; delete = os.args[i] }
'--lock' { i++; lock = os.args[i] }
'--unlock' { i++; unlock = os.args[i] }
'--clone' { i++; clone = os.args[i] }
'--clone-type' { i++; clone_type = os.args[i] }
'--name' { i++; name = os.args[i] }
'--ports' { i++; ports = os.args[i] }
'--hot' { hot = true }
'-k' { i++; api_key = os.args[i] }
'--account' { i++ /* already handled in first pass */ }
else {}
}
i++
}
cmd_snapshot(list, info, session, service, restore, delete, lock, unlock, clone, clone_type, name, ports, hot, api_key)
return
}
if os.args[1] == 'logs' {
mut source := ''
mut lines := 0
mut since := ''
mut grep := ''
mut follow := false
mut i := 2
for i < os.args.len {
match os.args[i] {
'--source' { i++; source = os.args[i] }
'--lines' { i++; lines = os.args[i].int() }
'--since' { i++; since = os.args[i] }
'--grep' { i++; grep = os.args[i] }
'--follow', '-f' { follow = true }
'-k' { i++; api_key = os.args[i] }
'--account' { i++ /* already handled in first pass */ }
else {}
}
i++
}
cmd_logs(source, lines, since, grep, follow, api_key)
return
}
if os.args[1] == 'health' {
cmd_health()
return
}
if os.args[1] == 'version' {
cmd_version()
return
}
// Execute mode
mut envs := []string{}
mut artifacts := false
mut network := ''
mut source_file := ''
mut vcpu := 0
mut i := 1
for i < os.args.len {
match os.args[i] {
'-e' {
i++
envs << os.args[i]
}
'-a' { artifacts = true }
'-n' {
i++
network = os.args[i]
}
'-v' {
i++
vcpu = os.args[i].int()
}
'-k' {
i++
api_key = os.args[i]
}
'--account' {
i++ // already handled in first pass
}
else {
if os.args[i].starts_with('-') {
eprintln('${red}Unknown option: ${os.args[i]}${reset}')
exit(1)
} else {
source_file = os.args[i]
}
}
}
i++
}
if source_file == '' {
eprintln('${red}Error: No source file specified${reset}')
exit(1)
}
cmd_execute(source_file, envs, artifacts, network, vcpu, api_key)
}
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