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 — PowerShell
# Download + setup
curl -O https://git.unturf.com/engineering/unturf/un-inception/-/raw/main/clients/powershell/sync/src/un.ps1 && chmod +x un.ps1 && ln -sf un.ps1 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.powershell
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 PowerShell existente:
curl -O https://git.unturf.com/engineering/unturf/un-inception/-/raw/main/clients/powershell/sync/src/un.ps1
# 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 PowerShell script:
. ./un.ps1
$result = Invoke-ExecuteCode -Language "powershell" -Code "Write-Host 'Hello from PowerShell running on unsandbox!'"
Write-Host $result.stdout # Hello from PowerShell running on unsandbox!
pwsh myapp.ps1
1feaafce855c1890045f7b7a1296abe6
SHA256: 4eda97ba8c6c7a42e0f0597b2cfea745e7e90dcb3370e152b723bcb7a45234a2
#!/usr/bin/env pwsh
# 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 pwsh
# un.ps1 - Unsandbox CLI Client (PowerShell Implementation)
#
# Usage:
# pwsh un.ps1 [options] <source_file>
# pwsh un.ps1 session [options]
# pwsh un.ps1 service [options]
$API_BASE = "https://api.unsandbox.com"
$PORTAL_BASE = "https://unsandbox.com"
$LANGUAGES_CACHE_TTL = 3600 # 1 hour in seconds
$EXT_MAP = @{
".ps1" = "powershell"; ".py" = "python"; ".js" = "javascript"
".ts" = "typescript"; ".rb" = "ruby"; ".php" = "php"; ".pl" = "perl"
".lua" = "lua"; ".sh" = "bash"; ".go" = "go"; ".rs" = "rust"
".c" = "c"; ".cpp" = "cpp"; ".java" = "java"; ".kt" = "kotlin"
".cs" = "csharp"; ".fs" = "fsharp"; ".hs" = "haskell"; ".ml" = "ocaml"
".clj" = "clojure"; ".scm" = "scheme"; ".lisp" = "commonlisp"
".erl" = "erlang"; ".ex" = "elixir"; ".jl" = "julia"; ".r" = "r"
".cr" = "crystal"; ".d" = "d"; ".nim" = "nim"; ".zig" = "zig"
".v" = "v"; ".dart" = "dart"; ".groovy" = "groovy"; ".f90" = "fortran"
".cob" = "cobol"; ".pro" = "prolog"; ".forth" = "forth"; ".tcl" = "tcl"
".raku" = "raku"; ".m" = "objc"; ".awk" = "awk"
}
$script:AccountIndex = $null
function Load-AccountsCSV {
param($Path, $Index)
if (-not (Test-Path $Path)) { return $null }
try {
$rows = @()
Get-Content $Path | Where-Object {
$_.Trim() -ne "" -and -not $_.TrimStart().StartsWith("#")
} | ForEach-Object {
$parts = $_ -split ",", 2
if ($parts.Count -ge 2) {
$rows += ,@($parts[0].Trim(), $parts[1].Trim())
}
}
if ($Index -lt $rows.Count) {
return $rows[$Index]
}
} catch {}
return $null
}
function Get-ApiKeys {
$home = $env:HOME
if (-not $home) { $home = $env:USERPROFILE }
# --account N: load row N from accounts.csv, bypasses env vars
if ($null -ne $script:AccountIndex) {
$result = Load-AccountsCSV -Path "$home/.unsandbox/accounts.csv" -Index $script:AccountIndex
if (-not $result) {
$result = Load-AccountsCSV -Path "./accounts.csv" -Index $script:AccountIndex
}
if ($result) { return $result }
Write-Error "Error: account $($script:AccountIndex) not found in accounts.csv"
exit 1
}
$publicKey = $env:UNSANDBOX_PUBLIC_KEY
$secretKey = $env:UNSANDBOX_SECRET_KEY
# Fallback to old UNSANDBOX_API_KEY for backwards compat
if (-not $publicKey -and $env:UNSANDBOX_API_KEY) {
$publicKey = $env:UNSANDBOX_API_KEY
$secretKey = ""
}
if ($publicKey -and $secretKey) {
return @($publicKey, $secretKey)
}
if ($publicKey) {
return @($publicKey, "")
}
# accounts.csv fallback (row 0 or UNSANDBOX_ACCOUNT env var)
$rowIdx = 0
if ($env:UNSANDBOX_ACCOUNT) {
try { $rowIdx = [int]$env:UNSANDBOX_ACCOUNT } catch {}
}
$result = Load-AccountsCSV -Path "$home/.unsandbox/accounts.csv" -Index $rowIdx
if (-not $result) {
$result = Load-AccountsCSV -Path "./accounts.csv" -Index $rowIdx
}
if ($result) { return $result }
Write-Error "Error: UNSANDBOX_PUBLIC_KEY or UNSANDBOX_API_KEY not set"
exit 1
}
function Invoke-Api {
param($Endpoint, $Method = "GET", $Body = $null, $BaseUrl = $null)
$publicKey, $secretKey = Get-ApiKeys
$headers = @{
"Authorization" = "Bearer $publicKey"
"Content-Type" = "application/json"
}
# Add HMAC signature if secret key exists
if ($secretKey) {
$timestamp = [int][double]::Parse((Get-Date -UFormat %s))
$bodyContent = if ($Body) { $Body } else { "" }
$sigInput = "${timestamp}:${Method}:${Endpoint}:${bodyContent}"
$hmac = New-Object System.Security.Cryptography.HMACSHA256
$hmac.Key = [System.Text.Encoding]::UTF8.GetBytes($secretKey)
$hash = $hmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($sigInput))
$signature = [System.BitConverter]::ToString($hash).Replace("-", "").ToLower()
$headers["X-Timestamp"] = $timestamp.ToString()
$headers["X-Signature"] = $signature
}
$base = if ($BaseUrl) { $BaseUrl } else { $API_BASE }
$uri = "$base$Endpoint"
try {
if ($Body) {
$response = Invoke-RestMethod -Uri $uri -Method $Method -Headers $headers -Body $Body
} else {
$response = Invoke-RestMethod -Uri $uri -Method $Method -Headers $headers
}
return $response
} catch {
$errorMsg = $_.Exception.Message
if ($errorMsg -match "401" -and $errorMsg -match "timestamp") {
Write-Host "`e[31mError: Request timestamp expired (must be within 5 minutes of server time)`e[0m" -ForegroundColor Red
Write-Host "`e[33mYour computer's clock may have drifted.`e[0m" -ForegroundColor Yellow
Write-Host "Check your system time and sync with NTP if needed:"
Write-Host " Linux: sudo ntpdate -s time.nist.gov"
Write-Host " macOS: sudo sntp -sS time.apple.com"
Write-Host " Windows: w32tm /resync"
} else {
Write-Error "Error: $errorMsg"
}
exit 1
}
}
# Internal API request function that returns status code info for sudo handling
function Invoke-ApiInternal {
param($Endpoint, $Method = "GET", $Body = $null, $SudoOtp = $null, $SudoChallengeId = $null)
$publicKey, $secretKey = Get-ApiKeys
$headers = @{
"Authorization" = "Bearer $publicKey"
"Content-Type" = "application/json"
}
# Add HMAC signature if secret key exists
if ($secretKey) {
$timestamp = [int][double]::Parse((Get-Date -UFormat %s))
$bodyContent = if ($Body) { $Body } else { "" }
$sigInput = "${timestamp}:${Method}:${Endpoint}:${bodyContent}"
$hmac = New-Object System.Security.Cryptography.HMACSHA256
$hmac.Key = [System.Text.Encoding]::UTF8.GetBytes($secretKey)
$hash = $hmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($sigInput))
$signature = [System.BitConverter]::ToString($hash).Replace("-", "").ToLower()
$headers["X-Timestamp"] = $timestamp.ToString()
$headers["X-Signature"] = $signature
}
# Add sudo OTP headers if provided
if ($SudoOtp) {
$headers["X-Sudo-OTP"] = $SudoOtp
}
if ($SudoChallengeId) {
$headers["X-Sudo-Challenge"] = $SudoChallengeId
}
$uri = "$API_BASE$Endpoint"
try {
if ($Body) {
$response = Invoke-RestMethod -Uri $uri -Method $Method -Headers $headers -Body $Body
} else {
$response = Invoke-RestMethod -Uri $uri -Method $Method -Headers $headers
}
return @{ Success = $true; Response = $response; StatusCode = 200 }
} catch {
$statusCode = 0
$responseBody = ""
if ($_.Exception.Response) {
$statusCode = [int]$_.Exception.Response.StatusCode
$stream = $_.Exception.Response.GetResponseStream()
$reader = New-Object System.IO.StreamReader($stream)
$responseBody = $reader.ReadToEnd()
}
return @{ Success = $false; StatusCode = $statusCode; ResponseBody = $responseBody; Error = $_.Exception.Message }
}
}
# Handle 428 sudo OTP challenge - prompts user for OTP and retries the request
function Invoke-SudoChallenge {
param($ResponseBody, $Endpoint, $Method, $Body)
# Extract challenge_id from response
$challengeId = $null
try {
$parsed = $ResponseBody | ConvertFrom-Json
$challengeId = $parsed.challenge_id
} catch {}
Write-Host "`e[33mConfirmation required. Check your email for a one-time code.`e[0m" -ForegroundColor Yellow
$otp = Read-Host "Enter OTP"
if (-not $otp) {
Write-Error "Operation cancelled"
exit 1
}
$otp = $otp.Trim()
# Retry the request with sudo headers
return Invoke-ApiInternal -Endpoint $Endpoint -Method $Method -Body $Body -SudoOtp $otp -SudoChallengeId $challengeId
}
# Wrapper for destructive operations that may require 428 sudo OTP
function Invoke-ApiWithSudo {
param($Endpoint, $Method = "GET", $Body = $null)
$result = Invoke-ApiInternal -Endpoint $Endpoint -Method $Method -Body $Body
if (-not $result.Success -and $result.StatusCode -eq 428) {
$retryResult = Invoke-SudoChallenge -ResponseBody $result.ResponseBody -Endpoint $Endpoint -Method $Method -Body $Body
if (-not $retryResult.Success) {
Write-Error "Error: $($retryResult.Error)"
exit 1
}
return $retryResult.Response
}
if (-not $result.Success) {
Write-Error "Error: $($result.Error)"
exit 1
}
return $result.Response
}
function Invoke-ApiText {
param($Endpoint, $Method, $Body, $BaseUrl = $null)
$publicKey, $secretKey = Get-ApiKeys
$headers = @{
"Authorization" = "Bearer $publicKey"
"Content-Type" = "text/plain"
}
# Add HMAC signature if secret key exists
if ($secretKey) {
$timestamp = [int][double]::Parse((Get-Date -UFormat %s))
$bodyContent = if ($Body) { $Body } else { "" }
$sigInput = "${timestamp}:${Method}:${Endpoint}:${bodyContent}"
$hmac = New-Object System.Security.Cryptography.HMACSHA256
$hmac.Key = [System.Text.Encoding]::UTF8.GetBytes($secretKey)
$hash = $hmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($sigInput))
$signature = [System.BitConverter]::ToString($hash).Replace("-", "").ToLower()
$headers["X-Timestamp"] = $timestamp.ToString()
$headers["X-Signature"] = $signature
}
$base = if ($BaseUrl) { $BaseUrl } else { $API_BASE }
$uri = "$base$Endpoint"
try {
$response = Invoke-RestMethod -Uri $uri -Method $Method -Headers $headers -Body $Body
return @{ Success = $true; Data = $response }
} catch {
return @{ Success = $false; Error = $_.Exception.Message }
}
}
function Read-EnvFile {
param($Path)
if (-not (Test-Path $Path)) {
Write-Error "Error: Env file not found: $Path"
exit 1
}
return Get-Content -Raw $Path
}
function Build-EnvContent {
param($Envs, $EnvFile)
$lines = @()
# Add from -e flags
foreach ($env in $Envs) {
$lines += $env
}
# Add from --env-file
if ($EnvFile) {
$content = Read-EnvFile -Path $EnvFile
foreach ($line in ($content -split "`n")) {
$trimmed = $line.Trim()
if ($trimmed -and -not $trimmed.StartsWith("#")) {
$lines += $trimmed
}
}
}
return $lines -join "`n"
}
$MAX_ENV_CONTENT_SIZE = 65536
function Invoke-ServiceEnvStatus {
param($ServiceId)
return Invoke-Api -Endpoint "/services/$ServiceId/env"
}
function Invoke-ServiceEnvSet {
param($ServiceId, $EnvContent)
if ($EnvContent.Length -gt $MAX_ENV_CONTENT_SIZE) {
Write-Host "`e[31mError: Env content exceeds maximum size of 64KB`e[0m"
return $false
}
$result = Invoke-ApiText -Endpoint "/services/$ServiceId/env" -Method "PUT" -Body $EnvContent
return $result.Success
}
function Invoke-ServiceEnvExport {
param($ServiceId)
return Invoke-Api -Endpoint "/services/$ServiceId/env/export" -Method "POST" -Body "{}"
}
function Invoke-ServiceEnvDelete {
param($ServiceId)
try {
Invoke-Api -Endpoint "/services/$ServiceId/env" -Method "DELETE"
return $true
} catch {
return $false
}
}
function Invoke-ServiceEnv {
param($Action, $Target, $Envs, $EnvFile)
switch ($Action) {
"status" {
if (-not $Target) {
Write-Error "Error: service env status requires service ID"
exit 1
}
$result = Invoke-ServiceEnvStatus -ServiceId $Target
if ($result.has_vault) {
Write-Host "`e[32mVault: configured`e[0m"
if ($result.env_count) {
Write-Host "Variables: $($result.env_count)"
}
if ($result.updated_at) {
Write-Host "Updated: $($result.updated_at)"
}
} else {
Write-Host "`e[33mVault: not configured`e[0m"
}
}
"set" {
if (-not $Target) {
Write-Error "Error: service env set requires service ID"
exit 1
}
if ($Envs.Count -eq 0 -and -not $EnvFile) {
Write-Error "Error: service env set requires -e or --env-file"
exit 1
}
$envContent = Build-EnvContent -Envs $Envs -EnvFile $EnvFile
if (Invoke-ServiceEnvSet -ServiceId $Target -EnvContent $envContent) {
Write-Host "`e[32mVault updated for service $Target`e[0m"
} else {
Write-Error "Error: Failed to update vault"
exit 1
}
}
"export" {
if (-not $Target) {
Write-Error "Error: service env export requires service ID"
exit 1
}
$result = Invoke-ServiceEnvExport -ServiceId $Target
if ($result.content) {
Write-Host $result.content -NoNewline
}
}
"delete" {
if (-not $Target) {
Write-Error "Error: service env delete requires service ID"
exit 1
}
if (Invoke-ServiceEnvDelete -ServiceId $Target) {
Write-Host "`e[32mVault deleted for service $Target`e[0m"
} else {
Write-Error "Error: Failed to delete vault"
exit 1
}
}
default {
Write-Error "Error: Unknown env action: $Action"
Write-Host "Usage: pwsh un.ps1 service env <status|set|export|delete> <service_id>"
exit 1
}
}
}
function Invoke-Execute {
param($SourceFile, $EnvVars = @{}, $Network = $null)
if (-not (Test-Path $SourceFile)) {
Write-Error "Error: File not found: $SourceFile"
exit 1
}
$ext = [System.IO.Path]::GetExtension($SourceFile).ToLower()
$language = $EXT_MAP[$ext]
if (-not $language) {
Write-Error "Error: Unknown extension: $ext"
exit 1
}
$code = Get-Content -Raw $SourceFile
$payload = @{
language = $language
code = $code
}
if ($EnvVars.Count -gt 0) {
$payload["env"] = $EnvVars
}
if ($Network) {
$payload["network"] = $Network
}
$body = $payload | ConvertTo-Json -Depth 10
$result = Invoke-Api -Endpoint "/execute" -Method "POST" -Body $body
if ($result.stdout) {
Write-Host "`e[34m$($result.stdout)`e[0m" -NoNewline
}
if ($result.stderr) {
Write-Host "`e[31m$($result.stderr)`e[0m" -NoNewline
}
exit $result.exit_code
}
function Invoke-Session {
param($Args)
if ($Args -contains "--list" -or $Args -contains "-l") {
$result = Invoke-Api -Endpoint "/sessions"
$result | ConvertTo-Json -Depth 5
return
}
if ($Args -contains "--kill") {
$idx = [array]::IndexOf($Args, "--kill")
$sessionId = $Args[$idx + 1]
Invoke-Api -Endpoint "/sessions/$sessionId" -Method "DELETE"
Write-Host "`e[32mSession terminated: $sessionId`e[0m"
return
}
# Create session
$shell = "bash"
if ($Args -contains "--shell" -or $Args -contains "-s") {
$idx = if ($Args -contains "--shell") { [array]::IndexOf($Args, "--shell") } else { [array]::IndexOf($Args, "-s") }
$shell = $Args[$idx + 1]
}
# Parse input files
$inputFiles = @()
for ($i = 0; $i -lt $Args.Count; $i++) {
if ($Args[$i] -eq "-f" -and ($i + 1) -lt $Args.Count) {
$filepath = $Args[$i + 1]
if (-not (Test-Path $filepath)) {
Write-Error "Error: Input file not found: $filepath"
exit 1
}
$content = [System.IO.File]::ReadAllBytes($filepath)
$b64Content = [Convert]::ToBase64String($content)
$inputFiles += @{
filename = [System.IO.Path]::GetFileName($filepath)
content_base64 = $b64Content
}
$i++
}
}
$payload = @{ shell = $shell }
if ($inputFiles.Count -gt 0) {
$payload["input_files"] = $inputFiles
}
$body = $payload | ConvertTo-Json -Depth 10
$result = Invoke-Api -Endpoint "/sessions" -Method "POST" -Body $body
Write-Host "`e[33mSession created (WebSocket required for interactive)`e[0m"
$result | ConvertTo-Json -Depth 5
}
function Get-LanguagesCachePath {
$cacheDir = Join-Path $env:HOME ".unsandbox"
if (-not (Test-Path $cacheDir)) {
New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null
}
return Join-Path $cacheDir "languages.json"
}
function Get-CachedLanguages {
$cachePath = Get-LanguagesCachePath
if (-not (Test-Path $cachePath)) {
return $null
}
try {
$cacheContent = Get-Content -Raw $cachePath | ConvertFrom-Json
$currentTime = [int][double]::Parse((Get-Date -UFormat %s))
if ($cacheContent.timestamp -and ($currentTime - $cacheContent.timestamp) -lt $LANGUAGES_CACHE_TTL) {
return $cacheContent.languages
}
} catch {
# Cache is invalid, return null
}
return $null
}
function Save-LanguagesCache {
param($Languages)
$cachePath = Get-LanguagesCachePath
$timestamp = [int][double]::Parse((Get-Date -UFormat %s))
$cacheData = @{
languages = $Languages
timestamp = $timestamp
}
try {
$cacheData | ConvertTo-Json -Compress | Set-Content -Path $cachePath
} catch {
# Silently fail if we can't write cache
}
}
function Invoke-Languages {
param($Args)
$jsonOutput = $Args -contains "--json"
# Check cache first
$languages = Get-CachedLanguages
if (-not $languages) {
# Fetch from API
$result = Invoke-Api -Endpoint "/languages"
$languages = $result.languages
# Save to cache
if ($languages) {
Save-LanguagesCache -Languages $languages
}
}
if ($jsonOutput) {
# Output as JSON array
$languages | ConvertTo-Json -Compress
} else {
# Output one language per line
foreach ($lang in $languages) {
Write-Output $lang
}
}
}
function Invoke-Key {
param($Args)
$extend = $Args -contains "--extend"
try {
$result = Invoke-Api -Endpoint "/keys/validate" -Method "POST" -BaseUrl $PORTAL_BASE
# Handle --extend flag
if ($extend) {
$publicKey = $result.public_key
if ($publicKey) {
$url = "$PORTAL_BASE/keys/extend?pk=$publicKey"
Write-Host "`e[34mOpening browser to extend key...`e[0m"
if ($IsWindows) {
Start-Process $url
} elseif ($IsMacOS) {
& open $url
} elseif ($IsLinux) {
& xdg-open $url
} else {
Write-Host "`e[33mPlease open manually: $url`e[0m"
}
return
} else {
Write-Error "Error: Could not retrieve public key"
exit 1
}
}
# Check if key is expired
if ($result.expired) {
Write-Host "`e[31mExpired`e[0m"
Write-Host "Public Key: $($result.public_key ?? 'N/A')"
Write-Host "Tier: $($result.tier ?? 'N/A')"
Write-Host "Expired: $($result.expires_at ?? 'N/A')"
Write-Host "`e[33mTo renew: Visit $PORTAL_BASE/keys/extend`e[0m"
exit 1
}
# Valid key
Write-Host "`e[32mValid`e[0m"
Write-Host "Public Key: $($result.public_key ?? 'N/A')"
Write-Host "Tier: $($result.tier ?? 'N/A')"
Write-Host "Status: $($result.status ?? 'N/A')"
Write-Host "Expires: $($result.expires_at ?? 'N/A')"
Write-Host "Time Remaining: $($result.time_remaining ?? 'N/A')"
Write-Host "Rate Limit: $($result.rate_limit ?? 'N/A')"
Write-Host "Burst: $($result.burst ?? 'N/A')"
Write-Host "Concurrency: $($result.concurrency ?? 'N/A')"
} catch {
Write-Host "`e[31mInvalid`e[0m"
Write-Host "Reason: $($_.Exception.Message)"
exit 1
}
}
function Invoke-Image {
param($Args)
# Parse arguments
$listMode = $Args -contains "--list" -or $Args -contains "-l"
$infoId = $null
$deleteId = $null
$lockId = $null
$unlockId = $null
$publishId = $null
$sourceType = $null
$visibilityId = $null
$visibilityMode = $null
$spawnId = $null
$cloneId = $null
$name = $null
$ports = $null
for ($i = 0; $i -lt $Args.Count; $i++) {
switch ($Args[$i]) {
"--info" { $infoId = $Args[$i + 1]; $i++ }
"--delete" { $deleteId = $Args[$i + 1]; $i++ }
"--lock" { $lockId = $Args[$i + 1]; $i++ }
"--unlock" { $unlockId = $Args[$i + 1]; $i++ }
"--publish" { $publishId = $Args[$i + 1]; $i++ }
"--source-type" { $sourceType = $Args[$i + 1]; $i++ }
"--visibility" {
$visibilityId = $Args[$i + 1]
$visibilityMode = $Args[$i + 2]
$i += 2
}
"--spawn" { $spawnId = $Args[$i + 1]; $i++ }
"--clone" { $cloneId = $Args[$i + 1]; $i++ }
"--name" { $name = $Args[$i + 1]; $i++ }
"--ports" { $ports = $Args[$i + 1]; $i++ }
}
}
if ($listMode) {
$result = Invoke-Api -Endpoint "/images"
$result | ConvertTo-Json -Depth 5
return
}
if ($infoId) {
$result = Invoke-Api -Endpoint "/images/$infoId"
$result | ConvertTo-Json -Depth 5
return
}
if ($deleteId) {
Invoke-ApiWithSudo -Endpoint "/images/$deleteId" -Method "DELETE"
Write-Host "`e[32mImage deleted: $deleteId`e[0m"
return
}
if ($lockId) {
Invoke-Api -Endpoint "/images/$lockId/lock" -Method "POST" -Body "{}"
Write-Host "`e[32mImage locked: $lockId`e[0m"
return
}
if ($unlockId) {
Invoke-ApiWithSudo -Endpoint "/images/$unlockId/unlock" -Method "POST" -Body "{}"
Write-Host "`e[32mImage unlocked: $unlockId`e[0m"
return
}
if ($publishId) {
if (-not $sourceType) {
Write-Error "Error: --publish requires --source-type (service or snapshot)"
exit 1
}
$payload = @{
source_type = $sourceType
source_id = $publishId
}
if ($name) { $payload["name"] = $name }
$body = $payload | ConvertTo-Json
$result = Invoke-Api -Endpoint "/images/publish" -Method "POST" -Body $body
Write-Host "`e[32mImage published`e[0m"
$result | ConvertTo-Json -Depth 5
return
}
if ($visibilityId -and $visibilityMode) {
$payload = @{ visibility = $visibilityMode } | ConvertTo-Json
Invoke-Api -Endpoint "/images/$visibilityId/visibility" -Method "POST" -Body $payload
Write-Host "`e[32mImage visibility set to $visibilityMode`: $visibilityId`e[0m"
return
}
if ($spawnId) {
$payload = @{}
if ($name) { $payload["name"] = $name }
if ($ports) {
$portList = $ports -split "," | ForEach-Object { [int]$_ }
$payload["ports"] = $portList
}
$body = $payload | ConvertTo-Json
$result = Invoke-Api -Endpoint "/images/$spawnId/spawn" -Method "POST" -Body $body
Write-Host "`e[32mService spawned from image`e[0m"
$result | ConvertTo-Json -Depth 5
return
}
if ($cloneId) {
$payload = @{}
if ($name) { $payload["name"] = $name }
$body = $payload | ConvertTo-Json
$result = Invoke-Api -Endpoint "/images/$cloneId/clone" -Method "POST" -Body $body
Write-Host "`e[32mImage cloned`e[0m"
$result | ConvertTo-Json -Depth 5
return
}
Write-Error "Error: Use --list, --info, --delete, --lock, --unlock, --publish, --visibility, --spawn, or --clone"
exit 1
}
function Invoke-Snapshot {
param($Args)
# Parse arguments
$listMode = $Args -contains "--list" -or $Args -contains "-l"
$infoId = $null
$deleteId = $null
$lockId = $null
$unlockId = $null
$restoreId = $null
$cloneId = $null
$cloneType = $null
$name = $null
$shell = $null
$ports = $null
for ($i = 0; $i -lt $Args.Count; $i++) {
switch ($Args[$i]) {
"--info" { $infoId = $Args[$i + 1]; $i++ }
"--delete" { $deleteId = $Args[$i + 1]; $i++ }
"--lock" { $lockId = $Args[$i + 1]; $i++ }
"--unlock" { $unlockId = $Args[$i + 1]; $i++ }
"--restore" { $restoreId = $Args[$i + 1]; $i++ }
"--clone" { $cloneId = $Args[$i + 1]; $i++ }
"--type" { $cloneType = $Args[$i + 1]; $i++ }
"--name" { $name = $Args[$i + 1]; $i++ }
"--shell" { $shell = $Args[$i + 1]; $i++ }
"-s" { $shell = $Args[$i + 1]; $i++ }
"--ports" { $ports = $Args[$i + 1]; $i++ }
}
}
if ($listMode) {
$result = Invoke-Api -Endpoint "/snapshots"
$result | ConvertTo-Json -Depth 5
return
}
if ($infoId) {
$result = Invoke-Api -Endpoint "/snapshots/$infoId"
$result | ConvertTo-Json -Depth 5
return
}
if ($deleteId) {
Invoke-ApiWithSudo -Endpoint "/snapshots/$deleteId" -Method "DELETE"
Write-Host "`e[32mSnapshot deleted: $deleteId`e[0m"
return
}
if ($lockId) {
Invoke-Api -Endpoint "/snapshots/$lockId/lock" -Method "POST" -Body "{}"
Write-Host "`e[32mSnapshot locked: $lockId`e[0m"
return
}
if ($unlockId) {
Invoke-ApiWithSudo -Endpoint "/snapshots/$unlockId/unlock" -Method "POST" -Body "{}"
Write-Host "`e[32mSnapshot unlocked: $unlockId`e[0m"
return
}
if ($restoreId) {
$result = Invoke-Api -Endpoint "/snapshots/$restoreId/restore" -Method "POST" -Body "{}"
Write-Host "`e[32mRestored from snapshot`e[0m"
$result | ConvertTo-Json -Depth 5
return
}
if ($cloneId) {
if (-not $cloneType) {
$cloneType = "session"
}
$payload = @{ type = $cloneType }
if ($name) { $payload["name"] = $name }
if ($shell) { $payload["shell"] = $shell }
if ($ports) {
$portList = $ports -split "," | ForEach-Object { [int]$_ }
$payload["ports"] = $portList
}
$body = $payload | ConvertTo-Json
$result = Invoke-Api -Endpoint "/snapshots/$cloneId/clone" -Method "POST" -Body $body
Write-Host "`e[32mCloned to $cloneType`e[0m"
$result | ConvertTo-Json -Depth 5
return
}
Write-Error "Error: Use --list, --info, --delete, --lock, --unlock, --restore, or --clone"
exit 1
}
function Invoke-Service {
param($Args)
# Parse env subcommand and -e/--env-file
$envAction = $null
$envTarget = $null
$envs = @()
$envFile = $null
for ($i = 0; $i -lt $Args.Count; $i++) {
if ($Args[$i] -eq "env" -and ($i + 1) -lt $Args.Count) {
$next = $Args[$i + 1]
if (-not $next.StartsWith("-")) {
$envAction = $next
$i++
if (($i + 1) -lt $Args.Count) {
$next2 = $Args[$i + 1]
if (-not $next2.StartsWith("-")) {
$envTarget = $next2
$i++
}
}
}
} elseif ($Args[$i] -eq "-e" -and ($i + 1) -lt $Args.Count) {
$envs += $Args[$i + 1]
$i++
} elseif ($Args[$i] -eq "--env-file" -and ($i + 1) -lt $Args.Count) {
$envFile = $Args[$i + 1]
$i++
}
}
# Handle env subcommand
if ($envAction) {
Invoke-ServiceEnv -Action $envAction -Target $envTarget -Envs $envs -EnvFile $envFile
return
}
if ($Args -contains "--list" -or $Args -contains "-l") {
$result = Invoke-Api -Endpoint "/services"
$result | ConvertTo-Json -Depth 5
return
}
if ($Args -contains "--info") {
$idx = [array]::IndexOf($Args, "--info")
$serviceId = $Args[$idx + 1]
$result = Invoke-Api -Endpoint "/services/$serviceId"
$result | ConvertTo-Json -Depth 5
return
}
if ($Args -contains "--logs") {
$idx = [array]::IndexOf($Args, "--logs")
$serviceId = $Args[$idx + 1]
$result = Invoke-Api -Endpoint "/services/$serviceId/logs"
Write-Host $result.logs
return
}
if ($Args -contains "--freeze") {
$idx = [array]::IndexOf($Args, "--freeze")
$serviceId = $Args[$idx + 1]
Invoke-Api -Endpoint "/services/$serviceId/freeze" -Method "POST" -Body "{}"
Write-Host "`e[32mService frozen: $serviceId`e[0m"
return
}
if ($Args -contains "--unfreeze") {
$idx = [array]::IndexOf($Args, "--unfreeze")
$serviceId = $Args[$idx + 1]
Invoke-Api -Endpoint "/services/$serviceId/unfreeze" -Method "POST" -Body "{}"
Write-Host "`e[32mService unfreezing: $serviceId`e[0m"
return
}
if ($Args -contains "--destroy") {
$idx = [array]::IndexOf($Args, "--destroy")
$serviceId = $Args[$idx + 1]
Invoke-ApiWithSudo -Endpoint "/services/$serviceId" -Method "DELETE"
Write-Host "`e[32mService destroyed: $serviceId`e[0m"
return
}
if ($Args -contains "--resize") {
$idx = [array]::IndexOf($Args, "--resize")
$serviceId = $Args[$idx + 1]
# Get vcpu value from --vcpu or -v
$vcpuValue = 0
if ($Args -contains "--vcpu") {
$vIdx = [array]::IndexOf($Args, "--vcpu")
$vcpuValue = [int]$Args[$vIdx + 1]
} elseif ($Args -contains "-v") {
$vIdx = [array]::IndexOf($Args, "-v")
$vcpuValue = [int]$Args[$vIdx + 1]
}
if ($vcpuValue -le 0) {
Write-Error "Error: --resize requires --vcpu or -v"
exit 1
}
if ($vcpuValue -lt 1 -or $vcpuValue -gt 8) {
Write-Error "Error: vCPU must be between 1 and 8"
exit 1
}
$payload = @{ vcpu = $vcpuValue } | ConvertTo-Json
Invoke-Api -Endpoint "/services/$serviceId" -Method "PATCH" -Body $payload
$ram = $vcpuValue * 2
Write-Host "`e[32mService resized to $vcpuValue vCPU, $ram GB RAM`e[0m"
return
}
if ($Args -contains "--set-unfreeze-on-demand") {
$idx = [array]::IndexOf($Args, "--set-unfreeze-on-demand")
$serviceId = $Args[$idx + 1]
$enabled = $Args[$idx + 2]
if ($enabled -eq "true" -or $enabled -eq "1") {
$payload = @{ unfreeze_on_demand = $true } | ConvertTo-Json
Invoke-Api -Endpoint "/services/$serviceId" -Method "PATCH" -Body $payload
Write-Host "`e[32mUnfreeze-on-demand enabled for service: $serviceId`e[0m"
} elseif ($enabled -eq "false" -or $enabled -eq "0") {
$payload = @{ unfreeze_on_demand = $false } | ConvertTo-Json
Invoke-Api -Endpoint "/services/$serviceId" -Method "PATCH" -Body $payload
Write-Host "`e[32mUnfreeze-on-demand disabled for service: $serviceId`e[0m"
} else {
Write-Error "Error: --set-unfreeze-on-demand requires true/false or 1/0"
exit 1
}
return
}
if ($Args -contains "--dump-bootstrap") {
$idx = [array]::IndexOf($Args, "--dump-bootstrap")
$serviceId = $Args[$idx + 1]
Write-Host "Fetching bootstrap script from $serviceId..." -ForegroundColor Yellow
$payload = @{ command = "cat /tmp/bootstrap.sh" } | ConvertTo-Json
$result = Invoke-Api -Endpoint "/services/$serviceId/execute" -Method "POST" -Body $payload
if ($result.stdout -and $result.stdout.Length -gt 0) {
$bootstrap = $result.stdout
if ($Args -contains "--dump-file") {
$dumpIdx = [array]::IndexOf($Args, "--dump-file")
$dumpFile = $Args[$dumpIdx + 1]
# Write to file
$bootstrap | Set-Content -Path $dumpFile -NoNewline
if ($IsLinux -or $IsMacOS) {
& chmod 755 $dumpFile
}
Write-Host "Bootstrap saved to $dumpFile"
} else {
# Print to stdout
Write-Host $bootstrap -NoNewline
}
} else {
Write-Error "Error: Failed to fetch bootstrap (service not running or no bootstrap file)"
exit 1
}
return
}
# Create service
if ($Args -contains "--name") {
$idx = [array]::IndexOf($Args, "--name")
$name = $Args[$idx + 1]
$payload = @{ name = $name }
if ($Args -contains "--ports") {
$pIdx = [array]::IndexOf($Args, "--ports")
$ports = $Args[$pIdx + 1] -split "," | ForEach-Object { [int]$_ }
$payload["ports"] = $ports
}
if ($Args -contains "--bootstrap") {
$bIdx = [array]::IndexOf($Args, "--bootstrap")
$payload["bootstrap"] = $Args[$bIdx + 1]
}
if ($Args -contains "--bootstrap-file") {
$bfIdx = [array]::IndexOf($Args, "--bootstrap-file")
$bootstrapFile = $Args[$bfIdx + 1]
if (Test-Path $bootstrapFile) {
$payload["bootstrap_content"] = Get-Content -Raw $bootstrapFile
} else {
Write-Error "Error: Bootstrap file not found: $bootstrapFile"
exit 1
}
}
if ($Args -contains "--type") {
$tIdx = [array]::IndexOf($Args, "--type")
$payload["service_type"] = $Args[$tIdx + 1]
}
if ($Args -contains "--unfreeze-on-demand") {
$payload["unfreeze_on_demand"] = $true
}
# Parse input files
$inputFiles = @()
for ($i = 0; $i -lt $Args.Count; $i++) {
if ($Args[$i] -eq "-f" -and ($i + 1) -lt $Args.Count) {
$filepath = $Args[$i + 1]
if (-not (Test-Path $filepath)) {
Write-Error "Error: Input file not found: $filepath"
exit 1
}
$content = [System.IO.File]::ReadAllBytes($filepath)
$b64Content = [Convert]::ToBase64String($content)
$inputFiles += @{
filename = [System.IO.Path]::GetFileName($filepath)
content_base64 = $b64Content
}
$i++
}
}
if ($inputFiles.Count -gt 0) {
$payload["input_files"] = $inputFiles
}
$body = $payload | ConvertTo-Json -Depth 10
$result = Invoke-Api -Endpoint "/services" -Method "POST" -Body $body
$serviceId = $result.id
Write-Host "`e[32mService created: $serviceId`e[0m"
$result | ConvertTo-Json -Depth 5
# Auto-set vault if env vars were provided
if ($envs.Count -gt 0 -or $envFile) {
$envContent = Build-EnvContent -Envs $envs -EnvFile $envFile
if ($envContent) {
if (Invoke-ServiceEnvSet -ServiceId $serviceId -EnvContent $envContent) {
Write-Host "`e[32mVault configured with environment variables`e[0m"
} else {
Write-Host "`e[33mWarning: Failed to set vault`e[0m"
}
}
}
return
}
Write-Error "Error: Specify --name to create or use --list, --info, etc."
exit 1
}
# Main
if ($args.Count -eq 0 -or $args[0] -eq "--help" -or $args[0] -eq "-h") {
Write-Host @"
Usage: pwsh un.ps1 [options] <source_file>
pwsh un.ps1 session [options]
pwsh un.ps1 service [options]
pwsh un.ps1 snapshot [options]
pwsh un.ps1 image [options]
pwsh un.ps1 languages [--json]
pwsh un.ps1 key [options]
Languages options:
--json Output as JSON array
Image options:
--list, -l List all images
--info ID Get image details
--delete ID Delete an image
--lock ID Lock image to prevent deletion
--unlock ID Unlock image
--publish ID Publish image from service/snapshot
--source-type TYPE Source type: service or snapshot
--visibility ID MODE Set visibility: private, unlisted, or public
--spawn ID Spawn new service from image
--clone ID Clone an image
--name NAME Name for spawned service or cloned image
--ports PORTS Ports for spawned service
Execute options:
-e KEY=VALUE Environment variable
-n MODE Network mode (zerotrust|semitrusted)
Session options:
--list, -l List sessions
--kill ID Terminate session
--shell NAME Shell/REPL to use
-f FILE Input file (can be repeated)
Service options:
--name NAME Service name
--ports PORTS Comma-separated ports
--type TYPE Service type (minecraft, mumble, teamspeak, source, tcp, udp)
--bootstrap CMD Bootstrap command
--unfreeze-on-demand Enable unfreeze-on-demand for new service
-f FILE Input file (can be repeated)
-e KEY=VALUE Environment variable for vault (can be repeated)
--env-file FILE Load vault variables from file
--list, -l List services
--info ID Get service info
--logs ID Get logs
--freeze ID Freeze service
--unfreeze ID Unfreeze service
--destroy ID Destroy service
--resize ID Resize service (requires --vcpu or -v)
--set-unfreeze-on-demand ID true|false Enable/disable unfreeze-on-demand
--dump-bootstrap ID Dump bootstrap script from service
--dump-file FILE Save bootstrap to file (with --dump-bootstrap)
Service env commands:
env status ID Show vault status
env set ID Set vault (-e KEY=VALUE or --env-file FILE)
env export ID Export vault contents
env delete ID Delete vault
Snapshot options:
--list, -l List all snapshots
--info ID Get snapshot details
--delete ID Delete snapshot
--lock ID Lock snapshot
--unlock ID Unlock snapshot
--restore ID Restore from snapshot
--clone ID Clone snapshot to session/service
--type TYPE Clone type: session or service
--name NAME Name for cloned resource
--shell NAME Shell for cloned session
--ports PORTS Ports for cloned service
Key options:
--extend Open browser to extend key
"@
exit 0
}
# Pre-parse --account N from args, strip from effective arg list
$effectiveArgs = @()
for ($i = 0; $i -lt $args.Count; $i++) {
if ($args[$i] -eq "--account" -and ($i + 1) -lt $args.Count) {
try { $script:AccountIndex = [int]$args[$i + 1] } catch {}
$i++
} else {
$effectiveArgs += $args[$i]
}
}
if ($effectiveArgs[0] -eq "session") {
Invoke-Session -Args $effectiveArgs[1..($effectiveArgs.Count-1)]
} elseif ($effectiveArgs[0] -eq "service") {
Invoke-Service -Args $effectiveArgs[1..($effectiveArgs.Count-1)]
} elseif ($effectiveArgs[0] -eq "snapshot") {
Invoke-Snapshot -Args $effectiveArgs[1..($effectiveArgs.Count-1)]
} elseif ($effectiveArgs[0] -eq "image") {
Invoke-Image -Args $effectiveArgs[1..($effectiveArgs.Count-1)]
} elseif ($effectiveArgs[0] -eq "languages") {
Invoke-Languages -Args $effectiveArgs[1..($effectiveArgs.Count-1)]
} elseif ($effectiveArgs[0] -eq "key") {
Invoke-Key -Args $effectiveArgs[1..($effectiveArgs.Count-1)]
} else {
# Parse execute args
$sourceFile = $null
$envVars = @{}
$network = $null
for ($i = 0; $i -lt $effectiveArgs.Count; $i++) {
switch ($effectiveArgs[$i]) {
"-e" {
$kv = $effectiveArgs[$i+1] -split "=", 2
$envVars[$kv[0]] = $kv[1]
$i++
}
"-n" { $network = $effectiveArgs[$i+1]; $i++ }
default {
if ($effectiveArgs[$i].StartsWith("-")) {
Write-Error "${RED}Unknown option: $($effectiveArgs[$i])${RESET}"
exit 1
} else {
$sourceFile = $effectiveArgs[$i]
}
}
}
}
if (-not $sourceFile) {
Write-Error "Error: No source file specified"
exit 1
}
Invoke-Execute -SourceFile $sourceFile -EnvVars $envVars -Network $network
}
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