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 — OCaml
# Download + setup
curl -O https://git.unturf.com/engineering/unturf/un-inception/-/raw/main/clients/ocaml/sync/src/un.ml
export UNSANDBOX_PUBLIC_KEY="unsb-pk-xxxx-xxxx-xxxx-xxxx"
export UNSANDBOX_SECRET_KEY="unsb-sk-xxxxx-xxxxx-xxxxx-xxxxx"
# Run code
./un script.ocaml
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 OCaml existente:
curl -O https://git.unturf.com/engineering/unturf/un-inception/-/raw/main/clients/ocaml/sync/src/un.ml
# 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 OCaml app: *)
open Un
let () =
let result = execute_code "ocaml" {|print_endline "Hello from OCaml running on unsandbox!"|} in
print_endline (result |> stdout) (* Hello from OCaml running on unsandbox! *)
ocamlfind ocamlopt -package curl -linkpkg -o myapp main.ml un.ml && ./myapp
3993d84f149495d28ec02cc581d23093
SHA256: cc29ab042d58d11783a74d16a110ff5c0fe82db0d2a81a8b0c951416355681bf
(* 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
*)
(** {1 unsandbox OCaml SDK}
Secure code execution in sandboxed containers.
{2 Library Usage}
{[
(* Simple execution *)
let result = Un.execute "python" "print('Hello World')" () in
print_endline result.stdout
(* Using Client for stored credentials *)
let client = Un.Client.create ~public_key:"unsb-pk-..." ~secret_key:"unsb-sk-..." () in
let result = Un.Client.execute client "python" code in
print_endline result.stdout
(* Async execution *)
let job = Un.execute_async "python" long_code () in
let result = Un.wait job.job_id () in
print_endline result.stdout
]}
{2 CLI Usage}
{[
chmod +x un.ml
./un.ml script.py
./un.ml session --shell python3
./un.ml service --name web --ports 80
]}
{2 Authentication}
Credentials are loaded in priority order:
+ Function arguments (public_key, secret_key)
+ Environment variables (UNSANDBOX_PUBLIC_KEY, UNSANDBOX_SECRET_KEY)
+ Config file (~/.unsandbox/accounts.csv)
*)
#!/usr/bin/env ocaml
(* ============================================================================
Configuration
============================================================================ *)
(** API base URL *)
let api_base = "https://api.unsandbox.com"
(** Portal base URL *)
let portal_base = "https://unsandbox.com"
(** Default execution timeout in seconds *)
let default_timeout = 300
(** Default TTL for code execution *)
let default_ttl = 60
(** Languages cache TTL in seconds (1 hour) *)
let languages_cache_ttl = 3600
(* ANSI colors *)
let blue = "\x1b[34m"
let red = "\x1b[31m"
let green = "\x1b[32m"
let yellow = "\x1b[33m"
let reset = "\x1b[0m"
(* ============================================================================
Types
============================================================================ *)
(** Execution options for API calls *)
type exec_options = {
env: (string * string) list; (** Environment variables *)
input_files: string list; (** Input file paths *)
network_mode: string; (** "zerotrust" or "semitrusted" *)
ttl: int; (** Execution timeout in seconds *)
vcpu: int; (** vCPU count (1-8) *)
return_artifacts: bool; (** Return compiled artifacts *)
}
(** Default execution options *)
let default_exec_options = {
env = [];
input_files = [];
network_mode = "zerotrust";
ttl = default_ttl;
vcpu = 1;
return_artifacts = false;
}
(** Execution result *)
type exec_result = {
success: bool;
stdout: string;
stderr: string;
exit_code: int;
job_id: string option;
}
(** Job status *)
type job_status = {
job_id: string;
status: string; (** "pending", "running", "completed", "failed", "timeout", "cancelled" *)
result: exec_result option;
}
(** Language info *)
type language_info = {
name: string;
version: string;
aliases: string list;
}
(* ============================================================================
Utility Functions
============================================================================ *)
(** Extension to language mapping *)
let ext_to_lang ext =
match ext with
| ".hs" -> Some "haskell" | ".ml" -> Some "ocaml" | ".clj" -> Some "clojure"
| ".scm" -> Some "scheme" | ".lisp" -> Some "commonlisp" | ".erl" -> Some "erlang"
| ".ex" -> Some "elixir" | ".exs" -> Some "elixir" | ".py" -> Some "python"
| ".js" -> Some "javascript" | ".ts" -> Some "typescript" | ".rb" -> Some "ruby"
| ".go" -> Some "go" | ".rs" -> Some "rust" | ".c" -> Some "c"
| ".cpp" -> Some "cpp" | ".cc" -> Some "cpp" | ".cxx" -> Some "cpp"
| ".java" -> Some "java" | ".kt" -> Some "kotlin" | ".cs" -> Some "csharp"
| ".fs" -> Some "fsharp" | ".jl" -> Some "julia" | ".r" -> Some "r"
| ".cr" -> Some "crystal" | ".d" -> Some "d" | ".nim" -> Some "nim"
| ".zig" -> Some "zig" | ".v" -> Some "v" | ".dart" -> Some "dart"
| ".groovy" -> Some "groovy" | ".scala" -> Some "scala"
| ".sh" -> Some "bash" | ".pl" -> Some "perl" | ".lua" -> Some "lua"
| ".php" -> Some "php"
| _ -> None
(** Read file contents *)
let read_file filename =
let ic = open_in filename in
let n = in_channel_length ic in
let s = really_input_string ic n in
close_in ic;
s
(** Base64 encode a file using shell command *)
let base64_encode_file filename =
let cmd = Printf.sprintf "base64 -w0 %s" (Filename.quote filename) in
let ic = Unix.open_process_in cmd in
let result = try input_line ic with End_of_file -> "" in
let _ = Unix.close_process_in ic in
String.trim result
(** Build input_files JSON from list of filenames *)
let build_input_files_json files =
if files = [] then ""
else
let entries = List.map (fun f ->
let basename = Filename.basename f in
let content = base64_encode_file f in
Printf.sprintf "{\"filename\":\"%s\",\"content\":\"%s\"}" basename content
) files in
",\"input_files\":[" ^ (String.concat "," entries) ^ "]"
(** Get file extension *)
let get_extension filename =
try
let dot_pos = String.rindex filename '.' in
String.sub filename dot_pos (String.length filename - dot_pos)
with Not_found -> ""
(** Get languages cache file path *)
let get_languages_cache_path () =
let home = try Sys.getenv "HOME" with Not_found -> "." in
Filename.concat home ".unsandbox/languages.json"
(** Load languages from cache if valid *)
let load_languages_cache () =
let cache_path = get_languages_cache_path () in
if Sys.file_exists cache_path then
try
let content = read_file cache_path in
let timestamp = extract_json_int content "timestamp" in
match timestamp with
| Some ts ->
let now = int_of_float (Unix.time ()) in
if now - ts < languages_cache_ttl then
Some content
else
None
| None -> None
with _ -> None
else
None
(** Save languages to cache *)
let save_languages_cache languages_json =
let cache_path = get_languages_cache_path () in
let cache_dir = Filename.dirname cache_path in
(try Unix.mkdir cache_dir 0o755 with Unix.Unix_error (Unix.EEXIST, _, _) -> ());
let timestamp = int_of_float (Unix.time ()) in
let cache_content = Printf.sprintf "{\"languages\":%s,\"timestamp\":%d}" languages_json timestamp in
let oc = open_out cache_path in
output_string oc cache_content;
close_out oc
(** Escape JSON string *)
let escape_json s =
let buf = Buffer.create (String.length s) in
String.iter (fun c ->
match c with
| '\\' -> Buffer.add_string buf "\\\\"
| '"' -> Buffer.add_string buf "\\\""
| '\n' -> Buffer.add_string buf "\\n"
| '\r' -> Buffer.add_string buf "\\r"
| '\t' -> Buffer.add_string buf "\\t"
| _ -> Buffer.add_char buf c
) s;
Buffer.contents buf
(** Unescape JSON string *)
let unescape_json s =
let s = Str.global_replace (Str.regexp "\\\\n") "\n" s in
let s = Str.global_replace (Str.regexp "\\\\t") "\t" s in
let s = Str.global_replace (Str.regexp "\\\\\"") "\"" s in
let s = Str.global_replace (Str.regexp "\\\\\\\\") "\\" s in
s
(** Extract JSON value - simple regex-based parser *)
let extract_json_value json_str key =
let pattern = "\"" ^ key ^ "\"\\s*:\\s*\"\\([^\"]*\\)\"" in
let regex = Str.regexp pattern in
try
let _ = Str.search_forward regex json_str 0 in
Some (Str.matched_group 1 json_str)
with Not_found -> None
(** Extract JSON integer value *)
let extract_json_int json_str key =
let pattern = "\"" ^ key ^ "\"\\s*:\\s*\\([0-9]+\\)" in
let regex = Str.regexp pattern in
try
let _ = Str.search_forward regex json_str 0 in
Some (int_of_string (Str.matched_group 1 json_str))
with Not_found -> None
(* ============================================================================
Credentials Management
============================================================================ *)
(** Global account index set by --account N CLI flag; -1 means not set *)
let cli_account_index = ref (-1)
(** Parse accounts from CSV content, return list of (pk, sk) pairs *)
let parse_accounts_csv content =
let lines = String.split_on_char '\n' content in
List.filter_map (fun line ->
let line = String.trim line in
if String.length line = 0 || line.[0] = '#' then None
else
try
let comma_pos = String.index line ',' in
let pk = String.trim (String.sub line 0 comma_pos) in
let sk = String.trim (String.sub line (comma_pos + 1) (String.length line - comma_pos - 1)) in
if String.length pk > 8 && String.length sk > 8 then
Some (pk, sk)
else None
with Not_found -> None
) lines
(** Load credentials from a specific CSV path at the given account index *)
let load_csv_at path account_index =
if Sys.file_exists path then
try
let content = read_file path in
let accounts = parse_accounts_csv content in
if account_index < List.length accounts then
Some (List.nth accounts account_index)
else None
with _ -> None
else None
(** Get credentials from config file ~/.unsandbox/accounts.csv *)
let get_credentials_from_file ?(account_index=0) () =
let home = try Sys.getenv "HOME" with Not_found -> "." in
let home_csv = Filename.concat home ".unsandbox/accounts.csv" in
match load_csv_at home_csv account_index with
| Some _ as r -> r
| None -> load_csv_at "accounts.csv" account_index
(**
Get API credentials in priority order:
1. Function arguments (public_key, secret_key)
2. --account N (cli_account_index ref) -> accounts.csv row N
3. Environment variables (UNSANDBOX_PUBLIC_KEY, UNSANDBOX_SECRET_KEY)
4. ~/.unsandbox/accounts.csv row 0 (or UNSANDBOX_ACCOUNT env)
5. ./accounts.csv row 0
@param public_key Optional public key override
@param secret_key Optional secret key override
@param account_index Account index in config file (default 0)
@return (public_key, secret_key) tuple
@raise Failure if no credentials found
*)
let get_credentials ?public_key ?secret_key ?(account_index=0) () =
(* Priority 1: Function arguments *)
match (public_key, secret_key) with
| (Some pk, Some sk) -> (pk, sk)
| _ ->
(* Priority 2: --account N CLI flag overrides env vars *)
let effective_index = if !cli_account_index >= 0 then !cli_account_index else account_index in
if !cli_account_index >= 0 then begin
match get_credentials_from_file ~account_index:effective_index () with
| Some (pk, sk) -> (pk, sk)
| None ->
Printf.fprintf stderr "Error: No credentials found for account index %d in accounts.csv\n" !cli_account_index;
exit 1
end else begin
(* Priority 3: Environment variables *)
let env_pk = try Some (Sys.getenv "UNSANDBOX_PUBLIC_KEY") with Not_found -> None in
let env_sk = try Some (Sys.getenv "UNSANDBOX_SECRET_KEY") with Not_found -> None in
match (env_pk, env_sk) with
| (Some pk, Some sk) -> (pk, sk)
| _ ->
(* Priority 4: ~/.unsandbox/accounts.csv (or UNSANDBOX_ACCOUNT index) *)
let default_index =
try int_of_string (String.trim (Sys.getenv "UNSANDBOX_ACCOUNT"))
with Not_found | Failure _ -> 0
in
match get_credentials_from_file ~account_index:default_index () with
| Some (pk, sk) -> (pk, sk)
| None ->
failwith "No credentials found. Set UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY, \
or create ~/.unsandbox/accounts.csv, or pass credentials to function."
end
(* Legacy function for backward compatibility *)
let get_api_keys () =
try
get_credentials ()
with Failure _ ->
(* Fall back to old API key for backwards compat *)
let api_key = try Some (Sys.getenv "UNSANDBOX_API_KEY") with Not_found -> None in
match api_key with
| Some ak -> (ak, ak) (* Use same key for both in legacy mode *)
| None ->
Printf.fprintf stderr "Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY not set\n";
exit 1
let get_api_key () =
let (public_key, _) = get_api_keys () in
public_key
(* ============================================================================
HMAC Authentication
============================================================================ *)
(** HMAC-SHA256 using openssl command *)
let hmac_sha256 secret message =
let cmd = Printf.sprintf "echo -n '%s' | openssl dgst -sha256 -hmac '%s' | awk '{print $2}'"
(Str.global_replace (Str.regexp "'") "'\\''" message)
(Str.global_replace (Str.regexp "'") "'\\''" secret) in
let ic = Unix.open_process_in cmd in
let result = input_line ic in
let _ = Unix.close_process_in ic in
String.trim result
(**
Generate HMAC-SHA256 signature for API request.
Signature = HMAC-SHA256(secret_key, "timestamp:METHOD:path:body")
*)
let make_signature secret_key timestamp method_ path body =
let message = Printf.sprintf "%s:%s:%s:%s" timestamp method_ path body in
hmac_sha256 secret_key message
(** Build authentication headers for HTTP request *)
let build_auth_headers public_key secret_key method_ path body =
let timestamp = string_of_int (int_of_float (Unix.time ())) in
let signature = make_signature secret_key timestamp method_ path body in
Printf.sprintf " -H 'Authorization: Bearer %s' -H 'X-Timestamp: %s' -H 'X-Signature: %s'"
public_key timestamp signature
(** Check for clock drift errors in API response *)
let check_clock_drift response =
let response_lower = String.lowercase_ascii response in
let contains_substring s sub =
try
let _ = Str.search_forward (Str.regexp_string sub) s 0 in
true
with Not_found -> false
in
let has_timestamp = contains_substring response_lower "timestamp" in
let has_401 = contains_substring response_lower "401" in
let has_expired = contains_substring response_lower "expired" in
let has_invalid = contains_substring response_lower "invalid" in
let has_error = has_401 || has_expired || has_invalid in
if has_timestamp && has_error then begin
Printf.fprintf stderr "%sError: Request timestamp expired (must be within 5 minutes of server time)%s\n" red reset;
Printf.fprintf stderr "%sYour computer's clock may have drifted.\n" yellow;
Printf.fprintf stderr "Check your system time and sync with NTP if needed:\n";
Printf.fprintf stderr " Linux: sudo ntpdate -s time.nist.gov\n";
Printf.fprintf stderr " macOS: sudo sntp -sS time.apple.com\n";
Printf.fprintf stderr " Windows: w32tm /resync%s\n" reset;
exit 1
end
(* ============================================================================
HTTP Client
============================================================================ *)
(** Make authenticated POST request to API *)
let api_post ?public_key ?secret_key endpoint json =
let (pk, sk) = get_credentials ?public_key ?secret_key () in
let auth_headers = build_auth_headers pk sk "POST" endpoint json in
let tmp_file = Printf.sprintf "/tmp/un_ocaml_%d.json" (Random.int 999999) in
let oc = open_out tmp_file in
output_string oc json;
close_out oc;
let cmd = Printf.sprintf "curl -s -X POST %s%s -H 'Content-Type: application/json'%s -d @%s"
api_base endpoint auth_headers tmp_file in
let ic = Unix.open_process_in cmd in
let rec read_all acc =
try let line = input_line ic in read_all (acc ^ line ^ "\n")
with End_of_file -> acc
in
let output = read_all "" in
let _ = Unix.close_process_in ic in
Sys.remove tmp_file;
check_clock_drift output;
output
(** Make authenticated GET request to API *)
let api_get ?public_key ?secret_key endpoint =
let (pk, sk) = get_credentials ?public_key ?secret_key () in
let auth_headers = build_auth_headers pk sk "GET" endpoint "" in
let cmd = Printf.sprintf "curl -s %s%s%s" api_base endpoint auth_headers in
let ic = Unix.open_process_in cmd in
let rec read_all acc =
try let line = input_line ic in read_all (acc ^ line ^ "\n")
with End_of_file -> acc
in
let output = read_all "" in
let _ = Unix.close_process_in ic in
check_clock_drift output;
output
(** Make authenticated DELETE request to API *)
let api_delete ?public_key ?secret_key endpoint =
let (pk, sk) = get_credentials ?public_key ?secret_key () in
let auth_headers = build_auth_headers pk sk "DELETE" endpoint "" in
let cmd = Printf.sprintf "curl -s -X DELETE %s%s%s" api_base endpoint auth_headers in
let ic = Unix.open_process_in cmd in
let rec read_all acc =
try let line = input_line ic in read_all (acc ^ line ^ "\n")
with End_of_file -> acc
in
let output = read_all "" in
let _ = Unix.close_process_in ic in
check_clock_drift output;
output
(** Result type for sudo challenge operations *)
type sudo_result = SudoSuccess of string | SudoError of string | SudoCancelled
(** Handle 428 sudo OTP challenge - prompts user for OTP and retries the request *)
let handle_sudo_challenge response method_ endpoint body =
let challenge_id = extract_json_value response "challenge_id" in
Printf.fprintf stderr "%sConfirmation required. Check your email for a one-time code.%s\n" yellow reset;
Printf.fprintf stderr "Enter OTP: ";
flush stderr;
let otp_raw = try input_line stdin with End_of_file -> "" in
let otp = String.trim otp_raw in
if otp = "" then begin
Printf.fprintf stderr "%sError: Operation cancelled%s\n" red reset;
SudoCancelled
end else begin
(* Retry the request with sudo headers *)
let (pk, sk) = get_credentials () in
let auth_headers = build_auth_headers pk sk method_ endpoint body in
let sudo_headers = Printf.sprintf " -H 'X-Sudo-OTP: %s'" otp in
let challenge_header = match challenge_id with
| Some cid -> Printf.sprintf " -H 'X-Sudo-Challenge: %s'" cid
| None -> ""
in
let cmd = match method_ with
| "DELETE" ->
Printf.sprintf "curl -s -X DELETE %s%s%s%s%s" api_base endpoint auth_headers sudo_headers challenge_header
| "POST" ->
let tmp_file = Printf.sprintf "/tmp/un_ocaml_%d.json" (Random.int 999999) in
let oc = open_out tmp_file in
output_string oc body;
close_out oc;
let result = Printf.sprintf "curl -s -X POST %s%s -H 'Content-Type: application/json'%s%s%s -d @%s"
api_base endpoint auth_headers sudo_headers challenge_header tmp_file in
result
| _ ->
Printf.sprintf "curl -s %s%s%s%s%s" api_base endpoint auth_headers sudo_headers challenge_header
in
let ic = Unix.open_process_in cmd in
let rec read_all acc =
try let line = input_line ic in read_all (acc ^ line ^ "\n")
with End_of_file -> acc
in
let retry_output = read_all "" in
let _ = Unix.close_process_in ic in
(* Clean up temp file for POST *)
(if method_ = "POST" then
try Sys.remove (Printf.sprintf "/tmp/un_ocaml_%d.json" (Random.int 999999)) with _ -> ());
let contains_error s =
try let _ = Str.search_forward (Str.regexp_string "\"error\"") s 0 in true
with Not_found -> false
in
if not (contains_error retry_output) then
SudoSuccess retry_output
else
SudoError retry_output
end
(** Make authenticated DELETE request with 428 handling *)
let api_delete_with_sudo ?public_key ?secret_key endpoint =
let (pk, sk) = get_credentials ?public_key ?secret_key () in
let auth_headers = build_auth_headers pk sk "DELETE" endpoint "" in
let cmd = Printf.sprintf "curl -s -w '\\n%%{http_code}' -X DELETE %s%s%s" api_base endpoint auth_headers in
let ic = Unix.open_process_in cmd in
let rec read_all acc =
try let line = input_line ic in read_all (acc ^ line ^ "\n")
with End_of_file -> acc
in
let output = read_all "" in
let _ = Unix.close_process_in ic in
(* Split response and status code *)
let lines = String.split_on_char '\n' output in
let lines_filtered = List.filter (fun s -> String.trim s <> "") lines in
let (body_lines, status_lines) =
let n = List.length lines_filtered in
if n > 0 then
(List.filteri (fun i _ -> i < n - 1) lines_filtered,
[List.nth lines_filtered (n - 1)])
else ([], [])
in
let body = String.concat "\n" body_lines in
let http_code = match status_lines with
| [s] -> (try int_of_string (String.trim s) with _ -> 200)
| _ -> 200
in
check_clock_drift body;
if http_code = 428 then
handle_sudo_challenge body "DELETE" endpoint ""
else
SudoSuccess body
(** Make authenticated POST request with 428 handling *)
let api_post_with_sudo ?public_key ?secret_key endpoint json =
let (pk, sk) = get_credentials ?public_key ?secret_key () in
let auth_headers = build_auth_headers pk sk "POST" endpoint json in
let tmp_file = Printf.sprintf "/tmp/un_ocaml_%d.json" (Random.int 999999) in
let oc = open_out tmp_file in
output_string oc json;
close_out oc;
let cmd = Printf.sprintf "curl -s -w '\\n%%{http_code}' -X POST %s%s -H 'Content-Type: application/json'%s -d @%s"
api_base endpoint auth_headers tmp_file in
let ic = Unix.open_process_in cmd in
let rec read_all acc =
try let line = input_line ic in read_all (acc ^ line ^ "\n")
with End_of_file -> acc
in
let output = read_all "" in
let _ = Unix.close_process_in ic in
Sys.remove tmp_file;
(* Split response and status code *)
let lines = String.split_on_char '\n' output in
let lines_filtered = List.filter (fun s -> String.trim s <> "") lines in
let (body_lines, status_lines) =
let n = List.length lines_filtered in
if n > 0 then
(List.filteri (fun i _ -> i < n - 1) lines_filtered,
[List.nth lines_filtered (n - 1)])
else ([], [])
in
let body = String.concat "\n" body_lines in
let http_code = match status_lines with
| [s] -> (try int_of_string (String.trim s) with _ -> 200)
| _ -> 200
in
check_clock_drift body;
if http_code = 428 then
handle_sudo_challenge body "POST" endpoint json
else
SudoSuccess body
(** Make authenticated POST request to portal *)
let portal_post ?public_key ?secret_key endpoint json =
let (pk, sk) = get_credentials ?public_key ?secret_key () in
let auth_headers = build_auth_headers pk sk "POST" endpoint json in
let tmp_file = Printf.sprintf "/tmp/un_ocaml_%d.json" (Random.int 999999) in
let oc = open_out tmp_file in
output_string oc json;
close_out oc;
let cmd = Printf.sprintf "curl -s -X POST %s%s -H 'Content-Type: application/json'%s -d @%s"
portal_base endpoint auth_headers tmp_file in
let ic = Unix.open_process_in cmd in
let rec read_all acc =
try let line = input_line ic in read_all (acc ^ line ^ "\n")
with End_of_file -> acc
in
let output = read_all "" in
let _ = Unix.close_process_in ic in
Sys.remove tmp_file;
check_clock_drift output;
output
(* ============================================================================
Library API - Core Execution Functions
============================================================================ *)
(**
Execute code synchronously and return results.
@param language Programming language (python, javascript, go, rust, etc.)
@param code Source code to execute
@param opts Execution options (optional)
@param public_key API public key (optional if env vars set)
@param secret_key API secret key (optional if env vars set)
@return Execution result
@example
{[
let result = execute "python" "print('Hello')" () in
print_endline result.stdout
]}
*)
let execute ?public_key ?secret_key ?(opts=default_exec_options) language code =
let env_json = if opts.env = [] then ""
else ",\"env\":{" ^ (String.concat "," (List.map (fun (k, v) ->
Printf.sprintf "\"%s\":\"%s\"" k (escape_json v)) opts.env)) ^ "}"
in
let input_files_json = build_input_files_json opts.input_files in
let artifacts_json = if opts.return_artifacts then ",\"return_artifacts\":true" else "" in
let network_json = Printf.sprintf ",\"network\":\"%s\"" opts.network_mode in
let vcpu_json = Printf.sprintf ",\"vcpu\":%d" opts.vcpu in
let ttl_json = Printf.sprintf ",\"ttl\":%d" opts.ttl in
let json = Printf.sprintf "{\"language\":\"%s\",\"code\":\"%s\"%s%s%s%s%s%s}"
language (escape_json code) env_json input_files_json artifacts_json network_json vcpu_json ttl_json in
let response = api_post ?public_key ?secret_key "/execute" json in
let stdout_val = match extract_json_value response "stdout" with Some s -> unescape_json s | None -> "" in
let stderr_val = match extract_json_value response "stderr" with Some s -> unescape_json s | None -> "" in
let exit_code = match extract_json_int response "exit_code" with Some i -> i | None -> 0 in
let job_id = extract_json_value response "job_id" in
{ success = (exit_code = 0); stdout = stdout_val; stderr = stderr_val; exit_code; job_id }
(**
Execute code asynchronously. Returns immediately with job_id for polling.
@param language Programming language
@param code Source code to execute
@param opts Execution options (optional)
@param public_key API public key (optional)
@param secret_key API secret key (optional)
@return Job status with job_id
@example
{[
let job = execute_async "python" long_code () in
let result = wait job.job_id () in
print_endline result.stdout
]}
*)
let execute_async ?public_key ?secret_key ?(opts=default_exec_options) language code =
let env_json = if opts.env = [] then ""
else ",\"env\":{" ^ (String.concat "," (List.map (fun (k, v) ->
Printf.sprintf "\"%s\":\"%s\"" k (escape_json v)) opts.env)) ^ "}"
in
let input_files_json = build_input_files_json opts.input_files in
let artifacts_json = if opts.return_artifacts then ",\"return_artifacts\":true" else "" in
let network_json = Printf.sprintf ",\"network\":\"%s\"" opts.network_mode in
let vcpu_json = Printf.sprintf ",\"vcpu\":%d" opts.vcpu in
let ttl_json = Printf.sprintf ",\"ttl\":%d" opts.ttl in
let json = Printf.sprintf "{\"language\":\"%s\",\"code\":\"%s\"%s%s%s%s%s%s}"
language (escape_json code) env_json input_files_json artifacts_json network_json vcpu_json ttl_json in
let response = api_post ?public_key ?secret_key "/execute/async" json in
let job_id = match extract_json_value response "job_id" with Some s -> s | None -> "" in
let status = match extract_json_value response "status" with Some s -> s | None -> "pending" in
{ job_id; status; result = None }
(**
Execute code with automatic language detection from shebang.
@param code Source code with shebang (e.g., #!/usr/bin/env python3)
@param opts Execution options (optional)
@param public_key API public key (optional)
@param secret_key API secret key (optional)
@return Execution result
*)
let run ?public_key ?secret_key ?(opts=default_exec_options) code =
let endpoint = Printf.sprintf "/run?ttl=%d&network_mode=%s" opts.ttl opts.network_mode in
let (pk, sk) = get_credentials ?public_key ?secret_key () in
let auth_headers = build_auth_headers pk sk "POST" endpoint code in
let tmp_file = Printf.sprintf "/tmp/un_ocaml_%d.txt" (Random.int 999999) in
let oc = open_out tmp_file in
output_string oc code;
close_out oc;
let cmd = Printf.sprintf "curl -s -X POST %s%s -H 'Content-Type: text/plain'%s --data-binary @%s"
api_base endpoint auth_headers tmp_file in
let ic = Unix.open_process_in cmd in
let rec read_all acc =
try let line = input_line ic in read_all (acc ^ line ^ "\n")
with End_of_file -> acc
in
let response = read_all "" in
let _ = Unix.close_process_in ic in
Sys.remove tmp_file;
check_clock_drift response;
let stdout_val = match extract_json_value response "stdout" with Some s -> unescape_json s | None -> "" in
let stderr_val = match extract_json_value response "stderr" with Some s -> unescape_json s | None -> "" in
let exit_code = match extract_json_int response "exit_code" with Some i -> i | None -> 0 in
let job_id = extract_json_value response "job_id" in
{ success = (exit_code = 0); stdout = stdout_val; stderr = stderr_val; exit_code; job_id }
(**
Execute code asynchronously with automatic language detection.
@param code Source code with shebang
@param opts Execution options (optional)
@param public_key API public key (optional)
@param secret_key API secret key (optional)
@return Job status with job_id
*)
let run_async ?public_key ?secret_key ?(opts=default_exec_options) code =
let endpoint = Printf.sprintf "/run/async?ttl=%d&network_mode=%s" opts.ttl opts.network_mode in
let (pk, sk) = get_credentials ?public_key ?secret_key () in
let auth_headers = build_auth_headers pk sk "POST" endpoint code in
let tmp_file = Printf.sprintf "/tmp/un_ocaml_%d.txt" (Random.int 999999) in
let oc = open_out tmp_file in
output_string oc code;
close_out oc;
let cmd = Printf.sprintf "curl -s -X POST %s%s -H 'Content-Type: text/plain'%s --data-binary @%s"
api_base endpoint auth_headers tmp_file in
let ic = Unix.open_process_in cmd in
let rec read_all acc =
try let line = input_line ic in read_all (acc ^ line ^ "\n")
with End_of_file -> acc
in
let response = read_all "" in
let _ = Unix.close_process_in ic in
Sys.remove tmp_file;
check_clock_drift response;
let job_id = match extract_json_value response "job_id" with Some s -> s | None -> "" in
let status = match extract_json_value response "status" with Some s -> s | None -> "pending" in
{ job_id; status; result = None }
(* ============================================================================
Library API - Job Management
============================================================================ *)
(** Polling delays (ms) - exponential backoff *)
let poll_delays = [|300; 450; 700; 900; 650; 1600; 2000|]
(**
Get job status and results.
@param job_id Job ID from execute_async or run_async
@param public_key API public key (optional)
@param secret_key API secret key (optional)
@return Job status
*)
let get_job ?public_key ?secret_key job_id =
let response = api_get ?public_key ?secret_key (Printf.sprintf "/jobs/%s" job_id) in
let status = match extract_json_value response "status" with Some s -> s | None -> "unknown" in
let result = if status = "completed" || status = "failed" then
let stdout_val = match extract_json_value response "stdout" with Some s -> unescape_json s | None -> "" in
let stderr_val = match extract_json_value response "stderr" with Some s -> unescape_json s | None -> "" in
let exit_code = match extract_json_int response "exit_code" with Some i -> i | None -> 0 in
Some { success = (exit_code = 0); stdout = stdout_val; stderr = stderr_val; exit_code; job_id = Some job_id }
else None in
{ job_id; status; result }
(**
Wait for job completion with exponential backoff polling.
@param job_id Job ID from execute_async or run_async
@param max_polls Maximum number of poll attempts (default 100)
@param public_key API public key (optional)
@param secret_key API secret key (optional)
@return Final execution result
@raise Failure if max polls exceeded or job failed
*)
let wait ?public_key ?secret_key ?(max_polls=100) job_id =
let terminal_states = ["completed"; "failed"; "timeout"; "cancelled"] in
let rec poll i =
if i >= max_polls then
failwith (Printf.sprintf "Max polls (%d) exceeded for job %s" max_polls job_id)
else begin
let delay_idx = min i (Array.length poll_delays - 1) in
Unix.sleepf (float_of_int poll_delays.(delay_idx) /. 1000.0);
let job = get_job ?public_key ?secret_key job_id in
if List.mem job.status terminal_states then
match job.result with
| Some result -> result
| None -> { success = false; stdout = ""; stderr = ""; exit_code = 1; job_id = Some job_id }
else
poll (i + 1)
end
in
poll 0
(**
Cancel a running job.
@param job_id Job ID to cancel
@param public_key API public key (optional)
@param secret_key API secret key (optional)
@return Partial result with output collected before cancellation
*)
let cancel_job ?public_key ?secret_key job_id =
let response = api_delete ?public_key ?secret_key (Printf.sprintf "/jobs/%s" job_id) in
let stdout_val = match extract_json_value response "stdout" with Some s -> unescape_json s | None -> "" in
let stderr_val = match extract_json_value response "stderr" with Some s -> unescape_json s | None -> "" in
let exit_code = match extract_json_int response "exit_code" with Some i -> i | None -> 137 in
{ success = false; stdout = stdout_val; stderr = stderr_val; exit_code; job_id = Some job_id }
(**
List all active jobs for this API key.
@param public_key API public key (optional)
@param secret_key API secret key (optional)
@return List of job status records
*)
let list_jobs ?public_key ?secret_key () =
let response = api_get ?public_key ?secret_key "/jobs" in
(* Return raw response for now - proper parsing would require JSON library *)
response
(* ============================================================================
Library API - Image Generation
============================================================================ *)
(**
Generate images from text prompt.
@param prompt Text description of the image to generate
@param model Model to use (optional)
@param size Image size (default "1024x1024")
@param quality "standard" or "hd" (default "standard")
@param n Number of images to generate (default 1)
@param public_key API public key (optional)
@param secret_key API secret key (optional)
@return JSON response with images
*)
let image ?public_key ?secret_key ?(model="") ?(size="1024x1024") ?(quality="standard") ?(n=1) prompt =
let model_json = if model = "" then "" else Printf.sprintf ",\"model\":\"%s\"" model in
let json = Printf.sprintf "{\"prompt\":\"%s\",\"size\":\"%s\",\"quality\":\"%s\",\"n\":%d%s}"
(escape_json prompt) size quality n model_json in
api_post ?public_key ?secret_key "/image" json
(* ============================================================================
Library API - Languages
============================================================================ *)
(**
Get list of supported programming languages.
Uses cached data if available and not expired (1 hour TTL).
@param public_key API public key (optional)
@param secret_key API secret key (optional)
@return JSON response with languages list
*)
let languages ?public_key ?secret_key () =
match load_languages_cache () with
| Some cached -> cached
| None ->
let response = api_get ?public_key ?secret_key "/languages" in
(* Extract and cache the languages array *)
let langs_pattern = "\"languages\":\\s*\\[\\([^]]*\\)\\]" in
let langs_regex = Str.regexp langs_pattern in
(try
let _ = Str.search_forward langs_regex response 0 in
let langs_str = Str.matched_group 1 response in
let languages_json = "[" ^ langs_str ^ "]" in
save_languages_cache languages_json
with Not_found -> ());
response
(* ============================================================================
Client Module
============================================================================ *)
(**
Client module with stored credentials for convenient API access.
@example
{[
let client = Client.create ~public_key:"unsb-pk-..." ~secret_key:"unsb-sk-..." () in
let result = Client.execute client "python" "print('Hello')" in
print_endline result.stdout
]}
*)
module Client = struct
(** Client type with stored credentials *)
type t = {
public_key: string;
secret_key: string;
}
(**
Create a new client with credentials.
@param public_key API public key (optional - uses env/config if not provided)
@param secret_key API secret key (optional - uses env/config if not provided)
@param account_index Account index in config file (default 0)
@return Client instance
*)
let create ?public_key ?secret_key ?(account_index=0) () =
let (pk, sk) = get_credentials ?public_key ?secret_key ~account_index () in
{ public_key = pk; secret_key = sk }
(** Execute code synchronously *)
let execute client ?opts language code =
execute ~public_key:client.public_key ~secret_key:client.secret_key ?opts language code
(** Execute code asynchronously *)
let execute_async client ?opts language code =
execute_async ~public_key:client.public_key ~secret_key:client.secret_key ?opts language code
(** Execute with auto-detect language *)
let run client ?opts code =
run ~public_key:client.public_key ~secret_key:client.secret_key ?opts code
(** Execute async with auto-detect language *)
let run_async client ?opts code =
run_async ~public_key:client.public_key ~secret_key:client.secret_key ?opts code
(** Get job status *)
let get_job client job_id =
get_job ~public_key:client.public_key ~secret_key:client.secret_key job_id
(** Wait for job completion *)
let wait client ?max_polls job_id =
wait ~public_key:client.public_key ~secret_key:client.secret_key ?max_polls job_id
(** Cancel a job *)
let cancel_job client job_id =
cancel_job ~public_key:client.public_key ~secret_key:client.secret_key job_id
(** List active jobs *)
let list_jobs client =
list_jobs ~public_key:client.public_key ~secret_key:client.secret_key ()
(** Generate image *)
let image client ?model ?size ?quality ?n prompt =
image ~public_key:client.public_key ~secret_key:client.secret_key ?model ?size ?quality ?n prompt
(** Get supported languages *)
let languages client =
languages ~public_key:client.public_key ~secret_key:client.secret_key ()
end
(* ============================================================================
Library API - Sessions (9)
============================================================================ *)
(** SDK version *)
let sdk_version = "4.2.0"
(** Return the SDK version *)
let version () = sdk_version
(** Check API health *)
let health_check () =
let cmd = Printf.sprintf "curl -s -o /dev/null -w '%%{http_code}' %s/health" api_base in
let ic = Unix.open_process_in cmd in
let status = try input_line ic with End_of_file -> "0" in
let _ = Unix.close_process_in ic in
String.trim status = "200"
(** Generate HMAC-SHA256 signature *)
let hmac_sign secret message = hmac_sha256 secret message
(** Detect language from filename *)
let detect_language filename =
let ext = get_extension filename in
ext_to_lang ext
(** List all sessions *)
let session_list ?public_key ?secret_key () =
api_get ?public_key ?secret_key "/sessions"
(** Get session details *)
let session_get ?public_key ?secret_key session_id =
api_get ?public_key ?secret_key (Printf.sprintf "/sessions/%s" session_id)
(** Create a new session *)
let session_create ?public_key ?secret_key ?(shell="bash") ?network ?vcpu () =
let network_json = match network with Some n -> Printf.sprintf ",\"network\":\"%s\"" n | None -> "" in
let vcpu_json = match vcpu with Some v -> Printf.sprintf ",\"vcpu\":%d" v | None -> "" in
let json = Printf.sprintf "{\"shell\":\"%s\"%s%s}" shell network_json vcpu_json in
let response = api_post ?public_key ?secret_key "/sessions" json in
extract_json_value response "id"
(** Destroy a session *)
let session_destroy ?public_key ?secret_key session_id =
let response = api_delete ?public_key ?secret_key (Printf.sprintf "/sessions/%s" session_id) in
not (String.length response > 0 && Str.string_match (Str.regexp ".*\"error\"") response 0)
(** Freeze a session *)
let session_freeze ?public_key ?secret_key session_id =
let response = api_post ?public_key ?secret_key (Printf.sprintf "/sessions/%s/freeze" session_id) "{}" in
not (String.length response > 0 && Str.string_match (Str.regexp ".*\"error\"") response 0)
(** Unfreeze a session *)
let session_unfreeze ?public_key ?secret_key session_id =
let response = api_post ?public_key ?secret_key (Printf.sprintf "/sessions/%s/unfreeze" session_id) "{}" in
not (String.length response > 0 && Str.string_match (Str.regexp ".*\"error\"") response 0)
(** Boost session resources *)
let session_boost ?public_key ?secret_key session_id vcpu =
let (pk, sk) = get_credentials ?public_key ?secret_key () in
let json = Printf.sprintf "{\"vcpu\":%d}" vcpu in
let endpoint = Printf.sprintf "/sessions/%s" session_id in
let auth_headers = build_auth_headers pk sk "PATCH" endpoint json in
let tmp_file = Printf.sprintf "/tmp/un_ocaml_%d.json" (Random.int 999999) in
let oc = open_out tmp_file in
output_string oc json;
close_out oc;
let cmd = Printf.sprintf "curl -s -X PATCH %s%s -H 'Content-Type: application/json'%s -d @%s"
api_base endpoint auth_headers tmp_file in
let ic = Unix.open_process_in cmd in
let rec read_all acc =
try let line = input_line ic in read_all (acc ^ line ^ "\n")
with End_of_file -> acc
in
let response = read_all "" in
let _ = Unix.close_process_in ic in
Sys.remove tmp_file;
not (String.length response > 0 && Str.string_match (Str.regexp ".*\"error\"") response 0)
(** Unboost session *)
let session_unboost ?public_key ?secret_key session_id =
session_boost ?public_key ?secret_key session_id 1
(** Execute a command in a session *)
let session_execute ?public_key ?secret_key session_id command =
let json = Printf.sprintf "{\"command\":\"%s\"}" (escape_json command) in
let response = api_post ?public_key ?secret_key (Printf.sprintf "/sessions/%s/execute" session_id) json in
let stdout_val = match extract_json_value response "stdout" with Some s -> unescape_json s | None -> "" in
let stderr_val = match extract_json_value response "stderr" with Some s -> unescape_json s | None -> "" in
let exit_code = match extract_json_int response "exit_code" with Some i -> i | None -> 0 in
{ success = (exit_code = 0); stdout = stdout_val; stderr = stderr_val; exit_code; job_id = None }
(* ============================================================================
Library API - Services (17)
============================================================================ *)
(** List all services *)
let service_list ?public_key ?secret_key () =
api_get ?public_key ?secret_key "/services"
(** Get service details *)
let service_get ?public_key ?secret_key service_id =
api_get ?public_key ?secret_key (Printf.sprintf "/services/%s" service_id)
(** Create a new service *)
let service_create ?public_key ?secret_key name ?ports ?bootstrap ?network ?vcpu () =
let ports_json = match ports with Some p -> Printf.sprintf ",\"ports\":[%s]" p | None -> "" in
let bootstrap_json = match bootstrap with Some b -> Printf.sprintf ",\"bootstrap\":\"%s\"" (escape_json b) | None -> "" in
let network_json = match network with Some n -> Printf.sprintf ",\"network\":\"%s\"" n | None -> "" in
let vcpu_json = match vcpu with Some v -> Printf.sprintf ",\"vcpu\":%d" v | None -> "" in
let json = Printf.sprintf "{\"name\":\"%s\"%s%s%s%s}" (escape_json name) ports_json bootstrap_json network_json vcpu_json in
let response = api_post ?public_key ?secret_key "/services" json in
extract_json_value response "id"
(** Destroy a service *)
let service_destroy ?public_key ?secret_key service_id =
match api_delete_with_sudo ?public_key ?secret_key (Printf.sprintf "/services/%s" service_id) with
| SudoSuccess _ -> true
| _ -> false
(** Freeze a service *)
let service_freeze ?public_key ?secret_key service_id =
let response = api_post ?public_key ?secret_key (Printf.sprintf "/services/%s/freeze" service_id) "{}" in
not (String.length response > 0 && Str.string_match (Str.regexp ".*\"error\"") response 0)
(** Unfreeze a service *)
let service_unfreeze ?public_key ?secret_key service_id =
let response = api_post ?public_key ?secret_key (Printf.sprintf "/services/%s/unfreeze" service_id) "{}" in
not (String.length response > 0 && Str.string_match (Str.regexp ".*\"error\"") response 0)
(** Lock a service *)
let service_lock ?public_key ?secret_key service_id =
let response = api_post ?public_key ?secret_key (Printf.sprintf "/services/%s/lock" service_id) "{}" in
not (String.length response > 0 && Str.string_match (Str.regexp ".*\"error\"") response 0)
(** Unlock a service *)
let service_unlock ?public_key ?secret_key service_id =
let response = api_post ?public_key ?secret_key (Printf.sprintf "/services/%s/unlock" service_id) "{}" in
not (String.length response > 0 && Str.string_match (Str.regexp ".*\"error\"") response 0)
(** Set unfreeze-on-demand for a service *)
let service_set_unfreeze_on_demand ?public_key ?secret_key service_id enabled =
let (pk, sk) = get_credentials ?public_key ?secret_key () in
let enabled_str = if enabled then "true" else "false" in
let json = Printf.sprintf "{\"unfreeze_on_demand\":%s}" enabled_str in
let endpoint = Printf.sprintf "/services/%s" service_id in
let auth_headers = build_auth_headers pk sk "PATCH" endpoint json in
let tmp_file = Printf.sprintf "/tmp/un_ocaml_%d.json" (Random.int 999999) in
let oc = open_out tmp_file in
output_string oc json;
close_out oc;
let cmd = Printf.sprintf "curl -s -X PATCH %s%s -H 'Content-Type: application/json'%s -d @%s"
api_base endpoint auth_headers tmp_file in
let ic = Unix.open_process_in cmd in
let rec read_all acc =
try let line = input_line ic in read_all (acc ^ line ^ "\n")
with End_of_file -> acc
in
let response = read_all "" in
let _ = Unix.close_process_in ic in
Sys.remove tmp_file;
not (String.length response > 0 && Str.string_match (Str.regexp ".*\"error\"") response 0)
(** Redeploy a service *)
let service_redeploy ?public_key ?secret_key service_id ?bootstrap () =
let bootstrap_json = match bootstrap with Some b -> Printf.sprintf "\"bootstrap\":\"%s\"" (escape_json b) | None -> "" in
let json = "{" ^ bootstrap_json ^ "}" in
let response = api_post ?public_key ?secret_key (Printf.sprintf "/services/%s/redeploy" service_id) json in
not (String.length response > 0 && Str.string_match (Str.regexp ".*\"error\"") response 0)
(** Get service logs *)
let service_logs ?public_key ?secret_key ?(all_logs=false) service_id =
let endpoint = if all_logs
then Printf.sprintf "/services/%s/logs?all=true" service_id
else Printf.sprintf "/services/%s/logs" service_id
in
api_get ?public_key ?secret_key endpoint
(** Execute a command in a service *)
let service_execute ?public_key ?secret_key ?timeout_ms service_id command =
let timeout_json = match timeout_ms with Some t -> Printf.sprintf ",\"timeout_ms\":%d" t | None -> "" in
let json = Printf.sprintf "{\"command\":\"%s\"%s}" (escape_json command) timeout_json in
let response = api_post ?public_key ?secret_key (Printf.sprintf "/services/%s/execute" service_id) json in
let stdout_val = match extract_json_value response "stdout" with Some s -> unescape_json s | None -> "" in
let stderr_val = match extract_json_value response "stderr" with Some s -> unescape_json s | None -> "" in
let exit_code = match extract_json_int response "exit_code" with Some i -> i | None -> 0 in
{ success = (exit_code = 0); stdout = stdout_val; stderr = stderr_val; exit_code; job_id = None }
(** Get service environment vault *)
let service_env_get_lib ?public_key ?secret_key service_id =
api_get ?public_key ?secret_key (Printf.sprintf "/services/%s/env" service_id)
(** Set service environment vault *)
let service_env_set_lib ?public_key ?secret_key service_id env_content =
let (pk, sk) = get_credentials ?public_key ?secret_key () in
let endpoint = Printf.sprintf "/services/%s/env" service_id in
let auth_headers = build_auth_headers pk sk "PUT" endpoint env_content in
let tmp_file = Printf.sprintf "/tmp/un_ocaml_%d.txt" (Random.int 999999) in
let oc = open_out tmp_file in
output_string oc env_content;
close_out oc;
let cmd = Printf.sprintf "curl -s -o /dev/null -w '%%{http_code}' -X PUT %s%s -H 'Content-Type: text/plain'%s -d @%s"
api_base endpoint auth_headers tmp_file in
let ic = Unix.open_process_in cmd in
let status = try input_line ic with End_of_file -> "0" in
let _ = Unix.close_process_in ic in
Sys.remove tmp_file;
let code = int_of_string (String.trim status) in
code >= 200 && code < 300
(** Delete service environment vault *)
let service_env_delete_lib ?public_key ?secret_key service_id =
let response = api_delete ?public_key ?secret_key (Printf.sprintf "/services/%s/env" service_id) in
not (String.length response > 0 && Str.string_match (Str.regexp ".*\"error\"") response 0)
(** Export service environment vault *)
let service_env_export_lib ?public_key ?secret_key service_id =
let response = api_post ?public_key ?secret_key (Printf.sprintf "/services/%s/env/export" service_id) "{}" in
match extract_json_value response "content" with
| Some content -> unescape_json content
| None -> ""
(** Resize a service *)
let service_resize ?public_key ?secret_key service_id vcpu =
let (pk, sk) = get_credentials ?public_key ?secret_key () in
let json = Printf.sprintf "{\"vcpu\":%d}" vcpu in
let endpoint = Printf.sprintf "/services/%s" service_id in
let auth_headers = build_auth_headers pk sk "PATCH" endpoint json in
let tmp_file = Printf.sprintf "/tmp/un_ocaml_%d.json" (Random.int 999999) in
let oc = open_out tmp_file in
output_string oc json;
close_out oc;
let cmd = Printf.sprintf "curl -s -X PATCH %s%s -H 'Content-Type: application/json'%s -d @%s"
api_base endpoint auth_headers tmp_file in
let ic = Unix.open_process_in cmd in
let rec read_all acc =
try let line = input_line ic in read_all (acc ^ line ^ "\n")
with End_of_file -> acc
in
let response = read_all "" in
let _ = Unix.close_process_in ic in
Sys.remove tmp_file;
not (String.length response > 0 && Str.string_match (Str.regexp ".*\"error\"") response 0)
(* ============================================================================
Library API - Snapshots (9)
============================================================================ *)
(** List all snapshots *)
let snapshot_list ?public_key ?secret_key () =
api_get ?public_key ?secret_key "/snapshots"
(** Get snapshot details *)
let snapshot_get ?public_key ?secret_key snapshot_id =
api_get ?public_key ?secret_key (Printf.sprintf "/snapshots/%s" snapshot_id)
(** Create a snapshot of a session *)
let snapshot_session ?public_key ?secret_key ?name ?(hot=false) session_id =
let name_json = match name with Some n -> Printf.sprintf "\"name\":\"%s\"," (escape_json n) | None -> "" in
let hot_json = if hot then "\"hot\":true" else "\"hot\":false" in
let json = "{" ^ name_json ^ hot_json ^ "}" in
let response = api_post ?public_key ?secret_key (Printf.sprintf "/sessions/%s/snapshot" session_id) json in
extract_json_value response "id"
(** Create a snapshot of a service *)
let snapshot_service ?public_key ?secret_key ?name ?(hot=false) service_id =
let name_json = match name with Some n -> Printf.sprintf "\"name\":\"%s\"," (escape_json n) | None -> "" in
let hot_json = if hot then "\"hot\":true" else "\"hot\":false" in
let json = "{" ^ name_json ^ hot_json ^ "}" in
let response = api_post ?public_key ?secret_key (Printf.sprintf "/services/%s/snapshot" service_id) json in
extract_json_value response "id"
(** Restore from a snapshot *)
let snapshot_restore ?public_key ?secret_key snapshot_id =
let response = api_post ?public_key ?secret_key (Printf.sprintf "/snapshots/%s/restore" snapshot_id) "{}" in
extract_json_value response "id"
(** Delete a snapshot *)
let snapshot_delete ?public_key ?secret_key snapshot_id =
match api_delete_with_sudo ?public_key ?secret_key (Printf.sprintf "/snapshots/%s" snapshot_id) with
| SudoSuccess _ -> true
| _ -> false
(** Lock a snapshot *)
let snapshot_lock ?public_key ?secret_key snapshot_id =
let response = api_post ?public_key ?secret_key (Printf.sprintf "/snapshots/%s/lock" snapshot_id) "{}" in
not (String.length response > 0 && Str.string_match (Str.regexp ".*\"error\"") response 0)
(** Unlock a snapshot *)
let snapshot_unlock ?public_key ?secret_key snapshot_id =
let response = api_post ?public_key ?secret_key (Printf.sprintf "/snapshots/%s/unlock" snapshot_id) "{}" in
not (String.length response > 0 && Str.string_match (Str.regexp ".*\"error\"") response 0)
(** Clone a snapshot to create a new session or service *)
let snapshot_clone ?public_key ?secret_key snapshot_id ~clone_type ?name ?ports ?shell () =
let type_json = Printf.sprintf "\"type\":\"%s\"" clone_type in
let name_json = match name with Some n -> Printf.sprintf ",\"name\":\"%s\"" (escape_json n) | None -> "" in
let ports_json = match ports with Some p -> Printf.sprintf ",\"ports\":[%s]" p | None -> "" in
let shell_json = match shell with Some s -> Printf.sprintf ",\"shell\":\"%s\"" s | None -> "" in
let json = "{" ^ type_json ^ name_json ^ ports_json ^ shell_json ^ "}" in
let response = api_post ?public_key ?secret_key (Printf.sprintf "/snapshots/%s/clone" snapshot_id) json in
extract_json_value response "id"
(* ============================================================================
Library API - Images (13)
============================================================================ *)
(** List images *)
let image_list ?public_key ?secret_key ?filter () =
let endpoint = match filter with Some f -> Printf.sprintf "/images?filter=%s" f | None -> "/images" in
api_get ?public_key ?secret_key endpoint
(** Get image details *)
let image_get ?public_key ?secret_key image_id =
api_get ?public_key ?secret_key (Printf.sprintf "/images/%s" image_id)
(** Publish an image *)
let image_publish ?public_key ?secret_key source_type source_id ?name ?description () =
let name_json = match name with Some n -> Printf.sprintf ",\"name\":\"%s\"" (escape_json n) | None -> "" in
let desc_json = match description with Some d -> Printf.sprintf ",\"description\":\"%s\"" (escape_json d) | None -> "" in
let json = Printf.sprintf "{\"source_type\":\"%s\",\"source_id\":\"%s\"%s%s}" source_type source_id name_json desc_json in
let response = api_post ?public_key ?secret_key "/images/publish" json in
extract_json_value response "id"
(** Delete an image *)
let image_delete ?public_key ?secret_key image_id =
match api_delete_with_sudo ?public_key ?secret_key (Printf.sprintf "/images/%s" image_id) with
| SudoSuccess _ -> true
| _ -> false
(** Lock an image *)
let image_lock ?public_key ?secret_key image_id =
let response = api_post ?public_key ?secret_key (Printf.sprintf "/images/%s/lock" image_id) "{}" in
not (String.length response > 0 && Str.string_match (Str.regexp ".*\"error\"") response 0)
(** Unlock an image *)
let image_unlock ?public_key ?secret_key image_id =
match api_post_with_sudo ?public_key ?secret_key (Printf.sprintf "/images/%s/unlock" image_id) "{}" with
| SudoSuccess _ -> true
| _ -> false
(** Set image visibility *)
let image_set_visibility ?public_key ?secret_key image_id visibility =
let json = Printf.sprintf "{\"visibility\":\"%s\"}" visibility in
let response = api_post ?public_key ?secret_key (Printf.sprintf "/images/%s/visibility" image_id) json in
not (String.length response > 0 && Str.string_match (Str.regexp ".*\"error\"") response 0)
(** Grant access to an image *)
let image_grant_access ?public_key ?secret_key image_id trusted_api_key =
let json = Printf.sprintf "{\"api_key\":\"%s\"}" trusted_api_key in
let response = api_post ?public_key ?secret_key (Printf.sprintf "/images/%s/access/grant" image_id) json in
not (String.length response > 0 && Str.string_match (Str.regexp ".*\"error\"") response 0)
(** Revoke access to an image *)
let image_revoke_access ?public_key ?secret_key image_id trusted_api_key =
let json = Printf.sprintf "{\"api_key\":\"%s\"}" trusted_api_key in
let response = api_post ?public_key ?secret_key (Printf.sprintf "/images/%s/access/revoke" image_id) json in
not (String.length response > 0 && Str.string_match (Str.regexp ".*\"error\"") response 0)
(** List trusted API keys for an image *)
let image_list_trusted ?public_key ?secret_key image_id =
api_get ?public_key ?secret_key (Printf.sprintf "/images/%s/access" image_id)
(** Transfer image ownership *)
let image_transfer ?public_key ?secret_key image_id to_api_key =
let json = Printf.sprintf "{\"to_api_key\":\"%s\"}" to_api_key in
let response = api_post ?public_key ?secret_key (Printf.sprintf "/images/%s/transfer" image_id) json in
not (String.length response > 0 && Str.string_match (Str.regexp ".*\"error\"") response 0)
(** Spawn a service from an image *)
let image_spawn ?public_key ?secret_key image_id ?name ?ports ?bootstrap ?network () =
let name_json = match name with Some n -> Printf.sprintf "\"name\":\"%s\"" (escape_json n) | None -> "" in
let ports_json = match ports with Some p -> Printf.sprintf "%s\"ports\":[%s]" (if name_json <> "" then "," else "") p | None -> "" in
let bootstrap_json = match bootstrap with Some b -> Printf.sprintf ",\"bootstrap\":\"%s\"" (escape_json b) | None -> "" in
let network_json = match network with Some n -> Printf.sprintf ",\"network\":\"%s\"" n | None -> "" in
let json = "{" ^ name_json ^ ports_json ^ bootstrap_json ^ network_json ^ "}" in
let response = api_post ?public_key ?secret_key (Printf.sprintf "/images/%s/spawn" image_id) json in
extract_json_value response "id"
(** Clone an image *)
let image_clone ?public_key ?secret_key image_id ?name ?description () =
let name_json = match name with Some n -> Printf.sprintf "\"name\":\"%s\"" (escape_json n) | None -> "" in
let desc_json = match description with Some d -> Printf.sprintf "%s\"description\":\"%s\"" (if name_json <> "" then "," else "") (escape_json d) | None -> "" in
let json = "{" ^ name_json ^ desc_json ^ "}" in
let response = api_post ?public_key ?secret_key (Printf.sprintf "/images/%s/clone" image_id) json in
extract_json_value response "id"
(** Validate API keys *)
let validate_keys ?public_key ?secret_key () =
portal_post ?public_key ?secret_key "/keys/validate" "{}"
(* ============================================================================
CLI - Legacy curl-based functions for CLI
============================================================================ *)
let curl_post api_key endpoint json =
let (public_key, secret_key) = get_api_keys () in
api_post ~public_key ~secret_key endpoint json
let curl_get api_key endpoint =
let (public_key, secret_key) = get_api_keys () in
api_get ~public_key ~secret_key endpoint
let curl_delete api_key endpoint =
let (public_key, secret_key) = get_api_keys () in
api_delete ~public_key ~secret_key endpoint
let portal_curl_post api_key endpoint json =
let (public_key, secret_key) = get_api_keys () in
portal_post ~public_key ~secret_key endpoint json
let curl_put_text endpoint body =
let (public_key, secret_key) = get_api_keys () in
let auth_headers = build_auth_headers public_key secret_key "PUT" endpoint body in
let tmp_file = Printf.sprintf "/tmp/un_ocaml_%d.txt" (Random.int 999999) in
let oc = open_out tmp_file in
output_string oc body;
close_out oc;
let cmd = Printf.sprintf "curl -s -o /dev/null -w '%%{http_code}' -X PUT %s%s -H 'Content-Type: text/plain'%s -d @%s"
api_base endpoint auth_headers tmp_file in
let ic = Unix.open_process_in cmd in
let status = try input_line ic with End_of_file -> "0" in
let _ = Unix.close_process_in ic in
Sys.remove tmp_file;
let code = int_of_string (String.trim status) in
code >= 200 && code < 300
let max_env_content_size = 65536
let read_env_file path =
if not (Sys.file_exists path) then begin
Printf.fprintf stderr "%sError: Env file not found: %s%s\n" red path reset;
exit 1
end;
read_file path
let build_env_content envs env_file =
let lines = ref envs in
(match env_file with
| Some path ->
let content = read_env_file path in
let file_lines = String.split_on_char '\n' content in
List.iter (fun line ->
let trimmed = String.trim line in
if String.length trimmed > 0 && trimmed.[0] <> '#' then
lines := trimmed :: !lines
) file_lines
| None -> ());
String.concat "\n" (List.rev !lines)
let service_env_status service_id =
let api_key = get_api_key () in
curl_get api_key (Printf.sprintf "/services/%s/env" service_id)
let service_env_set service_id env_content =
if String.length env_content > max_env_content_size then begin
Printf.fprintf stderr "%sError: Env content exceeds maximum size of 64KB%s\n" red reset;
false
end else
curl_put_text (Printf.sprintf "/services/%s/env" service_id) env_content
let service_env_export service_id =
let api_key = get_api_key () in
let (public_key, secret_key) = get_api_keys () in
let endpoint = Printf.sprintf "/services/%s/env/export" service_id in
let auth_headers = build_auth_headers public_key secret_key "POST" endpoint "{}" in
let tmp_file = Printf.sprintf "/tmp/un_ocaml_%d.json" (Random.int 999999) in
let oc = open_out tmp_file in
output_string oc "{}";
close_out oc;
let cmd = Printf.sprintf "curl -s -X POST %s%s -H 'Content-Type: application/json'%s -d @%s"
api_base endpoint auth_headers tmp_file in
let ic = Unix.open_process_in cmd in
let rec read_all acc =
try let line = input_line ic in read_all (acc ^ line ^ "\n")
with End_of_file -> acc
in
let response = read_all "" in
let _ = Unix.close_process_in ic in
Sys.remove tmp_file;
response
let service_env_delete service_id =
let api_key = get_api_key () in
try
let _ = curl_delete api_key (Printf.sprintf "/services/%s/env" service_id) in
true
with _ -> false
let service_env_command action target envs env_file =
match action with
| "status" ->
(match target with
| Some sid ->
let response = service_env_status sid in
let has_vault = match extract_json_value response "has_vault" with
| Some "true" -> true
| _ -> false
in
if has_vault then begin
Printf.printf "%sVault: configured%s\n" green reset;
(match extract_json_value response "env_count" with
| Some c -> Printf.printf "Variables: %s\n" c
| None -> ());
(match extract_json_value response "updated_at" with
| Some u -> Printf.printf "Updated: %s\n" u
| None -> ())
end else
Printf.printf "%sVault: not configured%s\n" yellow reset
| None ->
Printf.fprintf stderr "%sError: service env status requires service ID%s\n" red reset;
exit 1)
| "set" ->
(match target with
| Some sid ->
if envs = [] && env_file = None then begin
Printf.fprintf stderr "%sError: service env set requires -e or --env-file%s\n" red reset;
exit 1
end;
let env_content = build_env_content envs env_file in
if service_env_set sid env_content then
Printf.printf "%sVault updated for service %s%s\n" green sid reset
else begin
Printf.fprintf stderr "%sError: Failed to update vault%s\n" red reset;
exit 1
end
| None ->
Printf.fprintf stderr "%sError: service env set requires service ID%s\n" red reset;
exit 1)
| "export" ->
(match target with
| Some sid ->
let response = service_env_export sid in
(match extract_json_value response "content" with
| Some content -> Printf.printf "%s" (unescape_json content)
| None -> ())
| None ->
Printf.fprintf stderr "%sError: service env export requires service ID%s\n" red reset;
exit 1)
| "delete" ->
(match target with
| Some sid ->
if service_env_delete sid then
Printf.printf "%sVault deleted for service %s%s\n" green sid reset
else begin
Printf.fprintf stderr "%sError: Failed to delete vault%s\n" red reset;
exit 1
end
| None ->
Printf.fprintf stderr "%sError: service env delete requires service ID%s\n" red reset;
exit 1)
| _ ->
Printf.fprintf stderr "%sError: Unknown env action: %s%s\n" red action reset;
Printf.fprintf stderr "Usage: un.ml service env <status|set|export|delete> <service_id>\n";
exit 1
(* Open browser *)
let open_browser url =
Printf.printf "%sOpening browser: %s%s\n" blue url reset;
let _ = match Sys.os_type with
| "Unix" | "Cygwin" ->
(try Sys.command (Printf.sprintf "xdg-open '%s' 2>/dev/null" url)
with _ ->
try Sys.command (Printf.sprintf "open '%s' 2>/dev/null" url)
with _ -> 1)
| "Win32" ->
Sys.command (Printf.sprintf "start '%s'" url)
| _ -> 1
in ()
(* Parse JSON response for stdout/stderr/exit_code *)
let extract_field field json =
try
let pattern = "\"" ^ field ^ "\":\"\\([^\"]*\\)\"" in
let regex = Str.regexp pattern in
let _ = Str.search_forward regex json 0 in
Some (Str.matched_group 1 json)
with Not_found ->
try
let pattern = "\"" ^ field ^ "\":\\([0-9]+\\)" in
let regex = Str.regexp pattern in
let _ = Str.search_forward regex json 0 in
Some (Str.matched_group 1 json)
with Not_found -> None
(* Execute command *)
let execute_command file env_vars artifacts out_dir network vcpu =
let api_key = get_api_key () in
let ext = get_extension file in
let language = match ext_to_lang ext with
| Some lang -> lang
| None ->
Printf.fprintf stderr "Error: Unknown extension: %s\n" ext;
exit 1
in
let code = read_file file in
let env_json = if env_vars = [] then ""
else ",\"env\":{" ^ (String.concat "," (List.map (fun (k, v) ->
Printf.sprintf "\"%s\":\"%s\"" k (escape_json v)) env_vars)) ^ "}"
in
let artifacts_json = if artifacts then ",\"return_artifacts\":true" else "" in
let network_json = match network with Some n -> Printf.sprintf ",\"network\":\"%s\"" n | None -> "" in
let vcpu_json = match vcpu with Some v -> Printf.sprintf ",\"vcpu\":%d" v | None -> "" in
let json = Printf.sprintf "{\"language\":\"%s\",\"code\":\"%s\"%s%s%s%s}"
language (escape_json code) env_json artifacts_json network_json vcpu_json in
let tmp_file = Printf.sprintf "/tmp/un_ocaml_%d.json" (Random.int 999999) in
let oc = open_out tmp_file in
output_string oc json;
close_out oc;
let (public_key, secret_key) = get_api_keys () in
let auth_headers = build_auth_headers public_key secret_key "POST" "/execute" json in
let cmd = Printf.sprintf "curl -s -X POST %s/execute -H 'Content-Type: application/json'%s -d @%s"
api_base auth_headers tmp_file in
let ic = Unix.open_process_in cmd in
let rec read_all acc =
try let line = input_line ic in read_all (acc ^ line ^ "\n")
with End_of_file -> acc
in
let response = read_all "" in
let _ = Unix.close_process_in ic in
Sys.remove tmp_file;
(* Parse response *)
(match extract_field "stdout" response with
| Some s -> Printf.printf "%s%s%s" blue (unescape_json s) reset
| None -> ());
(match extract_field "stderr" response with
| Some s -> Printf.fprintf stderr "%s%s%s" red (unescape_json s) reset
| None -> ());
let exit_code = match extract_field "exit_code" response with
| Some s -> int_of_string s
| None -> 0
in
exit exit_code
(* Display key info *)
let display_key_info response extend =
let status = extract_json_value response "status" in
let public_key = extract_json_value response "public_key" in
let tier = extract_json_value response "tier" in
let valid_through = extract_json_value response "valid_through_datetime" in
let valid_for = extract_json_value response "valid_for_human" in
let rate_limit = extract_json_value response "rate_per_minute" in
let burst = extract_json_value response "burst" in
let concurrency = extract_json_value response "concurrency" in
let expired_at = extract_json_value response "expired_at_datetime" in
match status with
| Some "valid" ->
Printf.printf "%sValid%s\n\n" green reset;
(match public_key with Some pk -> Printf.printf "Public Key: %s\n" pk | None -> ());
(match tier with Some t -> Printf.printf "Tier: %s\n" t | None -> ());
Printf.printf "Status: valid\n";
(match valid_through with Some exp -> Printf.printf "Expires: %s\n" exp | None -> ());
(match valid_for with Some vf -> Printf.printf "Time Remaining: %s\n" vf | None -> ());
(match rate_limit with Some r -> Printf.printf "Rate Limit: %s/min\n" r | None -> ());
(match burst with Some b -> Printf.printf "Burst: %s\n" b | None -> ());
(match concurrency with Some c -> Printf.printf "Concurrency: %s\n" c | None -> ());
if extend then
(match public_key with
| Some pk -> open_browser (Printf.sprintf "%s/keys/extend?pk=%s" portal_base pk)
| None -> ())
| Some "expired" ->
Printf.printf "%sExpired%s\n\n" red reset;
(match public_key with Some pk -> Printf.printf "Public Key: %s\n" pk | None -> ());
(match tier with Some t -> Printf.printf "Tier: %s\n" t | None -> ());
(match expired_at with Some exp -> Printf.printf "Expired: %s\n" exp | None -> ());
Printf.printf "\n%sTo renew:%s Visit %s/keys/extend\n" yellow reset portal_base;
if extend then
(match public_key with
| Some pk -> open_browser (Printf.sprintf "%s/keys/extend?pk=%s" portal_base pk)
| None -> ())
| Some "invalid" ->
Printf.printf "%sInvalid%s\n" red reset
| Some s ->
Printf.printf "%sUnknown status: %s%s\n" red s reset;
Printf.printf "%s\n" response
| None ->
Printf.printf "%sError: Could not parse response%s\n" red reset;
Printf.printf "%s\n" response
(* Validate key command *)
let validate_key api_key extend =
let json = "{}" in
let response = portal_curl_post api_key "/keys/validate" json in
display_key_info response extend
(* Key command *)
let key_command extend =
let api_key = get_api_key () in
validate_key api_key extend
(* Session command *)
let session_command action shell network vcpu input_files =
let api_key = get_api_key () in
match action with
| "list" ->
let response = curl_get api_key "/sessions" in
Printf.printf "%s\n" response
| "kill" ->
(match shell with
| Some sid ->
let _ = curl_delete api_key ("/sessions/" ^ sid) in
Printf.printf "%sSession terminated: %s%s\n" green sid reset
| None ->
Printf.fprintf stderr "Error: --kill requires session ID\n";
exit 1)
| "create" ->
let sh = match shell with Some s -> s | None -> "bash" in
let network_json = match network with Some n -> Printf.sprintf ",\"network\":\"%s\"" n | None -> "" in
let vcpu_json = match vcpu with Some v -> Printf.sprintf ",\"vcpu\":%d" v | None -> "" in
let input_files_json = build_input_files_json input_files in
let json = Printf.sprintf "{\"shell\":\"%s\"%s%s%s}" sh network_json vcpu_json input_files_json in
let response = curl_post api_key "/sessions" json in
Printf.printf "%sSession created (WebSocket required)%s\n" yellow reset;
Printf.printf "%s\n" response
| _ -> ()
(* Set unfreeze_on_demand for a service *)
let set_service_unfreeze_on_demand service_id enabled =
let (public_key, secret_key) = get_api_keys () in
let enabled_str = if enabled then "true" else "false" in
let json = Printf.sprintf "{\"unfreeze_on_demand\":%s}" enabled_str in
let endpoint = Printf.sprintf "/services/%s" service_id in
let auth_headers = build_auth_headers public_key secret_key "PATCH" endpoint json in
let tmp_file = Printf.sprintf "/tmp/un_ocaml_%d.json" (Random.int 999999) in
let oc = open_out tmp_file in
output_string oc json;
close_out oc;
let cmd = Printf.sprintf "curl -s -X PATCH %s%s -H 'Content-Type: application/json'%s -d @%s"
api_base endpoint auth_headers tmp_file in
let _ = Sys.command cmd in
Sys.remove tmp_file;
true
(* Service command *)
let service_command action name ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand input_files envs env_file =
let api_key = get_api_key () in
match action with
| "env" ->
service_env_command (match name with Some n -> n | None -> "") (match ports with Some p -> Some p | None -> None) envs env_file
| "env_cmd" ->
(match (name, ports) with
| (Some act, target) -> service_env_command act target envs env_file
| _ ->
Printf.fprintf stderr "Error: service env requires action\n";
exit 1)
| "list" ->
let response = curl_get api_key "/services" in
Printf.printf "%s\n" response
| "info" ->
(match name with
| Some sid ->
let response = curl_get api_key ("/services/" ^ sid) in
Printf.printf "%s\n" response
| None ->
Printf.fprintf stderr "Error: --info requires service ID\n";
exit 1)
| "logs" ->
(match name with
| Some sid ->
let response = curl_get api_key ("/services/" ^ sid ^ "/logs") in
Printf.printf "%s\n" response
| None ->
Printf.fprintf stderr "Error: --logs requires service ID\n";
exit 1)
| "sleep" ->
(match name with
| Some sid ->
let _ = curl_post api_key ("/services/" ^ sid ^ "/freeze") "{}" in
Printf.printf "%sService frozen: %s%s\n" green sid reset
| None ->
Printf.fprintf stderr "Error: --freeze requires service ID\n";
exit 1)
| "wake" ->
(match name with
| Some sid ->
let _ = curl_post api_key ("/services/" ^ sid ^ "/unfreeze") "{}" in
Printf.printf "%sService unfreezing: %s%s\n" green sid reset
| None ->
Printf.fprintf stderr "Error: --unfreeze requires service ID\n";
exit 1)
| "destroy" ->
(match name with
| Some sid ->
(match api_delete_with_sudo ("/services/" ^ sid) with
| SudoSuccess _ -> Printf.printf "%sService destroyed: %s%s\n" green sid reset
| SudoCancelled -> exit 1
| SudoError msg ->
Printf.fprintf stderr "%sError: %s%s\n" red msg reset;
exit 1)
| None ->
Printf.fprintf stderr "Error: --destroy requires service ID\n";
exit 1)
| "resize" ->
(match (name, vcpu) with
| (Some sid, Some v) ->
if v < 1 || v > 8 then begin
Printf.fprintf stderr "%sError: vCPU must be between 1 and 8%s\n" red reset;
exit 1
end;
let json = Printf.sprintf "{\"vcpu\":%d}" v in
let endpoint = Printf.sprintf "/services/%s" sid in
let (public_key, secret_key) = get_api_keys () in
let auth_headers = build_auth_headers public_key secret_key "PATCH" endpoint json in
let tmp_file = Printf.sprintf "/tmp/un_ocaml_%d.json" (Random.int 999999) in
let oc = open_out tmp_file in
output_string oc json;
close_out oc;
let cmd = Printf.sprintf "curl -s -X PATCH %s%s -H 'Content-Type: application/json'%s -d @%s"
api_base endpoint auth_headers tmp_file in
let _ = Sys.command cmd in
Sys.remove tmp_file;
let ram = v * 2 in
Printf.printf "%sService resized to %d vCPU, %d GB RAM%s\n" green v ram reset
| (Some _, None) ->
Printf.fprintf stderr "%sError: --resize requires --vcpu or -v%s\n" red reset;
exit 1
| (None, _) ->
Printf.fprintf stderr "Error: --resize requires service ID\n";
exit 1)
| "set_unfreeze_on_demand" ->
(match (name, ports) with
| (Some sid, Some enabled_str) ->
let enabled = enabled_str = "true" || enabled_str = "1" in
let _ = set_service_unfreeze_on_demand sid enabled in
let status = if enabled then "enabled" else "disabled" in
Printf.printf "%sUnfreeze-on-demand %s for service: %s%s\n" green status sid reset
| _ ->
Printf.fprintf stderr "Error: --set-unfreeze-on-demand requires service ID and enabled (true/false)\n";
exit 1)
| "execute" ->
(match name with
| Some sid ->
(match bootstrap with
| Some cmd ->
let json = Printf.sprintf "{\"command\":\"%s\"}" (escape_json cmd) in
let response = curl_post api_key ("/services/" ^ sid ^ "/execute") json in
(match extract_field "stdout" response with
| Some s -> Printf.printf "%s%s%s" blue (unescape_json s) reset
| None -> ())
| None ->
Printf.fprintf stderr "Error: --command required with --execute\n";
exit 1)
| None ->
Printf.fprintf stderr "Error: --execute requires service ID\n";
exit 1)
| "dump_bootstrap" ->
(match name with
| Some sid ->
Printf.fprintf stderr "Fetching bootstrap script from %s...\n" sid;
let json = "{\"command\":\"cat /tmp/bootstrap.sh\"}" in
let response = curl_post api_key ("/services/" ^ sid ^ "/execute") json in
(match extract_field "stdout" response with
| Some s ->
let script = unescape_json s in
(match service_type with
| Some file ->
let oc = open_out file in
output_string oc script;
close_out oc;
Unix.chmod file 0o755;
Printf.printf "Bootstrap saved to %s\n" file
| None ->
Printf.printf "%s" script)
| None ->
Printf.fprintf stderr "%sError: Failed to fetch bootstrap (service not running or no bootstrap file)%s\n" red reset;
exit 1)
| None ->
Printf.fprintf stderr "Error: --dump-bootstrap requires service ID\n";
exit 1)
| "create" ->
(match name with
| Some n ->
let ports_json = match ports with Some p -> Printf.sprintf ",\"ports\":[%s]" p | None -> "" in
let bootstrap_json = match bootstrap with Some b -> Printf.sprintf ",\"bootstrap\":\"%s\"" (escape_json b) | None -> "" in
let bootstrap_content_json = match bootstrap_file with
| Some f ->
let content = read_file f in
Printf.sprintf ",\"bootstrap_content\":\"%s\"" (escape_json content)
| None -> ""
in
let service_type_json = match service_type with Some t -> Printf.sprintf ",\"service_type\":\"%s\"" t | None -> "" in
let network_json = match network with Some net -> Printf.sprintf ",\"network\":\"%s\"" net | None -> "" in
let vcpu_json = match vcpu with Some v -> Printf.sprintf ",\"vcpu\":%d" v | None -> "" in
let unfreeze_on_demand_json = if unfreeze_on_demand then ",\"unfreeze_on_demand\":true" else "" in
let input_files_json = build_input_files_json input_files in
let json = Printf.sprintf "{\"name\":\"%s\"%s%s%s%s%s%s%s%s}" n ports_json bootstrap_json bootstrap_content_json service_type_json network_json vcpu_json unfreeze_on_demand_json input_files_json in
let response = curl_post api_key "/services" json in
Printf.printf "%sService created%s\n" green reset;
Printf.printf "%s\n" response;
(* Auto-set vault if env vars were provided *)
(match extract_json_value response "id" with
| Some service_id when envs <> [] || env_file <> None ->
let env_content = build_env_content envs env_file in
if String.length env_content > 0 then
if service_env_set service_id env_content then
Printf.printf "%sVault configured with environment variables%s\n" green reset
else
Printf.printf "%sWarning: Failed to set vault%s\n" yellow reset
| _ -> ())
| None ->
Printf.fprintf stderr "Error: --name required to create service\n";
exit 1)
| _ -> ()
(* Parse -f flags from argument list *)
let rec parse_input_files acc = function
| [] -> List.rev acc
| "-f" :: file :: rest ->
if Sys.file_exists file then
parse_input_files (file :: acc) rest
else begin
Printf.fprintf stderr "Error: File not found: %s\n" file;
exit 1
end
| _ :: rest -> parse_input_files acc rest
(* ============================================================================
CLI Entry Point
============================================================================ *)
(* Languages command *)
let languages_command json_output =
let response = languages () in
if json_output then begin
(* Extract languages array and output as JSON *)
let pattern = "\"languages\":\\s*\\[\\([^]]*\\)\\]" in
let regex = Str.regexp pattern in
try
let _ = Str.search_forward regex response 0 in
let langs_str = Str.matched_group 1 response in
Printf.printf "[%s]\n" langs_str
with Not_found ->
Printf.printf "[]\n"
end else begin
(* Extract each language and print one per line *)
let pattern = "\"\\([a-zA-Z0-9_+-]+\\)\"" in
let regex = Str.regexp pattern in
(* Find the languages array first *)
let langs_pattern = "\"languages\":\\s*\\[\\([^]]*\\)\\]" in
let langs_regex = Str.regexp langs_pattern in
try
let _ = Str.search_forward langs_regex response 0 in
let langs_str = Str.matched_group 1 response in
let rec extract_langs pos =
try
let _ = Str.search_forward regex langs_str pos in
let lang = Str.matched_group 1 langs_str in
Printf.printf "%s\n" lang;
extract_langs (Str.match_end ())
with Not_found -> ()
in
extract_langs 0
with Not_found -> ()
end
(* Image command *)
let image_command args =
let api_key = get_api_key () in
let rec parse_args list_mode info_id delete_id lock_id unlock_id publish_id source_type visibility_id visibility_mode spawn_id clone_id name ports = function
| [] ->
if list_mode then begin
let response = curl_get api_key "/images" in
Printf.printf "%s\n" response
end
else if info_id <> "" then begin
let response = curl_get api_key (Printf.sprintf "/images/%s" info_id) in
Printf.printf "%s\n" response
end
else if delete_id <> "" then begin
(match api_delete_with_sudo (Printf.sprintf "/images/%s" delete_id) with
| SudoSuccess _ -> Printf.printf "%sImage deleted: %s%s\n" green delete_id reset
| SudoCancelled -> exit 1
| SudoError msg ->
Printf.fprintf stderr "%sError: %s%s\n" red msg reset;
exit 1)
end
else if lock_id <> "" then begin
let _ = curl_post api_key (Printf.sprintf "/images/%s/lock" lock_id) "{}" in
Printf.printf "%sImage locked: %s%s\n" green lock_id reset
end
else if unlock_id <> "" then begin
(match api_post_with_sudo (Printf.sprintf "/images/%s/unlock" unlock_id) "{}" with
| SudoSuccess _ -> Printf.printf "%sImage unlocked: %s%s\n" green unlock_id reset
| SudoCancelled -> exit 1
| SudoError msg ->
Printf.fprintf stderr "%sError: %s%s\n" red msg reset;
exit 1)
end
else if publish_id <> "" then begin
if source_type = "" then begin
Printf.fprintf stderr "%sError: --publish requires --source-type (service or snapshot)%s\n" red reset;
exit 1
end;
let name_json = if name <> "" then Printf.sprintf ",\"name\":\"%s\"" name else "" in
let json = Printf.sprintf "{\"source_type\":\"%s\",\"source_id\":\"%s\"%s}" source_type publish_id name_json in
let response = curl_post api_key "/images/publish" json in
Printf.printf "%sImage published%s\n" green reset;
Printf.printf "%s\n" response
end
else if visibility_id <> "" && visibility_mode <> "" then begin
let json = Printf.sprintf "{\"visibility\":\"%s\"}" visibility_mode in
let _ = curl_post api_key (Printf.sprintf "/images/%s/visibility" visibility_id) json in
Printf.printf "%sImage visibility set to %s: %s%s\n" green visibility_mode visibility_id reset
end
else if spawn_id <> "" then begin
let name_json = if name <> "" then Printf.sprintf "\"name\":\"%s\"" name else "" in
let ports_json = if ports <> "" then Printf.sprintf "%s\"ports\":[%s]" (if name <> "" then "," else "") ports else "" in
let json = Printf.sprintf "{%s%s}" name_json ports_json in
let response = curl_post api_key (Printf.sprintf "/images/%s/spawn" spawn_id) json in
Printf.printf "%sService spawned from image%s\n" green reset;
Printf.printf "%s\n" response
end
else if clone_id <> "" then begin
let name_json = if name <> "" then Printf.sprintf "{\"name\":\"%s\"}" name else "{}" in
let response = curl_post api_key (Printf.sprintf "/images/%s/clone" clone_id) name_json in
Printf.printf "%sImage cloned%s\n" green reset;
Printf.printf "%s\n" response
end
else begin
Printf.fprintf stderr "%sError: Use --list, --info, --delete, --lock, --unlock, --publish, --visibility, --spawn, or --clone%s\n" red reset;
exit 1
end
| "--list" :: rest -> parse_args true info_id delete_id lock_id unlock_id publish_id source_type visibility_id visibility_mode spawn_id clone_id name ports rest
| "-l" :: rest -> parse_args true info_id delete_id lock_id unlock_id publish_id source_type visibility_id visibility_mode spawn_id clone_id name ports rest
| "--info" :: id :: rest -> parse_args list_mode id delete_id lock_id unlock_id publish_id source_type visibility_id visibility_mode spawn_id clone_id name ports rest
| "--delete" :: id :: rest -> parse_args list_mode info_id id lock_id unlock_id publish_id source_type visibility_id visibility_mode spawn_id clone_id name ports rest
| "--lock" :: id :: rest -> parse_args list_mode info_id delete_id id unlock_id publish_id source_type visibility_id visibility_mode spawn_id clone_id name ports rest
| "--unlock" :: id :: rest -> parse_args list_mode info_id delete_id lock_id id publish_id source_type visibility_id visibility_mode spawn_id clone_id name ports rest
| "--publish" :: id :: rest -> parse_args list_mode info_id delete_id lock_id unlock_id id source_type visibility_id visibility_mode spawn_id clone_id name ports rest
| "--source-type" :: t :: rest -> parse_args list_mode info_id delete_id lock_id unlock_id publish_id t visibility_id visibility_mode spawn_id clone_id name ports rest
| "--visibility" :: id :: mode :: rest -> parse_args list_mode info_id delete_id lock_id unlock_id publish_id source_type id mode spawn_id clone_id name ports rest
| "--spawn" :: id :: rest -> parse_args list_mode info_id delete_id lock_id unlock_id publish_id source_type visibility_id visibility_mode id clone_id name ports rest
| "--clone" :: id :: rest -> parse_args list_mode info_id delete_id lock_id unlock_id publish_id source_type visibility_id visibility_mode spawn_id id name ports rest
| "--name" :: n :: rest -> parse_args list_mode info_id delete_id lock_id unlock_id publish_id source_type visibility_id visibility_mode spawn_id clone_id n ports rest
| "--ports" :: p :: rest -> parse_args list_mode info_id delete_id lock_id unlock_id publish_id source_type visibility_id visibility_mode spawn_id clone_id name p rest
| _ :: rest -> parse_args list_mode info_id delete_id lock_id unlock_id publish_id source_type visibility_id visibility_mode spawn_id clone_id name ports rest
in
parse_args false "" "" "" "" "" "" "" "" "" "" "" "" args
let strip_account_arg args =
let rec aux = function
| [] -> []
| "--account" :: n_str :: rest ->
(try cli_account_index := int_of_string (String.trim n_str)
with Failure _ ->
Printf.fprintf stderr "Error: --account requires an integer argument\n";
exit 1);
aux rest
| arg :: rest -> arg :: aux rest
in
aux args
let () =
Random.self_init ();
let raw_args = Array.to_list Sys.argv in
let args = strip_account_arg (List.tl raw_args) in
match args with
| [] ->
Printf.printf "Usage: un.ml [--account N] [options] <source_file>\n";
Printf.printf " un.ml [--account N] session [options]\n";
Printf.printf " un.ml [--account N] service [options]\n";
Printf.printf " un.ml [--account N] image [options]\n";
Printf.printf " un.ml [--account N] service env <action> <service_id>\n";
Printf.printf " un.ml languages [--json]\n";
Printf.printf " un.ml key [--extend]\n\n";
Printf.printf "Global options:\n";
Printf.printf " --account N Use accounts.csv row N (bypasses env vars)\n\n";
Printf.printf "Service options: --name, --ports, --bootstrap, --bootstrap-file, -e KEY=VALUE, --env-file FILE\n";
Printf.printf "Service env commands: status, set, export, delete\n";
Printf.printf "Image options: --list, --info ID, --delete ID, --lock ID, --unlock ID,\n";
Printf.printf " --publish ID --source-type TYPE, --visibility ID MODE,\n";
Printf.printf " --spawn ID, --clone ID, --name NAME, --ports PORTS\n";
Printf.printf "Languages options: --json (output as JSON array)\n";
exit 1
| "languages" :: rest ->
let json_output = List.mem "--json" rest in
languages_command json_output
| "image" :: rest ->
image_command rest
| "key" :: rest ->
let extend = List.mem "--extend" rest in
key_command extend
| "session" :: rest ->
let input_files = parse_input_files [] rest in
let rec parse_session action shell network vcpu = function
| [] -> session_command action shell network vcpu input_files
| "--list" :: rest -> parse_session "list" shell network vcpu rest
| "--kill" :: id :: rest -> parse_session "kill" (Some id) network vcpu rest
| "--shell" :: sh :: rest | "-s" :: sh :: rest -> parse_session action (Some sh) network vcpu rest
| "-n" :: net :: rest -> parse_session action shell (Some net) vcpu rest
| "-v" :: v :: rest -> parse_session action shell network (Some (int_of_string v)) rest
| "-f" :: _ :: rest -> parse_session action shell network vcpu rest (* skip -f, already parsed *)
| arg :: rest ->
if String.length arg > 0 && arg.[0] = '-' then begin
Printf.fprintf stderr "Unknown option: %s\n" arg;
Printf.fprintf stderr "Usage: un.ml session [options]\n";
exit 1
end else
parse_session action shell network vcpu rest
in
parse_session "create" None None None rest
| "service" :: rest ->
let input_files = parse_input_files [] rest in
let rec parse_envs acc = function
| [] -> List.rev acc
| "-e" :: kv :: rest -> parse_envs (kv :: acc) rest
| _ :: rest -> parse_envs acc rest
in
let envs = parse_envs [] rest in
let rec parse_service action name ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file = function
| [] -> service_command action name ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand input_files envs env_file
| "env" :: env_action :: target :: rest when not (String.length target > 0 && target.[0] = '-') ->
parse_service "env_cmd" (Some env_action) (Some target) bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest
| "env" :: env_action :: rest ->
parse_service "env_cmd" (Some env_action) None bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest
| "--list" :: rest -> parse_service "list" name ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest
| "--info" :: id :: rest -> parse_service "info" (Some id) ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest
| "--logs" :: id :: rest -> parse_service "logs" (Some id) ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest
| "--freeze" :: id :: rest -> parse_service "sleep" (Some id) ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest
| "--unfreeze" :: id :: rest -> parse_service "wake" (Some id) ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest
| "--destroy" :: id :: rest -> parse_service "destroy" (Some id) ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest
| "--resize" :: id :: rest -> parse_service "resize" (Some id) ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest
| "--vcpu" :: v :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network (Some (int_of_string v)) unfreeze_on_demand env_file rest
| "--execute" :: id :: "--command" :: cmd :: rest -> parse_service "execute" (Some id) ports (Some cmd) bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest
| "--dump-bootstrap" :: id :: file :: rest -> parse_service "dump_bootstrap" (Some id) ports bootstrap (Some file) service_type network vcpu unfreeze_on_demand env_file rest
| "--dump-bootstrap" :: id :: rest -> parse_service "dump_bootstrap" (Some id) ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest
| "--name" :: n :: rest -> parse_service "create" (Some n) ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest
| "--ports" :: p :: rest -> parse_service action name (Some p) bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest
| "--bootstrap" :: b :: rest -> parse_service action name ports (Some b) bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest
| "--bootstrap-file" :: f :: rest -> parse_service action name ports bootstrap (Some f) service_type network vcpu unfreeze_on_demand env_file rest
| "--type" :: t :: rest -> parse_service action name ports bootstrap bootstrap_file (Some t) network vcpu unfreeze_on_demand env_file rest
| "-n" :: net :: rest -> parse_service action name ports bootstrap bootstrap_file service_type (Some net) vcpu unfreeze_on_demand env_file rest
| "-v" :: v :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network (Some (int_of_string v)) unfreeze_on_demand env_file rest
| "--env-file" :: f :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand (Some f) rest
| "--unfreeze-on-demand" :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network vcpu true env_file rest
| "--set-unfreeze-on-demand" :: id :: enabled :: rest -> parse_service "set_unfreeze_on_demand" (Some id) (Some enabled) bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest
| "-e" :: _ :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest (* skip -e, already parsed *)
| "-f" :: _ :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest (* skip -f, already parsed *)
| _ :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest
in
parse_service "create" None None None None None None None false None rest
| args ->
let rec parse_execute file env_vars artifacts out_dir network vcpu = function
| [] -> execute_command file env_vars artifacts out_dir network vcpu
| "-e" :: kv :: rest ->
(try
let eq_pos = String.index kv '=' in
let k = String.sub kv 0 eq_pos in
let v = String.sub kv (eq_pos + 1) (String.length kv - eq_pos - 1) in
parse_execute file ((k, v) :: env_vars) artifacts out_dir network vcpu rest
with Not_found -> parse_execute file env_vars artifacts out_dir network vcpu rest)
| "-a" :: rest -> parse_execute file env_vars true out_dir network vcpu rest
| "-o" :: dir :: rest -> parse_execute file env_vars artifacts (Some dir) network vcpu rest
| "-n" :: net :: rest -> parse_execute file env_vars artifacts out_dir (Some net) vcpu rest
| "-v" :: v :: rest -> parse_execute file env_vars artifacts out_dir network (Some (int_of_string v)) rest
| arg :: rest ->
if file = "" && not (String.get arg 0 = '-') then
parse_execute arg env_vars artifacts out_dir network vcpu rest
else
parse_execute file env_vars artifacts out_dir network vcpu rest
in
parse_execute "" [] false None None None args
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