un-inception/un.rs
russell@unturf.com dc69bf4959 Rename sleep/wake to freeze/unfreeze across all 42 implementations
API endpoint changes:
- /services/{id}/sleep → /services/{id}/freeze
- /services/{id}/wake → /services/{id}/unfreeze

CLI flag changes:
- --sleep → --freeze
- --wake → --unfreeze

Message changes:
- 'Service sleeping' → 'Service frozen'
- 'Service waking' → 'Service unfreezing'
2026-01-15 11:52:15 -05:00

1064 lines
37 KiB
Rust

// PUBLIC DOMAIN - NO LICENSE, NO WARRANTY
//
// This is free public domain software for the public good of a permacomputer hosted
// at permacomputer.com - an always-on computer by the people, for the people. One
// which is durable, easy to repair, and distributed like tap water for machine
// learning intelligence.
//
// The permacomputer is community-owned infrastructure optimized around four values:
//
// TRUTH - First principles, math & science, open source code freely distributed
// FREEDOM - Voluntary partnerships, freedom from tyranny & corporate control
// HARMONY - Minimal waste, self-renewing systems with diverse thriving connections
// LOVE - Be yourself without hurting others, cooperation through natural law
//
// This software contributes to that vision by enabling code execution across 42+
// programming languages through a unified interface, accessible to all. Code is
// seeds to sprout on any abandoned technology.
//
// Learn more: https://www.permacomputer.com
//
// Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
// software, either in source code form or as a compiled binary, for any purpose,
// commercial or non-commercial, and by any means.
//
// NO WARRANTY. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND.
//
// That said, our permacomputer's digital membrane stratum continuously runs unit,
// integration, and functional tests on all of it's own software - with our
// permacomputer monitoring itself, repairing itself, with minimal human in the
// loop guidance. Our agents do their best.
//
// Copyright 2025 TimeHexOn & foxhop & russell@unturf
// https://www.timehexon.com
// https://www.foxhop.net
// https://www.unturf.com/software
// UN CLI - Rust Implementation
// Note: This uses curl subprocess to avoid requiring external crates
// Compile: rustc un.rs -o un_rust
// Usage:
// un.rs script.py
// un.rs -e KEY=VALUE -f data.txt script.py
// un.rs session --list
// un.rs service --name web --ports 8080
use std::env;
use std::fs;
use std::path::Path;
use std::process::{self, Command};
use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
const API_BASE: &str = "https://api.unsandbox.com";
const PORTAL_BASE: &str = "https://unsandbox.com";
const BLUE: &str = "\x1b[34m";
const RED: &str = "\x1b[31m";
const GREEN: &str = "\x1b[32m";
const YELLOW: &str = "\x1b[33m";
const RESET: &str = "\x1b[0m";
fn detect_language(filename: &str) -> Option<&'static str> {
let ext = Path::new(filename)
.extension()
.and_then(|e| e.to_str())
.unwrap_or("");
match ext {
"py" => Some("python"),
"js" => Some("javascript"),
"ts" => Some("typescript"),
"rb" => Some("ruby"),
"php" => Some("php"),
"pl" => Some("perl"),
"lua" => Some("lua"),
"sh" => Some("bash"),
"go" => Some("go"),
"rs" => Some("rust"),
"c" => Some("c"),
"cpp" | "cc" | "cxx" => Some("cpp"),
"java" => Some("java"),
"kt" => Some("kotlin"),
"cs" => Some("csharp"),
"fs" => Some("fsharp"),
"hs" => Some("haskell"),
"ml" => Some("ocaml"),
"clj" => Some("clojure"),
"scm" => Some("scheme"),
"lisp" => Some("commonlisp"),
"erl" => Some("erlang"),
"ex" | "exs" => Some("elixir"),
"jl" => Some("julia"),
"r" | "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"),
"f90" | "f95" => Some("fortran"),
"cob" => Some("cobol"),
"pro" => Some("prolog"),
"forth" | "4th" => Some("forth"),
"tcl" => Some("tcl"),
"raku" => Some("raku"),
"m" => Some("objc"),
_ => None,
}
}
fn get_api_keys(key_arg: Option<&str>) -> (String, String) {
let public_key = env::var("UNSANDBOX_PUBLIC_KEY").ok();
let secret_key = env::var("UNSANDBOX_SECRET_KEY").ok();
// Fall back to UNSANDBOX_API_KEY for backwards compatibility
if public_key.is_none() || secret_key.is_none() {
let fallback_key = if let Some(k) = key_arg {
k.to_string()
} else {
env::var("UNSANDBOX_API_KEY").unwrap_or_else(|_| {
eprintln!("{}Error: UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY not set (or UNSANDBOX_API_KEY for backwards compat){}", RED, RESET);
process::exit(1);
})
};
return (fallback_key.clone(), fallback_key);
}
(public_key.unwrap(), secret_key.unwrap())
}
fn compute_hmac(secret_key: &str, timestamp: &str, method: &str, path: &str, body: &str) -> String {
use std::process::Command;
let message = format!("{}:{}:{}:{}", timestamp, method, path, body);
// Use openssl for HMAC-SHA256
let output = Command::new("sh")
.arg("-c")
.arg(format!("printf '%s' '{}' | openssl dgst -sha256 -hmac '{}' | cut -d' ' -f2", message, secret_key))
.output()
.expect("Failed to compute HMAC");
String::from_utf8_lossy(&output.stdout).trim().to_string()
}
fn escape_json(s: &str) -> String {
s.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
.replace('\r', "\\r")
.replace('\t', "\\t")
}
fn unescape_json(s: &str) -> String {
s.replace("\\n", "\n")
.replace("\\r", "\r")
.replace("\\t", "\t")
.replace("\\\"", "\"")
.replace("\\\\", "\\")
}
fn extract_json_string(json: &str, key: &str) -> String {
let search = format!("\"{}\":\"", key);
if let Some(start) = json.find(&search) {
let start = start + search.len();
let mut end = start;
let chars: Vec<char> = json.chars().collect();
while end < chars.len() {
if chars[end] == '"' && (end == 0 || chars[end - 1] != '\\') {
break;
}
end += 1;
}
return unescape_json(&json[start..end]);
}
String::new()
}
fn extract_json_int(json: &str, key: &str) -> i32 {
let search = format!("\"{}\":", key);
if let Some(pos) = json.find(&search) {
let start = pos + search.len();
let rest = &json[start..];
let num_str: String = rest.chars().take_while(|c| c.is_numeric()).collect();
return num_str.parse().unwrap_or(1);
}
1
}
fn api_request(endpoint: &str, method: &str, body: Option<&str>, public_key: &str, secret_key: &str) -> String {
let url = format!("{}{}", API_BASE, endpoint);
let body_str = body.unwrap_or("");
// Compute HMAC signature
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
.to_string();
let signature = compute_hmac(secret_key, &timestamp, method, endpoint, body_str);
let mut cmd = Command::new("curl");
cmd.arg("-s")
.arg("-X")
.arg(method)
.arg(&url)
.arg("-H")
.arg("Content-Type: application/json")
.arg("-H")
.arg(format!("Authorization: Bearer {}", public_key))
.arg("-H")
.arg(format!("X-Timestamp: {}", timestamp))
.arg("-H")
.arg(format!("X-Signature: {}", signature));
if let Some(b) = body {
cmd.arg("-d").arg(b);
}
let output = cmd.output().unwrap_or_else(|e| {
eprintln!("{}Error running curl: {}{}", RED, e, RESET);
process::exit(1);
});
if !output.status.success() {
eprintln!("{}Error: HTTP request failed{}", RED, RESET);
process::exit(1);
}
let result = String::from_utf8_lossy(&output.stdout).to_string();
// Check for timestamp authentication errors
if result.contains("timestamp") && (result.contains("401") || result.contains("expired") || result.contains("invalid")) {
eprintln!("{}Error: Request timestamp expired (must be within 5 minutes of server time){}", RED, RESET);
eprintln!("{}Your computer's clock may have drifted.{}", YELLOW, RESET);
eprintln!("Check your system time and sync with NTP if needed:");
eprintln!(" Linux: sudo ntpdate -s time.nist.gov");
eprintln!(" macOS: sudo sntp -sS time.apple.com");
eprintln!(" Windows: w32tm /resync");
process::exit(1);
}
result
}
fn api_request_text(endpoint: &str, method: &str, body: &str, public_key: &str, secret_key: &str) -> String {
let url = format!("{}{}", API_BASE, endpoint);
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
.to_string();
let signature = compute_hmac(secret_key, &timestamp, method, endpoint, body);
let mut cmd = Command::new("curl");
cmd.arg("-s")
.arg("-X")
.arg(method)
.arg(&url)
.arg("-H")
.arg("Content-Type: text/plain")
.arg("-H")
.arg(format!("Authorization: Bearer {}", public_key))
.arg("-H")
.arg(format!("X-Timestamp: {}", timestamp))
.arg("-H")
.arg(format!("X-Signature: {}", signature));
if !body.is_empty() {
cmd.arg("-d").arg(body);
}
let output = cmd.output().unwrap_or_else(|e| {
eprintln!("{}Error running curl: {}{}", RED, e, RESET);
process::exit(1);
});
String::from_utf8_lossy(&output.stdout).to_string()
}
fn read_env_file(path: &str) -> String {
fs::read_to_string(path).unwrap_or_else(|e| {
eprintln!("{}Error reading env file: {}{}", RED, e, RESET);
process::exit(1);
})
}
fn build_env_content(envs: &[String], env_file: Option<&str>) -> String {
let mut parts: Vec<String> = Vec::new();
if let Some(path) = env_file {
parts.push(read_env_file(path).trim().to_string());
}
for e in envs {
if e.contains('=') {
parts.push(e.clone());
}
}
parts.join("\n")
}
fn service_env_status(service_id: &str, public_key: &str, secret_key: &str) -> String {
api_request(&format!("/services/{}/env", service_id), "GET", None, public_key, secret_key)
}
fn service_env_set(service_id: &str, env_content: &str, public_key: &str, secret_key: &str) -> bool {
api_request_text(&format!("/services/{}/env", service_id), "PUT", env_content, public_key, secret_key);
true
}
fn service_env_export(service_id: &str, public_key: &str, secret_key: &str) -> String {
api_request(&format!("/services/{}/env/export", service_id), "POST", None, public_key, secret_key)
}
fn service_env_delete(service_id: &str, public_key: &str, secret_key: &str) -> bool {
api_request(&format!("/services/{}/env", service_id), "DELETE", None, public_key, secret_key);
true
}
fn cmd_service_env(
action: &str,
target: Option<&str>,
envs: &[String],
env_file: Option<&str>,
public_key: &str,
secret_key: &str,
) {
match action {
"status" => {
let id = target.unwrap_or_else(|| {
eprintln!("{}Error: Usage: service env status <service_id>{}", RED, RESET);
process::exit(1);
});
let result = service_env_status(id, public_key, secret_key);
let has_env = result.contains("\"has_env\":true");
let size = extract_json_int(&result, "size");
let updated_at = extract_json_string(&result, "updated_at");
println!("Service: {}", id);
println!("Has Vault: {}", if has_env { "Yes" } else { "No" });
if has_env {
println!("Size: {} bytes", size);
println!("Updated: {}", updated_at);
}
}
"set" => {
let id = target.unwrap_or_else(|| {
eprintln!("{}Error: Usage: service env set <service_id> [-e KEY=VAL] [--env-file FILE]{}", RED, RESET);
process::exit(1);
});
let env_content = build_env_content(envs, env_file);
if env_content.is_empty() {
eprintln!("{}Error: No environment variables specified. Use -e KEY=VAL or --env-file FILE{}", RED, RESET);
process::exit(1);
}
if env_content.len() > 65536 {
eprintln!("{}Error: Environment content exceeds 64KB limit{}", RED, RESET);
process::exit(1);
}
service_env_set(id, &env_content, public_key, secret_key);
println!("{}Vault updated for service: {}{}", GREEN, id, RESET);
}
"export" => {
let id = target.unwrap_or_else(|| {
eprintln!("{}Error: Usage: service env export <service_id>{}", RED, RESET);
process::exit(1);
});
let result = service_env_export(id, public_key, secret_key);
let content = extract_json_string(&result, "content");
if !content.is_empty() {
print!("{}", content);
if !content.ends_with('\n') {
println!();
}
} else {
eprintln!("{}Vault is empty{}", YELLOW, RESET);
}
}
"delete" => {
let id = target.unwrap_or_else(|| {
eprintln!("{}Error: Usage: service env delete <service_id>{}", RED, RESET);
process::exit(1);
});
service_env_delete(id, public_key, secret_key);
println!("{}Vault deleted for service: {}{}", GREEN, id, RESET);
}
_ => {
eprintln!("{}Error: Unknown env action: {}. Use status, set, export, or delete{}", RED, action, RESET);
process::exit(1);
}
}
}
fn cmd_execute(
source_file: &str,
envs: Vec<String>,
files: Vec<String>,
artifacts: bool,
output_dir: Option<&str>,
network: Option<&str>,
vcpu: Option<i32>,
public_key: &str,
secret_key: &str,
) {
let code = fs::read_to_string(source_file).unwrap_or_else(|e| {
eprintln!("{}Error reading file: {}{}", RED, e, RESET);
process::exit(1);
});
let language = detect_language(source_file).unwrap_or_else(|| {
eprintln!("{}Error: Cannot detect language{}", RED, RESET);
process::exit(1);
});
let mut json = format!(
r#"{{"language":"{}","code":"{}""#,
language,
escape_json(&code)
);
// Environment variables
if !envs.is_empty() {
json.push_str(r#","env":{"#);
for (i, e) in envs.iter().enumerate() {
if let Some((k, v)) = e.split_once('=') {
if i > 0 {
json.push(',');
}
json.push_str(&format!(r#""{}":"{}""#, k, escape_json(v)));
}
}
json.push('}');
}
// Input files
if !files.is_empty() {
json.push_str(r#","input_files":["#);
for (i, f) in files.iter().enumerate() {
let content = fs::read(f).unwrap_or_else(|e| {
eprintln!("{}Error reading input file: {}{}", RED, e, RESET);
process::exit(1);
});
let b64 = base64::encode(&content);
if i > 0 {
json.push(',');
}
json.push_str(&format!(
r#"{{"filename":"{}","content_base64":"{}"}}"#,
Path::new(f).file_name().unwrap().to_str().unwrap(),
b64
));
}
json.push(']');
}
if artifacts {
json.push_str(r#","return_artifacts":true"#);
}
if let Some(n) = network {
json.push_str(&format!(r#","network":"{}""#, n));
}
if let Some(v) = vcpu {
json.push_str(&format!(r#","vcpu":{}"#, v));
}
json.push('}');
let result = api_request("/execute", "POST", Some(&json), public_key, secret_key);
// Print output
let stdout_str = extract_json_string(&result, "stdout");
let stderr_str = extract_json_string(&result, "stderr");
let exit_code = extract_json_int(&result, "exit_code");
if !stdout_str.is_empty() {
print!("{}{}{}", BLUE, stdout_str, RESET);
}
if !stderr_str.is_empty() {
eprint!("{}{}{}", RED, stderr_str, RESET);
}
// Artifacts (simplified - would need full JSON parsing)
if artifacts && result.contains("artifacts") {
eprintln!("{}Note: Artifact saving not fully implemented in Rust version{}", YELLOW, RESET);
}
process::exit(exit_code);
}
fn cmd_session(
list: bool,
kill: Option<&str>,
shell: Option<&str>,
network: Option<&str>,
vcpu: Option<i32>,
tmux: bool,
screen: bool,
files: &[String],
public_key: &str,
secret_key: &str,
) {
if list {
let result = api_request("/sessions", "GET", None, public_key, secret_key);
println!("{}", result);
return;
}
if let Some(id) = kill {
api_request(&format!("/sessions/{}", id), "DELETE", None, public_key, secret_key);
println!("{}Session terminated: {}{}", GREEN, id, RESET);
return;
}
// Create session
let mut json = format!(
r#"{{"shell":"{}""#,
shell.unwrap_or("bash")
);
if let Some(n) = network {
json.push_str(&format!(r#","network":"{}""#, n));
}
if let Some(v) = vcpu {
json.push_str(&format!(r#","vcpu":{}"#, v));
}
if tmux {
json.push_str(r#","persistence":"tmux""#);
}
if screen {
json.push_str(r#","persistence":"screen""#);
}
// Input files
if !files.is_empty() {
json.push_str(r#","input_files":["#);
for (i, f) in files.iter().enumerate() {
if i > 0 {
json.push(',');
}
let content = fs::read(f).unwrap_or_else(|e| {
eprintln!("{}Error reading input file {}: {}{}", RED, f, e, RESET);
process::exit(1);
});
let b64 = base64::encode(&content);
let filename = Path::new(f).file_name().map(|n| n.to_string_lossy()).unwrap_or_default();
json.push_str(&format!(r#"{{"filename":"{}","content_base64":"{}"}}"#, filename, b64));
}
json.push(']');
}
json.push('}');
println!("{}Creating session...{}", YELLOW, RESET);
let result = api_request("/sessions", "POST", Some(&json), public_key, secret_key);
let id = extract_json_string(&result, "id");
println!("{}Session created: {}{}", GREEN, id, RESET);
}
fn cmd_service(
name: Option<&str>,
ports: Option<&str>,
domains: Option<&str>,
service_type: Option<&str>,
bootstrap: Option<&str>,
bootstrap_file: Option<&str>,
files: &[String],
list: bool,
info: Option<&str>,
logs: Option<&str>,
tail: Option<&str>,
sleep: Option<&str>,
wake: Option<&str>,
destroy: Option<&str>,
resize: Option<&str>,
execute: Option<&str>,
command: Option<&str>,
dump_bootstrap: Option<&str>,
dump_file: Option<&str>,
network: Option<&str>,
vcpu: Option<i32>,
envs: &[String],
env_file: Option<&str>,
env_action: Option<&str>,
env_target: Option<&str>,
public_key: &str,
secret_key: &str,
) {
// Handle service env subcommand
if let Some(action) = env_action {
cmd_service_env(action, env_target, envs, env_file, public_key, secret_key);
return;
}
if list {
let result = api_request("/services", "GET", None, public_key, secret_key);
println!("{}", result);
return;
}
if let Some(id) = info {
let result = api_request(&format!("/services/{}", id), "GET", None, public_key, secret_key);
println!("{}", result);
return;
}
if let Some(id) = logs {
let result = api_request(&format!("/services/{}/logs", id), "GET", None, public_key, secret_key);
println!("{}", extract_json_string(&result, "logs"));
return;
}
if let Some(id) = tail {
let result = api_request(&format!("/services/{}/logs?lines=9000", id), "GET", None, public_key, secret_key);
println!("{}", extract_json_string(&result, "logs"));
return;
}
if let Some(id) = sleep {
api_request(&format!("/services/{}/freeze", id), "POST", None, public_key, secret_key);
println!("{}Service frozen: {}{}", GREEN, id, RESET);
return;
}
if let Some(id) = wake {
api_request(&format!("/services/{}/unfreeze", id), "POST", None, public_key, secret_key);
println!("{}Service unfreezing: {}{}", GREEN, id, RESET);
return;
}
if let Some(id) = destroy {
api_request(&format!("/services/{}", id), "DELETE", None, public_key, secret_key);
println!("{}Service destroyed: {}{}", GREEN, id, RESET);
return;
}
if let Some(id) = resize {
let v = vcpu.unwrap_or_else(|| {
eprintln!("{}Error: --resize requires -v <vcpu>{}", RED, RESET);
process::exit(1);
});
let json = format!(r#"{{"vcpu":{}}}"#, v);
api_request(&format!("/services/{}", id), "PATCH", Some(&json), public_key, secret_key);
println!("{}Service resized to {} vCPU, {} GB RAM{}", GREEN, v, v * 2, RESET);
return;
}
if let Some(id) = execute {
let cmd = command.unwrap_or("");
let json = format!(r#"{{"command":"{}"}}"#, escape_json(cmd));
let result = api_request(&format!("/services/{}/execute", id), "POST", Some(&json), public_key, secret_key);
let stdout_str = extract_json_string(&result, "stdout");
let stderr_str = extract_json_string(&result, "stderr");
if !stdout_str.is_empty() {
print!("{}{}{}", BLUE, stdout_str, RESET);
}
if !stderr_str.is_empty() {
eprint!("{}{}{}", RED, stderr_str, RESET);
}
return;
}
if let Some(id) = dump_bootstrap {
eprintln!("Fetching bootstrap script from {}...", id);
let json = r#"{"command":"cat /tmp/bootstrap.sh"}"#;
let result = api_request(&format!("/services/{}/execute", id), "POST", Some(json), public_key, secret_key);
let bootstrap = extract_json_string(&result, "stdout");
if !bootstrap.is_empty() {
if let Some(file) = dump_file {
match fs::write(file, &bootstrap) {
Ok(_) => {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let _ = fs::set_permissions(file, fs::Permissions::from_mode(0o755));
}
println!("Bootstrap saved to {}", file);
}
Err(e) => {
eprintln!("{}Error: Could not write to {}: {}{}", RED, file, e, RESET);
process::exit(1);
}
}
} else {
print!("{}", bootstrap);
}
} else {
eprintln!("{}Error: Failed to fetch bootstrap (service not running or no bootstrap file){}", RED, RESET);
process::exit(1);
}
return;
}
// Create service
if let Some(n) = name {
let mut json = format!(r#"{{"name":"{}""#, n);
if let Some(p) = ports {
json.push_str(r#","ports":["#);
let ports_vec: Vec<&str> = p.split(',').collect();
for (i, port) in ports_vec.iter().enumerate() {
if i > 0 {
json.push(',');
}
json.push_str(port.trim());
}
json.push(']');
}
if let Some(d) = domains {
json.push_str(r#","domains":["#);
let domains_vec: Vec<&str> = d.split(',').collect();
for (i, domain) in domains_vec.iter().enumerate() {
if i > 0 {
json.push(',');
}
json.push_str(&format!(r#""{}""#, domain.trim()));
}
json.push(']');
}
if let Some(t) = service_type {
json.push_str(&format!(r#","service_type":"{}""#, t));
}
if let Some(b) = bootstrap {
json.push_str(&format!(r#","bootstrap":"{}""#, escape_json(b)));
}
if let Some(bf) = bootstrap_file {
if Path::new(bf).exists() {
let content = fs::read_to_string(bf).unwrap_or_else(|e| {
eprintln!("{}Error reading bootstrap file: {}{}", RED, e, RESET);
process::exit(1);
});
json.push_str(&format!(r#","bootstrap_content":"{}""#, escape_json(&content)));
} else {
eprintln!("{}Error: Bootstrap file not found: {}{}", RED, bf, RESET);
process::exit(1);
}
}
// Input files
if !files.is_empty() {
json.push_str(r#","input_files":["#);
for (i, f) in files.iter().enumerate() {
if i > 0 {
json.push(',');
}
let content = fs::read(f).unwrap_or_else(|e| {
eprintln!("{}Error reading input file {}: {}{}", RED, f, e, RESET);
process::exit(1);
});
let b64 = base64::encode(&content);
let filename = Path::new(f).file_name().map(|n| n.to_string_lossy()).unwrap_or_default();
json.push_str(&format!(r#"{{"filename":"{}","content_base64":"{}"}}"#, filename, b64));
}
json.push(']');
}
if let Some(net) = network {
json.push_str(&format!(r#","network":"{}""#, net));
}
if let Some(v) = vcpu {
json.push_str(&format!(r#","vcpu":{}"#, v));
}
json.push('}');
let result = api_request("/services", "POST", Some(&json), public_key, secret_key);
let id = extract_json_string(&result, "id");
println!("{}Service created: {}{}", GREEN, id, RESET);
// Auto-set vault if env vars provided
if !id.is_empty() && (!envs.is_empty() || env_file.is_some()) {
let env_content = build_env_content(envs, env_file);
if !env_content.is_empty() && env_content.len() <= 65536 {
if service_env_set(&id, &env_content, public_key, secret_key) {
println!("{}Vault configured with environment variables{}", GREEN, RESET);
}
}
}
return;
}
eprintln!("{}Error: Specify --name to create a service{}", RED, RESET);
process::exit(1);
}
fn cmd_key(extend: bool, public_key: &str, secret_key: &str) {
let result = api_request("/keys/validate", "POST", Some("{}"), public_key, secret_key);
let status = extract_json_string(&result, "status");
let public_key = extract_json_string(&result, "public_key");
let tier = extract_json_string(&result, "tier");
let expired_at = extract_json_string(&result, "expired_at");
if extend && !public_key.is_empty() {
let url = format!("{}/keys/extend?pk={}", PORTAL_BASE, public_key);
println!("{}Opening browser: {}{}", YELLOW, url, RESET);
// Try xdg-open (Linux), open (macOS), or start (Windows)
let _ = Command::new("xdg-open")
.arg(&url)
.spawn()
.or_else(|_| Command::new("open").arg(&url).spawn())
.or_else(|_| Command::new("cmd").args(&["/c", "start", &url]).spawn());
return;
}
match status.as_str() {
"valid" => {
println!("{}Valid{}", GREEN, RESET);
println!("Public Key: {}", public_key);
println!("Tier: {}", tier);
if !expired_at.is_empty() {
println!("Expires: {}", expired_at);
}
}
"expired" => {
println!("{}Expired{}", RED, RESET);
println!("Public Key: {}", public_key);
println!("Tier: {}", tier);
if !expired_at.is_empty() {
println!("Expired: {}", expired_at);
}
println!("{}To renew: Visit {}/keys/extend{}", YELLOW, PORTAL_BASE, RESET);
}
"invalid" => {
println!("{}Invalid{}", RED, RESET);
}
_ => {
println!("{}Unknown status: {}{}", YELLOW, status, RESET);
}
}
}
// Minimal base64 encoding
mod base64 {
const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
pub fn encode(input: &[u8]) -> String {
let mut result = String::new();
let mut i = 0;
while i < input.len() {
let b1 = input[i];
let b2 = if i + 1 < input.len() { input[i + 1] } else { 0 };
let b3 = if i + 2 < input.len() { input[i + 2] } else { 0 };
result.push(CHARS[(b1 >> 2) as usize] as char);
result.push(CHARS[(((b1 & 0x03) << 4) | (b2 >> 4)) as usize] as char);
result.push(if i + 1 < input.len() {
CHARS[(((b2 & 0x0f) << 2) | (b3 >> 6)) as usize] as char
} else {
'='
});
result.push(if i + 2 < input.len() {
CHARS[(b3 & 0x3f) as usize] as char
} else {
'='
});
i += 3;
}
result
}
}
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
eprintln!("Usage: {} [options] <source_file>", args[0]);
eprintln!(" {} session [options]", args[0]);
eprintln!(" {} service [options]", args[0]);
eprintln!(" {} key [--extend]", args[0]);
process::exit(1);
}
// Parse arguments (simplified)
let mut api_key: Option<String> = None;
let mut network: Option<String> = None;
let mut vcpu: Option<i32> = None;
let mut envs: Vec<String> = Vec::new();
let mut files: Vec<String> = Vec::new();
let mut artifacts = false;
let mut output_dir: Option<String> = None;
let mut source_file: Option<String> = None;
let mut i = 1;
while i < args.len() {
match args[i].as_str() {
"-k" => {
i += 1;
if i < args.len() {
api_key = Some(args[i].clone());
}
}
"-n" => {
i += 1;
if i < args.len() {
network = Some(args[i].clone());
}
}
"-v" => {
i += 1;
if i < args.len() {
vcpu = args[i].parse().ok();
}
}
"-e" => {
i += 1;
if i < args.len() {
envs.push(args[i].clone());
}
}
"-f" => {
i += 1;
if i < args.len() {
files.push(args[i].clone());
}
}
"-a" => artifacts = true,
"-o" => {
i += 1;
if i < args.len() {
output_dir = Some(args[i].clone());
}
}
"session" => {
let (public_key, secret_key) = get_api_keys(api_key.as_deref());
// Collect -f files for session
let mut session_files: Vec<String> = Vec::new();
let mut j = i + 1;
while j < args.len() {
if args[j] == "-f" && j + 1 < args.len() {
session_files.push(args[j + 1].clone());
j += 2;
} else {
j += 1;
}
}
cmd_session(
args.contains(&"--list".to_string()),
args.iter().position(|x| x == "--kill").and_then(|p| args.get(p + 1)).map(|s| s.as_str()),
args.iter().position(|x| x == "--shell").and_then(|p| args.get(p + 1)).map(|s| s.as_str()),
network.as_deref(),
vcpu,
args.contains(&"--tmux".to_string()),
args.contains(&"--screen".to_string()),
&session_files,
&public_key,
&secret_key,
);
return;
}
"service" => {
let (public_key, secret_key) = get_api_keys(api_key.as_deref());
// Collect -f files and -e envs for service
let mut service_files: Vec<String> = Vec::new();
let mut service_envs: Vec<String> = Vec::new();
let mut env_file_opt: Option<String> = None;
let mut env_action: Option<String> = None;
let mut env_target: Option<String> = None;
let mut j = i + 1;
while j < args.len() {
if args[j] == "-f" && j + 1 < args.len() {
service_files.push(args[j + 1].clone());
j += 2;
} else if args[j] == "-e" && j + 1 < args.len() {
service_envs.push(args[j + 1].clone());
j += 2;
} else if args[j] == "--env-file" && j + 1 < args.len() {
env_file_opt = Some(args[j + 1].clone());
j += 2;
} else if args[j] == "env" && env_action.is_none() {
// service env <action> <target>
if j + 1 < args.len() && !args[j + 1].starts_with('-') {
env_action = Some(args[j + 1].clone());
if j + 2 < args.len() && !args[j + 2].starts_with('-') {
env_target = Some(args[j + 2].clone());
j += 3;
} else {
j += 2;
}
} else {
j += 1;
}
} else {
j += 1;
}
}
cmd_service(
args.iter().position(|x| x == "--name").and_then(|p| args.get(p + 1)).map(|s| s.as_str()),
args.iter().position(|x| x == "--ports").and_then(|p| args.get(p + 1)).map(|s| s.as_str()),
args.iter().position(|x| x == "--domains").and_then(|p| args.get(p + 1)).map(|s| s.as_str()),
args.iter().position(|x| x == "--type").and_then(|p| args.get(p + 1)).map(|s| s.as_str()),
args.iter().position(|x| x == "--bootstrap").and_then(|p| args.get(p + 1)).map(|s| s.as_str()),
args.iter().position(|x| x == "--bootstrap-file").and_then(|p| args.get(p + 1)).map(|s| s.as_str()),
&service_files,
args.contains(&"--list".to_string()),
args.iter().position(|x| x == "--info").and_then(|p| args.get(p + 1)).map(|s| s.as_str()),
args.iter().position(|x| x == "--logs").and_then(|p| args.get(p + 1)).map(|s| s.as_str()),
args.iter().position(|x| x == "--tail").and_then(|p| args.get(p + 1)).map(|s| s.as_str()),
args.iter().position(|x| x == "--freeze").and_then(|p| args.get(p + 1)).map(|s| s.as_str()),
args.iter().position(|x| x == "--unfreeze").and_then(|p| args.get(p + 1)).map(|s| s.as_str()),
args.iter().position(|x| x == "--destroy").and_then(|p| args.get(p + 1)).map(|s| s.as_str()),
args.iter().position(|x| x == "--resize").and_then(|p| args.get(p + 1)).map(|s| s.as_str()),
args.iter().position(|x| x == "--execute").and_then(|p| args.get(p + 1)).map(|s| s.as_str()),
args.iter().position(|x| x == "--command").and_then(|p| args.get(p + 1)).map(|s| s.as_str()),
args.iter().position(|x| x == "--dump-bootstrap").and_then(|p| args.get(p + 1)).map(|s| s.as_str()),
args.iter().position(|x| x == "--dump-file").and_then(|p| args.get(p + 1)).map(|s| s.as_str()),
network.as_deref(),
vcpu,
&service_envs,
env_file_opt.as_deref(),
env_action.as_deref(),
env_target.as_deref(),
&public_key,
&secret_key,
);
return;
}
"key" => {
let (public_key, secret_key) = get_api_keys(api_key.as_deref());
cmd_key(
args.contains(&"--extend".to_string()),
&public_key,
&secret_key,
);
return;
}
_ => {
if args[i].starts_with('-') {
eprintln!("{}Unknown option: {}{}", RED, args[i], RESET);
std::process::exit(1);
} else {
source_file = Some(args[i].clone());
}
}
}
i += 1;
}
// Execute mode
if let Some(file) = source_file {
let (public_key, secret_key) = get_api_keys(api_key.as_deref());
cmd_execute(
&file,
envs,
files,
artifacts,
output_dir.as_deref(),
network.as_deref(),
vcpu,
&public_key,
&secret_key,
);
} else {
eprintln!("{}Error: No source file specified{}", RED, RESET);
process::exit(1);
}
}