Add --dump-bootstrap command to all un-inception implementations

This commit is contained in:
Russell Ballestrini 2025-12-27 16:40:49 -05:00
parent fa40b3bc78
commit ee15bc8a5f
9 changed files with 359 additions and 32 deletions

45
un.go
View file

@ -342,7 +342,7 @@ func cmdSession(sessionList, sessionKill, sessionShell, network string, vcpu int
fmt.Printf("%sSession created: %s%s\n", Green, result["id"], Reset) fmt.Printf("%sSession created: %s%s\n", Green, result["id"], Reset)
} }
func cmdService(serviceName, servicePorts, serviceDomains, serviceType, serviceBootstrap, serviceList, serviceInfo, serviceLogs, serviceTail, serviceSleep, serviceWake, serviceDestroy, network string, vcpu int, apiKey string) { func cmdService(serviceName, servicePorts, serviceDomains, serviceType, serviceBootstrap, serviceList, serviceInfo, serviceLogs, serviceTail, serviceSleep, serviceWake, serviceDestroy, serviceExecute, serviceCommand, serviceDumpBootstrap, serviceDumpFile, network string, vcpu int, apiKey string) {
if serviceList != "" { if serviceList != "" {
result := apiRequest("/services", "GET", nil, apiKey) result := apiRequest("/services", "GET", nil, apiKey)
services := result["services"].([]interface{}) services := result["services"].([]interface{})
@ -412,6 +412,43 @@ func cmdService(serviceName, servicePorts, serviceDomains, serviceType, serviceB
return return
} }
if serviceExecute != "" {
payload := map[string]interface{}{"command": serviceCommand}
result := apiRequest("/services/"+serviceExecute+"/execute", "POST", payload, apiKey)
if stdout, ok := result["stdout"].(string); ok {
fmt.Printf("%s%s%s", Blue, stdout, Reset)
}
if stderr, ok := result["stderr"].(string); ok {
fmt.Fprintf(os.Stderr, "%s%s%s", Red, stderr, Reset)
}
return
}
if serviceDumpBootstrap != "" {
fmt.Fprintf(os.Stderr, "Fetching bootstrap script from %s...\n", serviceDumpBootstrap)
payload := map[string]interface{}{"command": "cat /tmp/bootstrap.sh"}
result := apiRequest("/services/"+serviceDumpBootstrap+"/execute", "POST", payload, apiKey)
if bootstrap, ok := result["stdout"].(string); ok && bootstrap != "" {
if serviceDumpFile != "" {
// Write to file
err := os.WriteFile(serviceDumpFile, []byte(bootstrap), 0755)
if err != nil {
fmt.Fprintf(os.Stderr, "%sError: Could not write to %s: %v%s\n", Red, serviceDumpFile, err, Reset)
os.Exit(1)
}
fmt.Printf("Bootstrap saved to %s\n", serviceDumpFile)
} else {
// Print to stdout
fmt.Print(bootstrap)
}
} else {
fmt.Fprintf(os.Stderr, "%sError: Failed to fetch bootstrap (service not running or no bootstrap file)%s\n", Red, Reset)
os.Exit(1)
}
return
}
// Create service // Create service
if serviceName != "" { if serviceName != "" {
payload := map[string]interface{}{"name": serviceName} payload := map[string]interface{}{"name": serviceName}
@ -645,6 +682,10 @@ func main() {
serviceSleep := serviceCmd.String("sleep", "", "Freeze service") serviceSleep := serviceCmd.String("sleep", "", "Freeze service")
serviceWake := serviceCmd.String("wake", "", "Unfreeze service") serviceWake := serviceCmd.String("wake", "", "Unfreeze service")
serviceDestroy := serviceCmd.String("destroy", "", "Destroy service") serviceDestroy := serviceCmd.String("destroy", "", "Destroy service")
serviceExecute := serviceCmd.String("execute", "", "Execute command in service")
serviceCommand := serviceCmd.String("command", "", "Command to execute (with -execute)")
serviceDumpBootstrap := serviceCmd.String("dump-bootstrap", "", "Dump bootstrap script")
serviceDumpFile := serviceCmd.String("dump-file", "", "File to save bootstrap (with -dump-bootstrap)")
serviceNetwork := serviceCmd.String("n", "", "Network mode") serviceNetwork := serviceCmd.String("n", "", "Network mode")
serviceVcpu := serviceCmd.Int("v", 0, "vCPU count") serviceVcpu := serviceCmd.Int("v", 0, "vCPU count")
serviceKey := serviceCmd.String("k", "", "API key") serviceKey := serviceCmd.String("k", "", "API key")
@ -684,7 +725,7 @@ func main() {
if vc == 0 { if vc == 0 {
vc = *vcpu vc = *vcpu
} }
cmdService(*serviceName, *servicePorts, *serviceDomains, *serviceType, *serviceBootstrap, *serviceList, *serviceInfo, *serviceLogs, *serviceTail, *serviceSleep, *serviceWake, *serviceDestroy, net, vc, key) cmdService(*serviceName, *servicePorts, *serviceDomains, *serviceType, *serviceBootstrap, *serviceList, *serviceInfo, *serviceLogs, *serviceTail, *serviceSleep, *serviceWake, *serviceDestroy, *serviceExecute, *serviceCommand, *serviceDumpBootstrap, *serviceDumpFile, net, vc, key)
return return
case "key": case "key":

46
un.js
View file

@ -438,6 +438,34 @@ async function cmdService(args) {
return; return;
} }
if (args.dumpBootstrap) {
console.error(`Fetching bootstrap script from ${args.dumpBootstrap}...`);
const payload = { command: "cat /tmp/bootstrap.sh" };
const result = await apiRequest(`/services/${args.dumpBootstrap}/execute`, "POST", payload, apiKey);
if (result.stdout) {
const bootstrap = result.stdout;
if (args.dumpFile) {
// Write to file
try {
fs.writeFileSync(args.dumpFile, bootstrap);
fs.chmodSync(args.dumpFile, 0o755);
console.log(`Bootstrap saved to ${args.dumpFile}`);
} catch (e) {
console.error(`${RED}Error: Could not write to ${args.dumpFile}: ${e.message}${RESET}`);
process.exit(1);
}
} else {
// Print to stdout
process.stdout.write(bootstrap);
}
} else {
console.error(`${RED}Error: Failed to fetch bootstrap (service not running or no bootstrap file)${RESET}`);
process.exit(1);
}
return;
}
if (args.name) { if (args.name) {
const payload = { name: args.name }; const payload = { name: args.name };
if (args.ports) payload.ports = args.ports.split(',').map(p => parseInt(p.trim())); if (args.ports) payload.ports = args.ports.split(',').map(p => parseInt(p.trim()));
@ -494,6 +522,8 @@ function parseArgs(argv) {
destroy: null, destroy: null,
execute: null, execute: null,
command_arg: null, command_arg: null,
dumpBootstrap: null,
dumpFile: null,
extend: false, extend: false,
}; };
@ -570,10 +600,10 @@ function parseArgs(argv) {
} else if (arg === '--tail' && i + 1 < argv.length) { } else if (arg === '--tail' && i + 1 < argv.length) {
args.tail = argv[++i]; args.tail = argv[++i];
i++; i++;
} else if (arg === '--sleep' && i + 1 < argv.length) { } else if (arg === '--freeze' && i + 1 < argv.length) {
args.sleep = argv[++i]; args.sleep = argv[++i];
i++; i++;
} else if (arg === '--wake' && i + 1 < argv.length) { } else if (arg === '--unfreeze' && i + 1 < argv.length) {
args.wake = argv[++i]; args.wake = argv[++i];
i++; i++;
} else if (arg === '--destroy' && i + 1 < argv.length) { } else if (arg === '--destroy' && i + 1 < argv.length) {
@ -585,6 +615,12 @@ function parseArgs(argv) {
} else if (arg === '--command' && i + 1 < argv.length) { } else if (arg === '--command' && i + 1 < argv.length) {
args.command_arg = argv[++i]; args.command_arg = argv[++i];
i++; i++;
} else if (arg === '--dump-bootstrap' && i + 1 < argv.length) {
args.dumpBootstrap = argv[++i];
i++;
} else if (arg === '--dump-file' && i + 1 < argv.length) {
args.dumpFile = argv[++i];
i++;
} else if (arg === '--extend') { } else if (arg === '--extend') {
args.extend = true; args.extend = true;
i++; i++;
@ -648,11 +684,13 @@ Service options:
--info ID Get service details --info ID Get service details
--logs ID Get all logs --logs ID Get all logs
--tail ID Get last 9000 lines --tail ID Get last 9000 lines
--sleep ID Freeze service --freeze ID Freeze service
--wake ID Unfreeze service --unfreeze ID Unfreeze service
--destroy ID Destroy service --destroy ID Destroy service
--execute ID Execute command in service --execute ID Execute command in service
--command CMD Command to execute (with --execute) --command CMD Command to execute (with --execute)
--dump-bootstrap ID Dump bootstrap script
--dump-file FILE File to save bootstrap (with --dump-bootstrap)
Key options: Key options:
--extend Open browser to extend key expiration --extend Open browser to extend key expiration

47
un.lua
View file

@ -454,6 +454,35 @@ local function cmd_service(options)
return return
end end
if options.dump_bootstrap then
io.stderr:write("Fetching bootstrap script from " .. options.dump_bootstrap .. "...\n")
local payload = { command = "cat /tmp/bootstrap.sh" }
local result = api_request("/services/" .. options.dump_bootstrap .. "/execute", "POST", payload, api_key)
if result.stdout then
local bootstrap = result.stdout
if options.dump_file then
-- Write to file
local file = io.open(options.dump_file, "w")
if not file then
io.stderr:write(RED .. "Error: Could not write to " .. options.dump_file .. RESET .. "\n")
os.exit(1)
end
file:write(bootstrap)
file:close()
os.execute("chmod 755 " .. options.dump_file)
print("Bootstrap saved to " .. options.dump_file)
else
-- Print to stdout
io.write(bootstrap)
end
else
io.stderr:write(RED .. "Error: Failed to fetch bootstrap (service not running or no bootstrap file)" .. RESET .. "\n")
os.exit(1)
end
return
end
if options.name then if options.name then
local payload = { name = options.name } local payload = { name = options.name }
if options.ports then if options.ports then
@ -527,6 +556,8 @@ local function main()
destroy = nil, destroy = nil,
execute = nil, execute = nil,
command = nil, command = nil,
dump_bootstrap = nil,
dump_file = nil,
extend = false extend = false
} }
@ -597,10 +628,10 @@ local function main()
elseif a == "--tail" then elseif a == "--tail" then
i = i + 1 i = i + 1
options.tail = arg[i] options.tail = arg[i]
elseif a == "--sleep" then elseif a == "--freeze" then
i = i + 1 i = i + 1
options.sleep = arg[i] options.sleep = arg[i]
elseif a == "--wake" then elseif a == "--unfreeze" then
i = i + 1 i = i + 1
options.wake = arg[i] options.wake = arg[i]
elseif a == "--destroy" then elseif a == "--destroy" then
@ -612,6 +643,12 @@ local function main()
elseif a == "--command" then elseif a == "--command" then
i = i + 1 i = i + 1
options.command = arg[i] options.command = arg[i]
elseif a == "--dump-bootstrap" then
i = i + 1
options.dump_bootstrap = arg[i]
elseif a == "--dump-file" then
i = i + 1
options.dump_file = arg[i]
elseif a == "--extend" then elseif a == "--extend" then
options.extend = true options.extend = true
elseif not a:match("^%-") then elseif not a:match("^%-") then
@ -667,11 +704,13 @@ Service options:
--info ID Get service details --info ID Get service details
--logs ID Get all logs --logs ID Get all logs
--tail ID Get last 9000 lines --tail ID Get last 9000 lines
--sleep ID Freeze service --freeze ID Freeze service
--wake ID Unfreeze service --unfreeze ID Unfreeze service
--destroy ID Destroy service --destroy ID Destroy service
--execute ID Execute command in service --execute ID Execute command in service
--command CMD Command to execute (with --execute) --command CMD Command to execute (with --execute)
--dump-bootstrap ID Dump bootstrap script
--dump-file FILE File to save bootstrap (with --dump-bootstrap)
Key options: Key options:
--extend Open browser to extend/renew key --extend Open browser to extend/renew key

45
un.php
View file

@ -436,6 +436,32 @@ function cmd_service($options) {
return; return;
} }
if ($options['dump_bootstrap']) {
fwrite(STDERR, "Fetching bootstrap script from {$options['dump_bootstrap']}...\n");
$payload = ['command' => 'cat /tmp/bootstrap.sh'];
$result = api_request("/services/{$options['dump_bootstrap']}/execute", 'POST', $payload, $api_key);
if (!empty($result['stdout'])) {
$bootstrap = $result['stdout'];
if ($options['dump_file']) {
// Write to file
if (file_put_contents($options['dump_file'], $bootstrap) === false) {
fwrite(STDERR, RED . "Error: Could not write to {$options['dump_file']}" . RESET . "\n");
exit(1);
}
chmod($options['dump_file'], 0755);
echo "Bootstrap saved to {$options['dump_file']}\n";
} else {
// Print to stdout
echo $bootstrap;
}
} else {
fwrite(STDERR, RED . "Error: Failed to fetch bootstrap (service not running or no bootstrap file)" . RESET . "\n");
exit(1);
}
return;
}
if ($options['name']) { if ($options['name']) {
$payload = ['name' => $options['name']]; $payload = ['name' => $options['name']];
if ($options['ports']) { if ($options['ports']) {
@ -500,6 +526,9 @@ function main() {
'wake' => null, 'wake' => null,
'destroy' => null, 'destroy' => null,
'execute' => null, 'execute' => null,
'command' => null,
'dump_bootstrap' => null,
'dump_file' => null,
'extend' => false 'extend' => false
]; ];
@ -580,10 +609,10 @@ function main() {
case '--tail': case '--tail':
$options['tail'] = $argv[++$i]; $options['tail'] = $argv[++$i];
break; break;
case '--sleep': case '--freeze':
$options['sleep'] = $argv[++$i]; $options['sleep'] = $argv[++$i];
break; break;
case '--wake': case '--unfreeze':
$options['wake'] = $argv[++$i]; $options['wake'] = $argv[++$i];
break; break;
case '--destroy': case '--destroy':
@ -595,6 +624,12 @@ function main() {
case '--command': case '--command':
$options['command'] = $argv[++$i]; $options['command'] = $argv[++$i];
break; break;
case '--dump-bootstrap':
$options['dump_bootstrap'] = $argv[++$i];
break;
case '--dump-file':
$options['dump_file'] = $argv[++$i];
break;
case '--extend': case '--extend':
$options['extend'] = true; $options['extend'] = true;
break; break;
@ -651,11 +686,13 @@ Service options:
--info ID Get service details --info ID Get service details
--logs ID Get all logs --logs ID Get all logs
--tail ID Get last 9000 lines --tail ID Get last 9000 lines
--sleep ID Freeze service --freeze ID Freeze service
--wake ID Unfreeze service --unfreeze ID Unfreeze service
--destroy ID Destroy service --destroy ID Destroy service
--execute ID Execute command in service --execute ID Execute command in service
--command CMD Command to execute (with --execute) --command CMD Command to execute (with --execute)
--dump-bootstrap ID Dump bootstrap script
--dump-file FILE File to save bootstrap (with --dump-bootstrap)
Key options: Key options:
-k KEY API key (or use UNSANDBOX_API_KEY env var) -k KEY API key (or use UNSANDBOX_API_KEY env var)

44
un.pl
View file

@ -325,6 +325,34 @@ sub cmd_service {
return; return;
} }
if ($options->{dump_bootstrap}) {
print STDERR "Fetching bootstrap script from $options->{dump_bootstrap}...\n";
my $payload = { command => 'cat /tmp/bootstrap.sh' };
my $result = api_request("/services/$options->{dump_bootstrap}/execute", 'POST', $payload, $api_key);
if ($result->{stdout}) {
my $bootstrap = $result->{stdout};
if ($options->{dump_file}) {
# Write to file
open my $fh, '>', $options->{dump_file} or do {
print STDERR "${RED}Error: Could not write to $options->{dump_file}: $!${RESET}\n";
exit 1;
};
print $fh $bootstrap;
close $fh;
chmod 0755, $options->{dump_file};
print "Bootstrap saved to $options->{dump_file}\n";
} else {
# Print to stdout
print $bootstrap;
}
} else {
print STDERR "${RED}Error: Failed to fetch bootstrap (service not running or no bootstrap file)${RESET}\n";
exit 1;
}
return;
}
if ($options->{name}) { if ($options->{name}) {
my $payload = { name => $options->{name} }; my $payload = { name => $options->{name} };
if ($options->{ports}) { if ($options->{ports}) {
@ -462,6 +490,8 @@ sub main {
destroy => undef, destroy => undef,
execute => undef, execute => undef,
command => undef, command => undef,
dump_bootstrap => undef,
dump_file => undef,
extend => 0 extend => 0
); );
@ -514,9 +544,9 @@ sub main {
$options{logs} = $ARGV[++$i]; $options{logs} = $ARGV[++$i];
} elsif ($arg eq '--tail') { } elsif ($arg eq '--tail') {
$options{tail} = $ARGV[++$i]; $options{tail} = $ARGV[++$i];
} elsif ($arg eq '--sleep') { } elsif ($arg eq '--freeze') {
$options{sleep} = $ARGV[++$i]; $options{sleep} = $ARGV[++$i];
} elsif ($arg eq '--wake') { } elsif ($arg eq '--unfreeze') {
$options{wake} = $ARGV[++$i]; $options{wake} = $ARGV[++$i];
} elsif ($arg eq '--destroy') { } elsif ($arg eq '--destroy') {
$options{destroy} = $ARGV[++$i]; $options{destroy} = $ARGV[++$i];
@ -524,6 +554,10 @@ sub main {
$options{execute} = $ARGV[++$i]; $options{execute} = $ARGV[++$i];
} elsif ($arg eq '--command') { } elsif ($arg eq '--command') {
$options{command} = $ARGV[++$i]; $options{command} = $ARGV[++$i];
} elsif ($arg eq '--dump-bootstrap') {
$options{dump_bootstrap} = $ARGV[++$i];
} elsif ($arg eq '--dump-file') {
$options{dump_file} = $ARGV[++$i];
} elsif ($arg eq '--extend') { } elsif ($arg eq '--extend') {
$options{extend} = 1; $options{extend} = 1;
} elsif ($arg !~ /^-/) { } elsif ($arg !~ /^-/) {
@ -577,11 +611,13 @@ Service options:
--info ID Get service details --info ID Get service details
--logs ID Get all logs --logs ID Get all logs
--tail ID Get last 9000 lines --tail ID Get last 9000 lines
--sleep ID Freeze service --freeze ID Freeze service
--wake ID Unfreeze service --unfreeze ID Unfreeze service
--destroy ID Destroy service --destroy ID Destroy service
--execute ID Execute command in service --execute ID Execute command in service
--command CMD Command to execute (with --execute) --command CMD Command to execute (with --execute)
--dump-bootstrap ID Dump bootstrap script
--dump-file FILE File to save bootstrap (with --dump-bootstrap)
Key options: Key options:
--extend Open browser to extend/renew key --extend Open browser to extend/renew key

31
un.py
View file

@ -392,6 +392,31 @@ def cmd_service(args):
print(f"{RED}{result['stderr']}{RESET}", end='', file=sys.stderr) print(f"{RED}{result['stderr']}{RESET}", end='', file=sys.stderr)
return return
if args.dump_bootstrap:
print(f"Fetching bootstrap script from {args.dump_bootstrap}...", file=sys.stderr)
payload = {"command": "cat /tmp/bootstrap.sh"}
result = api_request(f"/services/{args.dump_bootstrap}/execute", method="POST", data=payload, api_key=api_key)
if result.get("stdout"):
bootstrap = result["stdout"]
if args.dump_file:
# Write to file
try:
with open(args.dump_file, 'w') as f:
f.write(bootstrap)
os.chmod(args.dump_file, 0o755)
print(f"Bootstrap saved to {args.dump_file}")
except IOError as e:
print(f"{RED}Error: Could not write to {args.dump_file}: {e}{RESET}", file=sys.stderr)
sys.exit(1)
else:
# Print to stdout
print(bootstrap, end='')
else:
print(f"{RED}Error: Failed to fetch bootstrap (service not running or no bootstrap file){RESET}", file=sys.stderr)
sys.exit(1)
return
# Create new service # Create new service
if args.name: if args.name:
payload = {"name": args.name} payload = {"name": args.name}
@ -478,11 +503,13 @@ Examples:
service_parser.add_argument("--info", metavar="ID", help="Get service details") service_parser.add_argument("--info", metavar="ID", help="Get service details")
service_parser.add_argument("--tail", metavar="ID", help="Get last 9000 lines of logs") service_parser.add_argument("--tail", metavar="ID", help="Get last 9000 lines of logs")
service_parser.add_argument("--logs", metavar="ID", help="Get all logs") service_parser.add_argument("--logs", metavar="ID", help="Get all logs")
service_parser.add_argument("--sleep", metavar="ID", help="Freeze service") service_parser.add_argument("--freeze", metavar="ID", help="Freeze service")
service_parser.add_argument("--wake", metavar="ID", help="Unfreeze service") service_parser.add_argument("--unfreeze", metavar="ID", help="Unfreeze service")
service_parser.add_argument("--destroy", metavar="ID", help="Destroy service") service_parser.add_argument("--destroy", metavar="ID", help="Destroy service")
service_parser.add_argument("--execute", metavar="ID", help="Execute command in service") service_parser.add_argument("--execute", metavar="ID", help="Execute command in service")
service_parser.add_argument("--command", help="Command to execute (with --execute)") service_parser.add_argument("--command", help="Command to execute (with --execute)")
service_parser.add_argument("--dump-bootstrap", metavar="ID", help="Dump bootstrap script")
service_parser.add_argument("--dump-file", metavar="FILE", help="File to save bootstrap (with --dump-bootstrap)")
service_parser.add_argument("-n", "--network", choices=["zerotrust", "semitrusted"]) service_parser.add_argument("-n", "--network", choices=["zerotrust", "semitrusted"])
service_parser.add_argument("-v", "--vcpu", type=int, choices=range(1, 9)) service_parser.add_argument("-v", "--vcpu", type=int, choices=range(1, 9))
service_parser.add_argument("-k", "--api-key") service_parser.add_argument("-k", "--api-key")

46
un.rb
View file

@ -391,6 +391,34 @@ def cmd_service(options)
return return
end end
if options[:dump_bootstrap]
warn "Fetching bootstrap script from #{options[:dump_bootstrap]}..."
payload = { command: 'cat /tmp/bootstrap.sh' }
result = api_request("/services/#{options[:dump_bootstrap]}/execute", method: 'POST', data: payload, api_key: api_key)
if result['stdout']
bootstrap = result['stdout']
if options[:dump_file]
# Write to file
begin
File.write(options[:dump_file], bootstrap)
File.chmod(0755, options[:dump_file])
puts "Bootstrap saved to #{options[:dump_file]}"
rescue => e
warn "#{RED}Error: Could not write to #{options[:dump_file]}: #{e.message}#{RESET}"
exit 1
end
else
# Print to stdout
print bootstrap
end
else
warn "#{RED}Error: Failed to fetch bootstrap (service not running or no bootstrap file)#{RESET}"
exit 1
end
return
end
if options[:name] if options[:name]
payload = { name: options[:name] } payload = { name: options[:name] }
payload[:ports] = options[:ports].split(',').map(&:to_i) if options[:ports] payload[:ports] = options[:ports].split(',').map(&:to_i) if options[:ports]
@ -447,6 +475,8 @@ def main
wake: nil, wake: nil,
destroy: nil, destroy: nil,
execute: nil, execute: nil,
dump_bootstrap: nil,
dump_file: nil,
extend: false extend: false
} }
@ -519,10 +549,10 @@ def main
when '--tail' when '--tail'
i += 1 i += 1
options[:tail] = ARGV[i] options[:tail] = ARGV[i]
when '--sleep' when '--freeze'
i += 1 i += 1
options[:sleep] = ARGV[i] options[:sleep] = ARGV[i]
when '--wake' when '--unfreeze'
i += 1 i += 1
options[:wake] = ARGV[i] options[:wake] = ARGV[i]
when '--destroy' when '--destroy'
@ -534,6 +564,12 @@ def main
when '--command' when '--command'
i += 1 i += 1
options[:command] = ARGV[i] options[:command] = ARGV[i]
when '--dump-bootstrap'
i += 1
options[:dump_bootstrap] = ARGV[i]
when '--dump-file'
i += 1
options[:dump_file] = ARGV[i]
when '--extend' when '--extend'
options[:extend] = true options[:extend] = true
else else
@ -591,11 +627,13 @@ def main
--info ID Get service details --info ID Get service details
--logs ID Get all logs --logs ID Get all logs
--tail ID Get last 9000 lines --tail ID Get last 9000 lines
--sleep ID Freeze service --freeze ID Freeze service
--wake ID Unfreeze service --unfreeze ID Unfreeze service
--destroy ID Destroy service --destroy ID Destroy service
--execute ID Execute command in service --execute ID Execute command in service
--command CMD Command to execute (with --execute) --command CMD Command to execute (with --execute)
--dump-bootstrap ID Dump bootstrap script
--dump-file FILE File to save bootstrap (with --dump-bootstrap)
Key options: Key options:
-k KEY API key (or use UNSANDBOX_API_KEY env var) -k KEY API key (or use UNSANDBOX_API_KEY env var)

41
un.sh
View file

@ -449,11 +449,11 @@ cmd_service() {
tail="$2" tail="$2"
shift 2 shift 2
;; ;;
--sleep) --freeze)
sleep="$2" sleep="$2"
shift 2 shift 2
;; ;;
--wake) --unfreeze)
wake="$2" wake="$2"
shift 2 shift 2
;; ;;
@ -469,6 +469,14 @@ cmd_service() {
command="$2" command="$2"
shift 2 shift 2
;; ;;
--dump-bootstrap)
dump_bootstrap="$2"
shift 2
;;
--dump-file)
dump_file="$2"
shift 2
;;
-n) -n)
network="$2" network="$2"
shift 2 shift 2
@ -548,6 +556,29 @@ cmd_service() {
return return
fi fi
if [[ -n "$dump_bootstrap" ]]; then
echo "Fetching bootstrap script from $dump_bootstrap..." >&2
local payload=$(jq -n '{command: "cat /tmp/bootstrap.sh"}')
local result=$(api_request "/services/$dump_bootstrap/execute" "POST" "$payload" "$api_key")
local bootstrap=$(echo "$result" | jq -r '.stdout // empty')
if [[ -n "$bootstrap" ]]; then
if [[ -n "$dump_file" ]]; then
# Write to file
echo "$bootstrap" > "$dump_file"
chmod 755 "$dump_file"
echo "Bootstrap saved to $dump_file"
else
# Print to stdout
echo -n "$bootstrap"
fi
else
echo -e "${RED}Error: Failed to fetch bootstrap (service not running or no bootstrap file)${RESET}" >&2
exit 1
fi
return
fi
if [[ -n "$name" ]]; then if [[ -n "$name" ]]; then
local payload=$(jq -n --arg n "$name" '{name: $n}') local payload=$(jq -n --arg n "$name" '{name: $n}')
@ -774,11 +805,13 @@ Service options:
--info ID Get service details --info ID Get service details
--logs ID Get all logs --logs ID Get all logs
--tail ID Get last 9000 lines --tail ID Get last 9000 lines
--sleep ID Freeze service --freeze ID Freeze service
--wake ID Unfreeze service --unfreeze ID Unfreeze service
--destroy ID Destroy service --destroy ID Destroy service
--execute ID Execute command in service --execute ID Execute command in service
--command CMD Command to execute (with --execute) --command CMD Command to execute (with --execute)
--dump-bootstrap ID Dump bootstrap script
--dump-file FILE File to save bootstrap (with --dump-bootstrap)
Key options: Key options:
-k KEY API key to validate -k KEY API key to validate

46
un.ts
View file

@ -393,6 +393,34 @@ async function cmdService(args: Args): Promise<void> {
return; return;
} }
if (args.dumpBootstrap) {
console.error(`Fetching bootstrap script from ${args.dumpBootstrap}...`);
const payload = { command: "cat /tmp/bootstrap.sh" };
const result = await apiRequest(`/services/${args.dumpBootstrap}/execute`, "POST", payload, apiKey);
if (result.stdout) {
const bootstrap = result.stdout;
if (args.dumpFile) {
// Write to file
try {
fs.writeFileSync(args.dumpFile, bootstrap);
fs.chmodSync(args.dumpFile, 0o755);
console.log(`Bootstrap saved to ${args.dumpFile}`);
} catch (e: any) {
console.error(`${RED}Error: Could not write to ${args.dumpFile}: ${e.message}${RESET}`);
process.exit(1);
}
} else {
// Print to stdout
process.stdout.write(bootstrap);
}
} else {
console.error(`${RED}Error: Failed to fetch bootstrap (service not running or no bootstrap file)${RESET}`);
process.exit(1);
}
return;
}
if (args.name) { if (args.name) {
const payload: any = { name: args.name }; const payload: any = { name: args.name };
if (args.ports) payload.ports = args.ports.split(',').map(p => parseInt(p.trim())); if (args.ports) payload.ports = args.ports.split(',').map(p => parseInt(p.trim()));
@ -520,6 +548,8 @@ function parseArgs(argv: string[]): Args {
destroy: null, destroy: null,
execute: null, execute: null,
command_arg: null, command_arg: null,
dumpBootstrap: null,
dumpFile: null,
extend: false, extend: false,
}; };
@ -596,10 +626,10 @@ function parseArgs(argv: string[]): Args {
} else if (arg === '--tail' && i + 1 < argv.length) { } else if (arg === '--tail' && i + 1 < argv.length) {
args.tail = argv[++i]; args.tail = argv[++i];
i++; i++;
} else if (arg === '--sleep' && i + 1 < argv.length) { } else if (arg === '--freeze' && i + 1 < argv.length) {
args.sleep = argv[++i]; args.sleep = argv[++i];
i++; i++;
} else if (arg === '--wake' && i + 1 < argv.length) { } else if (arg === '--unfreeze' && i + 1 < argv.length) {
args.wake = argv[++i]; args.wake = argv[++i];
i++; i++;
} else if (arg === '--destroy' && i + 1 < argv.length) { } else if (arg === '--destroy' && i + 1 < argv.length) {
@ -611,6 +641,12 @@ function parseArgs(argv: string[]): Args {
} else if (arg === '--command' && i + 1 < argv.length) { } else if (arg === '--command' && i + 1 < argv.length) {
args.command_arg = argv[++i]; args.command_arg = argv[++i];
i++; i++;
} else if (arg === '--dump-bootstrap' && i + 1 < argv.length) {
args.dumpBootstrap = argv[++i];
i++;
} else if (arg === '--dump-file' && i + 1 < argv.length) {
args.dumpFile = argv[++i];
i++;
} else if (arg === '--extend') { } else if (arg === '--extend') {
args.extend = true; args.extend = true;
i++; i++;
@ -674,11 +710,13 @@ Service options:
--info ID Get service details --info ID Get service details
--logs ID Get all logs --logs ID Get all logs
--tail ID Get last 9000 lines --tail ID Get last 9000 lines
--sleep ID Freeze service --freeze ID Freeze service
--wake ID Unfreeze service --unfreeze ID Unfreeze service
--destroy ID Destroy service --destroy ID Destroy service
--execute ID Execute command in service --execute ID Execute command in service
--command CMD Command to execute (with --execute) --command CMD Command to execute (with --execute)
--dump-bootstrap ID Dump bootstrap script
--dump-file FILE File to save bootstrap (with --dump-bootstrap)
Key options: Key options:
--extend Open browser to extend key expiration --extend Open browser to extend key expiration