Add --dump-bootstrap command to remaining 28 un-inception implementations
This commit is contained in:
parent
ee15bc8a5f
commit
dba8ffeedc
33 changed files with 1636 additions and 109 deletions
76
Un.cs
76
Un.cs
|
|
@ -406,6 +406,62 @@ class Un
|
|||
return;
|
||||
}
|
||||
|
||||
if (args.ServiceExecute != null)
|
||||
{
|
||||
var payload = new Dictionary<string, object>
|
||||
{
|
||||
["command"] = args.ServiceCommand
|
||||
};
|
||||
var result = ApiRequest($"/services/{args.ServiceExecute}/execute", "POST", payload, apiKey);
|
||||
if (result.ContainsKey("stdout") && !string.IsNullOrEmpty((string)result["stdout"]))
|
||||
{
|
||||
Console.Write($"{BLUE}{result["stdout"]}{RESET}");
|
||||
}
|
||||
if (result.ContainsKey("stderr") && !string.IsNullOrEmpty((string)result["stderr"]))
|
||||
{
|
||||
Console.Error.Write($"{RED}{result["stderr"]}{RESET}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.ServiceDumpBootstrap != null)
|
||||
{
|
||||
Console.Error.WriteLine($"Fetching bootstrap script from {args.ServiceDumpBootstrap}...");
|
||||
var payload = new Dictionary<string, object>
|
||||
{
|
||||
["command"] = "cat /tmp/bootstrap.sh"
|
||||
};
|
||||
var result = ApiRequest($"/services/{args.ServiceDumpBootstrap}/execute", "POST", payload, apiKey);
|
||||
|
||||
var bootstrap = result.ContainsKey("stdout") ? (string)result["stdout"] : null;
|
||||
if (!string.IsNullOrEmpty(bootstrap))
|
||||
{
|
||||
if (args.ServiceDumpFile != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.WriteAllText(args.ServiceDumpFile, bootstrap);
|
||||
Console.WriteLine($"Bootstrap saved to {args.ServiceDumpFile}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.Error.WriteLine($"{RED}Error: Could not write to {args.ServiceDumpFile}: {e.Message}{RESET}");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Write(bootstrap);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine($"{RED}Error: Failed to fetch bootstrap (service not running or no bootstrap file){RESET}");
|
||||
Environment.Exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.ServiceName != null)
|
||||
{
|
||||
var payload = new Dictionary<string, object>
|
||||
|
|
@ -770,6 +826,10 @@ class Un
|
|||
public string ServiceWake = null;
|
||||
public string ServiceDestroy = null;
|
||||
public string ServiceType = null;
|
||||
public string ServiceExecute = null;
|
||||
public string ServiceCommand = null;
|
||||
public string ServiceDumpBootstrap = null;
|
||||
public string ServiceDumpFile = null;
|
||||
public bool KeyExtend = false;
|
||||
}
|
||||
|
||||
|
|
@ -803,9 +863,13 @@ class Un
|
|||
else if (arg == "--info") result.ServiceInfo = args[++i];
|
||||
else if (arg == "--logs") result.ServiceLogs = args[++i];
|
||||
else if (arg == "--tail") result.ServiceTail = args[++i];
|
||||
else if (arg == "--sleep") result.ServiceSleep = args[++i];
|
||||
else if (arg == "--wake") result.ServiceWake = args[++i];
|
||||
else if (arg == "--freeze") result.ServiceSleep = args[++i];
|
||||
else if (arg == "--unfreeze") result.ServiceWake = args[++i];
|
||||
else if (arg == "--destroy") result.ServiceDestroy = args[++i];
|
||||
else if (arg == "--execute") result.ServiceExecute = args[++i];
|
||||
else if (arg == "--command") result.ServiceCommand = args[++i];
|
||||
else if (arg == "--dump-bootstrap") result.ServiceDumpBootstrap = args[++i];
|
||||
else if (arg == "--dump-file") result.ServiceDumpFile = args[++i];
|
||||
else if (arg == "--extend") result.KeyExtend = true;
|
||||
else if (!arg.StartsWith("-")) result.SourceFile = arg;
|
||||
}
|
||||
|
|
@ -842,9 +906,13 @@ Service options:
|
|||
--info ID Get service details
|
||||
--logs ID Get all logs
|
||||
--tail ID Get last 9000 lines
|
||||
--sleep ID Freeze service
|
||||
--wake ID Unfreeze service
|
||||
--freeze ID Freeze service
|
||||
--unfreeze ID Unfreeze service
|
||||
--destroy ID Destroy service
|
||||
--execute ID Execute command in service
|
||||
--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:
|
||||
--extend Open browser to extend expired key");
|
||||
|
|
|
|||
67
Un.java
67
Un.java
|
|
@ -274,6 +274,49 @@ public class Un {
|
|||
return;
|
||||
}
|
||||
|
||||
if (args.serviceExecute != null) {
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("command", args.serviceCommand);
|
||||
Map<String, Object> result = apiRequest("/services/" + args.serviceExecute + "/execute", "POST", payload, apiKey);
|
||||
String stdout = (String) result.get("stdout");
|
||||
String stderr = (String) result.get("stderr");
|
||||
if (stdout != null && !stdout.isEmpty()) {
|
||||
System.out.print(BLUE + stdout + RESET);
|
||||
}
|
||||
if (stderr != null && !stderr.isEmpty()) {
|
||||
System.err.print(RED + stderr + RESET);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.serviceDumpBootstrap != null) {
|
||||
System.err.println("Fetching bootstrap script from " + args.serviceDumpBootstrap + "...");
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("command", "cat /tmp/bootstrap.sh");
|
||||
Map<String, Object> result = apiRequest("/services/" + args.serviceDumpBootstrap + "/execute", "POST", payload, apiKey);
|
||||
|
||||
String bootstrap = (String) result.get("stdout");
|
||||
if (bootstrap != null && !bootstrap.isEmpty()) {
|
||||
if (args.serviceDumpFile != null) {
|
||||
try {
|
||||
java.nio.file.Path path = java.nio.file.Paths.get(args.serviceDumpFile);
|
||||
java.nio.file.Files.write(path, bootstrap.getBytes());
|
||||
path.toFile().setExecutable(true, false);
|
||||
System.out.println("Bootstrap saved to " + args.serviceDumpFile);
|
||||
} catch (Exception e) {
|
||||
System.err.println(RED + "Error: Could not write to " + args.serviceDumpFile + ": " + e.getMessage() + RESET);
|
||||
System.exit(1);
|
||||
}
|
||||
} else {
|
||||
System.out.print(bootstrap);
|
||||
}
|
||||
} else {
|
||||
System.err.println(RED + "Error: Failed to fetch bootstrap (service not running or no bootstrap file)" + RESET);
|
||||
System.exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.serviceName != null) {
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("name", args.serviceName);
|
||||
|
|
@ -667,6 +710,10 @@ public class Un {
|
|||
String serviceSleep = null;
|
||||
String serviceWake = null;
|
||||
String serviceDestroy = null;
|
||||
String serviceExecute = null;
|
||||
String serviceCommand = null;
|
||||
String serviceDumpBootstrap = null;
|
||||
String serviceDumpFile = null;
|
||||
|
||||
// Key args
|
||||
boolean keyExtend = false;
|
||||
|
|
@ -717,12 +764,20 @@ public class Un {
|
|||
result.serviceLogs = args[++i];
|
||||
} else if (arg.equals("--tail")) {
|
||||
result.serviceTail = args[++i];
|
||||
} else if (arg.equals("--sleep")) {
|
||||
} else if (arg.equals("--freeze")) {
|
||||
result.serviceSleep = args[++i];
|
||||
} else if (arg.equals("--wake")) {
|
||||
} else if (arg.equals("--unfreeze")) {
|
||||
result.serviceWake = args[++i];
|
||||
} else if (arg.equals("--destroy")) {
|
||||
result.serviceDestroy = args[++i];
|
||||
} else if (arg.equals("--execute")) {
|
||||
result.serviceExecute = args[++i];
|
||||
} else if (arg.equals("--command")) {
|
||||
result.serviceCommand = args[++i];
|
||||
} else if (arg.equals("--dump-bootstrap")) {
|
||||
result.serviceDumpBootstrap = args[++i];
|
||||
} else if (arg.equals("--dump-file")) {
|
||||
result.serviceDumpFile = args[++i];
|
||||
} else if (arg.equals("--extend")) {
|
||||
result.keyExtend = true;
|
||||
} else if (!arg.startsWith("-")) {
|
||||
|
|
@ -761,9 +816,13 @@ public class Un {
|
|||
System.out.println(" --info ID Get service details");
|
||||
System.out.println(" --logs ID Get all logs");
|
||||
System.out.println(" --tail ID Get last 9000 lines");
|
||||
System.out.println(" --sleep ID Freeze service");
|
||||
System.out.println(" --wake ID Unfreeze service");
|
||||
System.out.println(" --freeze ID Freeze service");
|
||||
System.out.println(" --unfreeze ID Unfreeze service");
|
||||
System.out.println(" --destroy ID Destroy service");
|
||||
System.out.println(" --execute ID Execute command in service");
|
||||
System.out.println(" --command CMD Command to execute (with --execute)");
|
||||
System.out.println(" --dump-bootstrap ID Dump bootstrap script");
|
||||
System.out.println(" --dump-file FILE File to save bootstrap (with --dump-bootstrap)");
|
||||
System.out.println();
|
||||
System.out.println("Key options:");
|
||||
System.out.println(" --extend Open browser to extend key");
|
||||
|
|
|
|||
50
un.awk
50
un.awk
|
|
@ -188,6 +188,47 @@ function service_destroy(id) {
|
|||
print GREEN "Service destroyed: " id RESET
|
||||
}
|
||||
|
||||
function service_dump_bootstrap(id, dump_file) {
|
||||
api_key = get_api_key()
|
||||
print "Fetching bootstrap script from " id "..." > "/dev/stderr"
|
||||
|
||||
# Build the curl command to execute on the service
|
||||
cmd = "curl -s -X POST '" API_BASE "/services/" id "/execute' " \
|
||||
"-H 'Content-Type: application/json' " \
|
||||
"-H 'Authorization: Bearer " api_key "' " \
|
||||
"-d '{\"command\":\"cat /tmp/bootstrap.sh\"}'"
|
||||
|
||||
response = ""
|
||||
while ((cmd | getline line) > 0) {
|
||||
response = response line
|
||||
}
|
||||
close(cmd)
|
||||
|
||||
# Parse stdout from response
|
||||
if (match(response, /"stdout":"([^"]*)"/, arr)) {
|
||||
stdout = arr[1]
|
||||
# Unescape JSON
|
||||
gsub(/\\n/, "\n", stdout)
|
||||
gsub(/\\t/, "\t", stdout)
|
||||
gsub(/\\"/, "\"", stdout)
|
||||
gsub(/\\\\/, "\\", stdout)
|
||||
|
||||
if (dump_file != "") {
|
||||
# Write to file
|
||||
print stdout > dump_file
|
||||
close(dump_file)
|
||||
system("chmod 755 " dump_file)
|
||||
print "Bootstrap saved to " dump_file
|
||||
} else {
|
||||
# Print to stdout
|
||||
printf "%s", stdout
|
||||
}
|
||||
} else {
|
||||
print RED "Error: Failed to fetch bootstrap (service not running or no bootstrap file)" RESET > "/dev/stderr"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
function service_create(name, ports, domains, service_type, bootstrap) {
|
||||
api_key = get_api_key()
|
||||
|
||||
|
|
@ -337,6 +378,7 @@ function show_help() {
|
|||
print " awk -f un.awk service --list"
|
||||
print " awk -f un.awk service --create --name NAME [--ports PORTS] [--domains DOMAINS] [--type TYPE] [--bootstrap CMD]"
|
||||
print " awk -f un.awk service --destroy ID"
|
||||
print " awk -f un.awk service --dump-bootstrap ID [--dump-file FILE]"
|
||||
print ""
|
||||
print "Service options:"
|
||||
print " --name NAME Service name (required for --create)"
|
||||
|
|
@ -344,6 +386,8 @@ function show_help() {
|
|||
print " --domains DOMAINS Comma-separated domain names"
|
||||
print " --type TYPE Service type for SRV records (minecraft, mumble, teamspeak, source, tcp, udp)"
|
||||
print " --bootstrap CMD Bootstrap command or script"
|
||||
print " --dump-bootstrap ID Dump bootstrap script from service"
|
||||
print " --dump-file FILE Save bootstrap to file (with --dump-bootstrap)"
|
||||
print ""
|
||||
print "Requires: UNSANDBOX_API_KEY environment variable"
|
||||
}
|
||||
|
|
@ -390,6 +434,12 @@ END {
|
|||
service_list()
|
||||
} else if (ARGC >= 4 && ARGV[2] == "--destroy") {
|
||||
service_destroy(ARGV[3])
|
||||
} else if (ARGC >= 4 && ARGV[2] == "--dump-bootstrap") {
|
||||
dump_file = ""
|
||||
if (ARGC >= 6 && ARGV[4] == "--dump-file") {
|
||||
dump_file = ARGV[5]
|
||||
}
|
||||
service_dump_bootstrap(ARGV[3], dump_file)
|
||||
} else if (ARGV[2] == "--create") {
|
||||
# Parse service creation arguments
|
||||
name = ""
|
||||
|
|
|
|||
41
un.clj
41
un.clj
|
|
@ -201,6 +201,31 @@
|
|||
:destroy (do
|
||||
(curl-delete api-key (str "/services/" sid))
|
||||
(println (str green "Service destroyed: " sid reset)))
|
||||
:execute (when (and sid bootstrap)
|
||||
(let [json (str "{\"command\":\"" (escape-json bootstrap) "\"}")
|
||||
response (curl-post api-key (str "/services/" sid "/execute") json)
|
||||
stdout-val (extract-field "stdout" response)]
|
||||
(when stdout-val
|
||||
(print (str blue (unescape-json stdout-val) reset))
|
||||
(flush))))
|
||||
:dump-bootstrap (when sid
|
||||
(binding [*out* *err*]
|
||||
(println (str "Fetching bootstrap script from " sid "...")))
|
||||
(let [json "{\"command\":\"cat /tmp/bootstrap.sh\"}"
|
||||
response (curl-post api-key (str "/services/" sid "/execute") json)
|
||||
stdout-val (extract-field "stdout" response)]
|
||||
(if stdout-val
|
||||
(let [script (unescape-json stdout-val)]
|
||||
(if service-type
|
||||
(do
|
||||
(spit service-type script)
|
||||
(sh "chmod" "755" service-type)
|
||||
(println (str "Bootstrap saved to " service-type)))
|
||||
(print script)))
|
||||
(do
|
||||
(binding [*out* *err*]
|
||||
(println (str red "Error: Failed to fetch bootstrap (service not running or no bootstrap file)" reset)))
|
||||
(System/exit 1)))))
|
||||
:create (when name
|
||||
(let [ports-json (if ports (str ",\"ports\":[" ports "]") "")
|
||||
bootstrap-json (if bootstrap (str ",\"bootstrap\":\"" (escape-json bootstrap) "\"") "")
|
||||
|
|
@ -336,11 +361,11 @@
|
|||
(recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell
|
||||
:logs (second args) service-name service-ports service-bootstrap service-type key-extend mode)
|
||||
|
||||
(and (= mode :service) (= (first args) "--sleep"))
|
||||
(and (= mode :service) (= (first args) "--freeze"))
|
||||
(recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell
|
||||
:sleep (second args) service-name service-ports service-bootstrap service-type key-extend mode)
|
||||
|
||||
(and (= mode :service) (= (first args) "--wake"))
|
||||
(and (= mode :service) (= (first args) "--unfreeze"))
|
||||
(recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell
|
||||
:wake (second args) service-name service-ports service-bootstrap service-type key-extend mode)
|
||||
|
||||
|
|
@ -348,6 +373,18 @@
|
|||
(recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell
|
||||
:destroy (second args) service-name service-ports service-bootstrap service-type key-extend mode)
|
||||
|
||||
(and (= mode :service) (= (first args) "--execute"))
|
||||
(recur (rest (rest (rest args))) file env-vars artifacts out-dir network vcpu session-action session-id session-shell
|
||||
:execute (second args) service-name service-ports (nth args 2) service-type key-extend mode)
|
||||
|
||||
(and (= mode :service) (= (first args) "--dump-bootstrap") (>= (count args) 3) (not (.startsWith (nth args 2) "-")))
|
||||
(recur (rest (rest (rest args))) file env-vars artifacts out-dir network vcpu session-action session-id session-shell
|
||||
:dump-bootstrap (second args) service-name service-ports service-bootstrap (nth args 2) key-extend mode)
|
||||
|
||||
(and (= mode :service) (= (first args) "--dump-bootstrap"))
|
||||
(recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell
|
||||
:dump-bootstrap (second args) service-name service-ports service-bootstrap service-type key-extend mode)
|
||||
|
||||
(and (= mode :service) (= (first args) "--name"))
|
||||
(recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell
|
||||
:create service-id (second args) service-ports service-bootstrap service-type key-extend mode)
|
||||
|
|
|
|||
55
un.cob
55
un.cob
|
|
@ -197,22 +197,25 @@
|
|||
ELSE IF WS-ARG2 = "--logs"
|
||||
ACCEPT WS-ID FROM ARGUMENT-VALUE
|
||||
PERFORM SERVICE-LOGS
|
||||
ELSE IF WS-ARG2 = "--sleep"
|
||||
ELSE IF WS-ARG2 = "--freeze"
|
||||
ACCEPT WS-ID FROM ARGUMENT-VALUE
|
||||
PERFORM SERVICE-SLEEP
|
||||
ELSE IF WS-ARG2 = "--wake"
|
||||
ELSE IF WS-ARG2 = "--unfreeze"
|
||||
ACCEPT WS-ID FROM ARGUMENT-VALUE
|
||||
PERFORM SERVICE-WAKE
|
||||
ELSE IF WS-ARG2 = "--destroy"
|
||||
ACCEPT WS-ID FROM ARGUMENT-VALUE
|
||||
PERFORM SERVICE-DESTROY
|
||||
ELSE IF WS-ARG2 = "--dump-bootstrap"
|
||||
ACCEPT WS-ID FROM ARGUMENT-VALUE
|
||||
PERFORM SERVICE-DUMP-BOOTSTRAP
|
||||
ELSE IF WS-ARG2 = "--name"
|
||||
ACCEPT WS-NAME FROM ARGUMENT-VALUE
|
||||
PERFORM PARSE-SERVICE-CREATE-ARGS
|
||||
PERFORM SERVICE-CREATE
|
||||
ELSE
|
||||
DISPLAY "Error: Use --list, --info, --logs, "
|
||||
"--sleep, --wake, --destroy, or --name" UPON SYSERR
|
||||
"--freeze, --unfreeze, --destroy, --dump-bootstrap, or --name" UPON SYSERR
|
||||
MOVE 1 TO RETURN-CODE
|
||||
END-IF.
|
||||
|
||||
|
|
@ -375,6 +378,52 @@
|
|||
|
||||
CALL "SYSTEM" USING WS-CURL-CMD.
|
||||
|
||||
SERVICE-DUMP-BOOTSTRAP.
|
||||
* Check if WS-ARG3 contains --dump-file argument
|
||||
ACCEPT WS-ARG3 FROM ARGUMENT-VALUE.
|
||||
MOVE SPACES TO WS-BOOTSTRAP.
|
||||
IF WS-ARG3 = "--dump-file"
|
||||
ACCEPT WS-BOOTSTRAP FROM ARGUMENT-VALUE
|
||||
END-IF.
|
||||
|
||||
STRING "echo 'Fetching bootstrap script from "
|
||||
FUNCTION TRIM(WS-ID) "...' >&2; "
|
||||
"RESP=$(curl -s -X POST "
|
||||
"https://api.unsandbox.com/services/"
|
||||
FUNCTION TRIM(WS-ID) "/execute "
|
||||
"-H 'Content-Type: application/json' "
|
||||
"-H 'Authorization: Bearer " FUNCTION TRIM(WS-API-KEY)
|
||||
"' -d '{\"command\":\"cat /tmp/bootstrap.sh\"}'); "
|
||||
"STDOUT=$(echo \"$RESP\" | jq -r '.stdout // empty'); "
|
||||
"if [ -n \"$STDOUT\" ]; then "
|
||||
DELIMITED BY SIZE INTO WS-CURL-CMD
|
||||
END-STRING.
|
||||
|
||||
IF WS-BOOTSTRAP NOT = SPACES
|
||||
STRING FUNCTION TRIM(WS-CURL-CMD)
|
||||
"echo \"$STDOUT\" > '"
|
||||
FUNCTION TRIM(WS-BOOTSTRAP)
|
||||
"' && chmod 755 '"
|
||||
FUNCTION TRIM(WS-BOOTSTRAP)
|
||||
"' && echo 'Bootstrap saved to "
|
||||
FUNCTION TRIM(WS-BOOTSTRAP) "'; "
|
||||
DELIMITED BY SIZE INTO WS-CURL-CMD
|
||||
END-STRING
|
||||
ELSE
|
||||
STRING FUNCTION TRIM(WS-CURL-CMD)
|
||||
"echo \"$STDOUT\"; "
|
||||
DELIMITED BY SIZE INTO WS-CURL-CMD
|
||||
END-STRING
|
||||
END-IF.
|
||||
|
||||
STRING FUNCTION TRIM(WS-CURL-CMD)
|
||||
"else echo -e '\x1b[31mError: Failed to fetch "
|
||||
"bootstrap\x1b[0m' >&2; exit 1; fi"
|
||||
DELIMITED BY SIZE INTO WS-CURL-CMD
|
||||
END-STRING.
|
||||
|
||||
CALL "SYSTEM" USING WS-CURL-CMD.
|
||||
|
||||
PARSE-SERVICE-CREATE-ARGS.
|
||||
* Parse remaining arguments for service creation
|
||||
* This is a simplified parser that looks for specific flags
|
||||
|
|
|
|||
101
un.cpp
101
un.cpp
|
|
@ -218,7 +218,7 @@ void cmd_session(bool list, const string& kill, const string& shell, const strin
|
|||
cout << exec_curl(cmd) << endl;
|
||||
}
|
||||
|
||||
void cmd_service(const string& name, const string& ports, const string& type, const string& bootstrap, bool list, const string& info, const string& logs, const string& tail, const string& sleep, const string& wake, const string& destroy, const string& network, int vcpu, const string& api_key) {
|
||||
void cmd_service(const string& name, const string& ports, const string& type, const string& bootstrap, bool list, const string& info, const string& logs, const string& tail, const string& sleep, const string& wake, const string& destroy, const string& execute, const string& command, const string& dump_bootstrap, const string& dump_file, const string& network, int vcpu, const string& api_key) {
|
||||
if (list) {
|
||||
string cmd = "curl -s -X GET '" + API_BASE + "/services' -H 'Authorization: Bearer " + api_key + "'";
|
||||
cout << exec_curl(cmd) << endl;
|
||||
|
|
@ -264,6 +264,93 @@ void cmd_service(const string& name, const string& ports, const string& type, co
|
|||
return;
|
||||
}
|
||||
|
||||
if (!execute.empty()) {
|
||||
ostringstream json;
|
||||
json << "{\"command\":\"" << escape_json(command) << "\"}";
|
||||
string cmd = "curl -s -X POST '" + API_BASE + "/services/" + execute + "/execute' "
|
||||
"-H 'Content-Type: application/json' "
|
||||
"-H 'Authorization: Bearer " + api_key + "' "
|
||||
"-d '" + json.str() + "'";
|
||||
string result = exec_curl(cmd);
|
||||
|
||||
size_t stdout_pos = result.find("\"stdout\":\"");
|
||||
size_t stderr_pos = result.find("\"stderr\":\"");
|
||||
|
||||
if (stdout_pos != string::npos) {
|
||||
stdout_pos += 10;
|
||||
size_t end = result.find("\"", stdout_pos);
|
||||
while (end > 0 && result[end-1] == '\\') end = result.find("\"", end+1);
|
||||
if (end != string::npos) {
|
||||
string out = result.substr(stdout_pos, end - stdout_pos);
|
||||
size_t pos = 0;
|
||||
while ((pos = out.find("\\n", pos)) != string::npos) {
|
||||
out.replace(pos, 2, "\n");
|
||||
}
|
||||
cout << BLUE << out << RESET;
|
||||
}
|
||||
}
|
||||
|
||||
if (stderr_pos != string::npos) {
|
||||
stderr_pos += 10;
|
||||
size_t end = result.find("\"", stderr_pos);
|
||||
while (end > 0 && result[end-1] == '\\') end = result.find("\"", end+1);
|
||||
if (end != string::npos) {
|
||||
string err = result.substr(stderr_pos, end - stderr_pos);
|
||||
size_t pos = 0;
|
||||
while ((pos = err.find("\\n", pos)) != string::npos) {
|
||||
err.replace(pos, 2, "\n");
|
||||
}
|
||||
cerr << RED << err << RESET;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dump_bootstrap.empty()) {
|
||||
cerr << "Fetching bootstrap script from " << dump_bootstrap << "..." << endl;
|
||||
string cmd = "curl -s -X POST '" + API_BASE + "/services/" + dump_bootstrap + "/execute' "
|
||||
"-H 'Content-Type: application/json' "
|
||||
"-H 'Authorization: Bearer " + api_key + "' "
|
||||
"-d '{\"command\":\"cat /tmp/bootstrap.sh\"}'";
|
||||
string result = exec_curl(cmd);
|
||||
|
||||
size_t stdout_pos = result.find("\"stdout\":\"");
|
||||
if (stdout_pos != string::npos) {
|
||||
stdout_pos += 10;
|
||||
size_t end = result.find("\"", stdout_pos);
|
||||
while (end > 0 && result[end-1] == '\\') end = result.find("\"", end+1);
|
||||
if (end != string::npos) {
|
||||
string bootstrap_script = result.substr(stdout_pos, end - stdout_pos);
|
||||
size_t pos = 0;
|
||||
while ((pos = bootstrap_script.find("\\n", pos)) != string::npos) {
|
||||
bootstrap_script.replace(pos, 2, "\n");
|
||||
}
|
||||
|
||||
if (!dump_file.empty()) {
|
||||
ofstream outfile(dump_file);
|
||||
if (outfile) {
|
||||
outfile << bootstrap_script;
|
||||
outfile.close();
|
||||
chmod(dump_file.c_str(), 0755);
|
||||
cout << "Bootstrap saved to " << dump_file << endl;
|
||||
} else {
|
||||
cerr << RED << "Error: Could not write to " << dump_file << RESET << endl;
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
cout << bootstrap_script;
|
||||
}
|
||||
} else {
|
||||
cerr << RED << "Error: Failed to fetch bootstrap (service not running or no bootstrap file)" << RESET << endl;
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
cerr << RED << "Error: Failed to fetch bootstrap (service not running or no bootstrap file)" << RESET << endl;
|
||||
exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!name.empty()) {
|
||||
ostringstream json;
|
||||
json << "{\"name\":\"" << name << "\"";
|
||||
|
|
@ -414,7 +501,7 @@ int main(int argc, char* argv[]) {
|
|||
if (cmd_type == "service") {
|
||||
string name, ports, type, bootstrap;
|
||||
bool list = false;
|
||||
string info, logs, tail, sleep, wake, destroy, network;
|
||||
string info, logs, tail, sleep, wake, destroy, execute, command, dump_bootstrap, dump_file, network;
|
||||
int vcpu = 0;
|
||||
|
||||
for (int i = 2; i < argc; i++) {
|
||||
|
|
@ -427,15 +514,19 @@ int main(int argc, char* argv[]) {
|
|||
else if (arg == "--info" && i+1 < argc) info = argv[++i];
|
||||
else if (arg == "--logs" && i+1 < argc) logs = argv[++i];
|
||||
else if (arg == "--tail" && i+1 < argc) tail = argv[++i];
|
||||
else if (arg == "--sleep" && i+1 < argc) sleep = argv[++i];
|
||||
else if (arg == "--wake" && i+1 < argc) wake = argv[++i];
|
||||
else if (arg == "--freeze" && i+1 < argc) sleep = argv[++i];
|
||||
else if (arg == "--unfreeze" && i+1 < argc) wake = argv[++i];
|
||||
else if (arg == "--destroy" && i+1 < argc) destroy = argv[++i];
|
||||
else if (arg == "--execute" && i+1 < argc) execute = argv[++i];
|
||||
else if (arg == "--command" && i+1 < argc) command = argv[++i];
|
||||
else if (arg == "--dump-bootstrap" && i+1 < argc) dump_bootstrap = argv[++i];
|
||||
else if (arg == "--dump-file" && i+1 < argc) dump_file = argv[++i];
|
||||
else if (arg == "-n" && i+1 < argc) network = argv[++i];
|
||||
else if (arg == "-v" && i+1 < argc) vcpu = stoi(argv[++i]);
|
||||
else if (arg == "-k" && i+1 < argc) api_key = argv[++i];
|
||||
}
|
||||
|
||||
cmd_service(name, ports, type, bootstrap, list, info, logs, tail, sleep, wake, destroy, network, vcpu, api_key);
|
||||
cmd_service(name, ports, type, bootstrap, list, info, logs, tail, sleep, wake, destroy, execute, command, dump_bootstrap, dump_file, network, vcpu, api_key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
46
un.cr
46
un.cr
|
|
@ -363,6 +363,39 @@ def cmd_service(args)
|
|||
return
|
||||
end
|
||||
|
||||
if execute_id = args[:execute]?.as?(String)
|
||||
command = args[:command]?.as?(String) || ""
|
||||
payload = JSON.parse({command: command}.to_json)
|
||||
result = api_request("/services/#{execute_id}/execute", api_key, method: "POST", data: payload)
|
||||
if stdout = result["stdout"]?.try(&.as_s?)
|
||||
print BLUE, stdout, RESET
|
||||
end
|
||||
if stderr = result["stderr"]?.try(&.as_s?)
|
||||
print RED, stderr, RESET
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if dump_id = args[:dump_bootstrap]?.as?(String)
|
||||
STDERR.puts "Fetching bootstrap script from #{dump_id}..."
|
||||
payload = JSON.parse({command: "cat /tmp/bootstrap.sh"}.to_json)
|
||||
result = api_request("/services/#{dump_id}/execute", api_key, method: "POST", data: payload)
|
||||
|
||||
if bootstrap = result["stdout"]?.try(&.as_s?)
|
||||
if file_path = args[:dump_file]?.as?(String)
|
||||
File.write(file_path, bootstrap)
|
||||
File.chmod(file_path, 0o755)
|
||||
puts "Bootstrap saved to #{file_path}"
|
||||
else
|
||||
print bootstrap
|
||||
end
|
||||
else
|
||||
STDERR.puts "#{RED}Error: Failed to fetch bootstrap (service not running or no bootstrap file)#{RESET}"
|
||||
exit 1
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
# Create new service
|
||||
if name = args[:name]?.as?(String)
|
||||
payload = JSON.parse({name: name}.to_json)
|
||||
|
|
@ -409,7 +442,7 @@ def cmd_service(args)
|
|||
return
|
||||
end
|
||||
|
||||
STDERR.puts "#{RED}Error: Use --list, --info, --logs, --sleep, --wake, --destroy, or --name to create#{RESET}"
|
||||
STDERR.puts "#{RED}Error: Use --list, --info, --logs, --freeze, --unfreeze, --destroy, or --name to create#{RESET}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
|
|
@ -430,6 +463,9 @@ def main
|
|||
sleep: nil,
|
||||
wake: nil,
|
||||
destroy: nil,
|
||||
execute: nil,
|
||||
dump_bootstrap: nil,
|
||||
dump_file: nil,
|
||||
name: nil,
|
||||
ports: nil,
|
||||
domains: nil,
|
||||
|
|
@ -451,9 +487,13 @@ def main
|
|||
opts.on("--kill=ID", "Kill session") { |id| args[:kill] = id }
|
||||
opts.on("--info=ID", "Get service info") { |id| args[:info] = id }
|
||||
opts.on("--logs=ID", "Get service logs") { |id| args[:logs] = id }
|
||||
opts.on("--sleep=ID", "Sleep service") { |id| args[:sleep] = id }
|
||||
opts.on("--wake=ID", "Wake service") { |id| args[:wake] = id }
|
||||
opts.on("--freeze=ID", "Sleep service") { |id| args[:sleep] = id }
|
||||
opts.on("--unfreeze=ID", "Wake service") { |id| args[:wake] = id }
|
||||
opts.on("--destroy=ID", "Destroy service") { |id| args[:destroy] = id }
|
||||
opts.on("--execute=ID", "Execute command in service") { |id| args[:execute] = id }
|
||||
opts.on("--command=CMD", "Command to execute (with --execute)") { |cmd| args[:command] = cmd }
|
||||
opts.on("--dump-bootstrap=ID", "Dump bootstrap script") { |id| args[:dump_bootstrap] = id }
|
||||
opts.on("--dump-file=FILE", "File to save bootstrap (with --dump-bootstrap)") { |file| args[:dump_file] = file }
|
||||
opts.on("--name=NAME", "Service name") { |n| args[:name] = n }
|
||||
opts.on("--ports=PORTS", "Comma-separated ports") { |p| args[:ports] = p }
|
||||
opts.on("--domains=DOMAINS", "Comma-separated domains") { |d| args[:domains] = d }
|
||||
|
|
|
|||
82
un.d
82
un.d
|
|
@ -152,7 +152,7 @@ void cmdSession(bool list, string kill, string shell, string network, int vcpu,
|
|||
writeln(execCurl(cmd));
|
||||
}
|
||||
|
||||
void cmdService(string name, string ports, string bootstrap, string type, bool list, string info, string logs, string tail, string sleep, string wake, string destroy, string network, int vcpu, string apiKey) {
|
||||
void cmdService(string name, string ports, string bootstrap, string type, bool list, string info, string logs, string tail, string sleep, string wake, string destroy, string execute, string command, string dumpBootstrap, string dumpFile, string network, int vcpu, string apiKey) {
|
||||
if (list) {
|
||||
string cmd = format(`curl -s -X GET '%s/services' -H 'Authorization: Bearer %s'`, API_BASE, apiKey);
|
||||
writeln(execCurl(cmd));
|
||||
|
|
@ -198,6 +198,74 @@ void cmdService(string name, string ports, string bootstrap, string type, bool l
|
|||
return;
|
||||
}
|
||||
|
||||
if (!execute.empty) {
|
||||
string json = format(`{"command":"%s"}`, escapeJson(command));
|
||||
string cmd = format(`curl -s -X POST '%s/services/%s/execute' -H 'Content-Type: application/json' -H 'Authorization: Bearer %s' -d '%s'`, API_BASE, execute, apiKey, json);
|
||||
string result = execCurl(cmd);
|
||||
|
||||
// Simple JSON parsing for stdout/stderr
|
||||
import std.algorithm : findSplitAfter;
|
||||
auto stdoutSearch = result.findSplitAfter(`"stdout":"`);
|
||||
if (stdoutSearch[0].length > 0 && stdoutSearch[1].length > 0) {
|
||||
auto stdoutEnd = stdoutSearch[1].findSplitAfter(`"`);
|
||||
if (stdoutEnd[0].length > 1) {
|
||||
string output = stdoutEnd[0][0..$-1];
|
||||
output = output.replace("\\n", "\n").replace("\\r", "\r").replace("\\t", "\t").replace("\\\"", "\"").replace("\\\\", "\\");
|
||||
write(output);
|
||||
}
|
||||
}
|
||||
|
||||
auto stderrSearch = result.findSplitAfter(`"stderr":"`);
|
||||
if (stderrSearch[0].length > 0 && stderrSearch[1].length > 0) {
|
||||
auto stderrEnd = stderrSearch[1].findSplitAfter(`"`);
|
||||
if (stderrEnd[0].length > 1) {
|
||||
string errout = stderrEnd[0][0..$-1];
|
||||
errout = errout.replace("\\n", "\n").replace("\\r", "\r").replace("\\t", "\t").replace("\\\"", "\"").replace("\\\\", "\\");
|
||||
stderr.write(errout);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dumpBootstrap.empty) {
|
||||
stderr.writefln("Fetching bootstrap script from %s...", dumpBootstrap);
|
||||
string cmd = format(`curl -s -X POST '%s/services/%s/execute' -H 'Content-Type: application/json' -H 'Authorization: Bearer %s' -d '{"command":"cat /tmp/bootstrap.sh"}'`, API_BASE, dumpBootstrap, apiKey);
|
||||
string result = execCurl(cmd);
|
||||
|
||||
import std.algorithm : findSplitAfter;
|
||||
auto stdoutSearch = result.findSplitAfter(`"stdout":"`);
|
||||
if (stdoutSearch[0].length > 0 && stdoutSearch[1].length > 0) {
|
||||
auto stdoutEnd = stdoutSearch[1].findSplitAfter(`"`);
|
||||
if (stdoutEnd[0].length > 1) {
|
||||
string bootstrapScript = stdoutEnd[0][0..$-1];
|
||||
bootstrapScript = bootstrapScript.replace("\\n", "\n").replace("\\r", "\r").replace("\\t", "\t").replace("\\\"", "\"").replace("\\\\", "\\");
|
||||
|
||||
if (!dumpFile.empty) {
|
||||
try {
|
||||
std.file.write(dumpFile, bootstrapScript);
|
||||
version(Posix) {
|
||||
import core.sys.posix.sys.stat;
|
||||
chmod(dumpFile.toStringz(), octal!755);
|
||||
}
|
||||
writefln("Bootstrap saved to %s", dumpFile);
|
||||
} catch (Exception e) {
|
||||
stderr.writefln("%sError: Could not write to %s: %s%s", RED, dumpFile, e.msg, RESET);
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
write(bootstrapScript);
|
||||
}
|
||||
} else {
|
||||
stderr.writefln("%sError: Failed to fetch bootstrap (service not running or no bootstrap file)%s", RED, RESET);
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
stderr.writefln("%sError: Failed to fetch bootstrap (service not running or no bootstrap file)%s", RED, RESET);
|
||||
exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!name.empty) {
|
||||
string json = format(`{"name":"%s"`, name);
|
||||
if (!ports.empty) json ~= format(`,"ports":[%s]`, ports);
|
||||
|
|
@ -379,7 +447,7 @@ int main(string[] args) {
|
|||
if (args[1] == "service") {
|
||||
string name, ports, bootstrap, type;
|
||||
bool list = false;
|
||||
string info, logs, tail, sleep, wake, destroy, network;
|
||||
string info, logs, tail, sleep, wake, destroy, execute, command, dumpBootstrap, dumpFile, network;
|
||||
int vcpu = 0;
|
||||
|
||||
for (size_t i = 2; i < args.length; i++) {
|
||||
|
|
@ -391,15 +459,19 @@ int main(string[] args) {
|
|||
else if (args[i] == "--info" && i+1 < args.length) info = args[++i];
|
||||
else if (args[i] == "--logs" && i+1 < args.length) logs = args[++i];
|
||||
else if (args[i] == "--tail" && i+1 < args.length) tail = args[++i];
|
||||
else if (args[i] == "--sleep" && i+1 < args.length) sleep = args[++i];
|
||||
else if (args[i] == "--wake" && i+1 < args.length) wake = args[++i];
|
||||
else if (args[i] == "--freeze" && i+1 < args.length) sleep = args[++i];
|
||||
else if (args[i] == "--unfreeze" && i+1 < args.length) wake = args[++i];
|
||||
else if (args[i] == "--destroy" && i+1 < args.length) destroy = args[++i];
|
||||
else if (args[i] == "--execute" && i+1 < args.length) execute = args[++i];
|
||||
else if (args[i] == "--command" && i+1 < args.length) command = args[++i];
|
||||
else if (args[i] == "--dump-bootstrap" && i+1 < args.length) dumpBootstrap = args[++i];
|
||||
else if (args[i] == "--dump-file" && i+1 < args.length) dumpFile = args[++i];
|
||||
else if (args[i] == "-n" && i+1 < args.length) network = args[++i];
|
||||
else if (args[i] == "-v" && i+1 < args.length) vcpu = to!int(args[++i]);
|
||||
else if (args[i] == "-k" && i+1 < args.length) apiKey = args[++i];
|
||||
}
|
||||
|
||||
cmdService(name, ports, bootstrap, type, list, info, logs, tail, sleep, wake, destroy, network, vcpu, apiKey);
|
||||
cmdService(name, ports, bootstrap, type, list, info, logs, tail, sleep, wake, destroy, execute, command, dumpBootstrap, dumpFile, network, vcpu, apiKey);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
72
un.dart
72
un.dart
|
|
@ -91,6 +91,10 @@ class Args {
|
|||
String? serviceSleep;
|
||||
String? serviceWake;
|
||||
String? serviceDestroy;
|
||||
String? serviceExecute;
|
||||
String? serviceCommand;
|
||||
String? serviceDumpBootstrap;
|
||||
String? serviceDumpFile;
|
||||
bool keyExtend = false;
|
||||
}
|
||||
|
||||
|
|
@ -318,6 +322,50 @@ Future<void> cmdService(Args args) async {
|
|||
return;
|
||||
}
|
||||
|
||||
if (args.serviceExecute != null) {
|
||||
final payload = <String, dynamic>{
|
||||
'command': args.serviceCommand,
|
||||
};
|
||||
final result = await apiRequestCurl('/services/${args.serviceExecute}/execute', 'POST', jsonEncode(payload), apiKey);
|
||||
final stdoutText = result['stdout'] as String?;
|
||||
final stderrText = result['stderr'] as String?;
|
||||
if (stdoutText != null && stdoutText.isNotEmpty) {
|
||||
stdout.write('$blue$stdoutText$reset');
|
||||
}
|
||||
if (stderrText != null && stderrText.isNotEmpty) {
|
||||
stderr.write('$red$stderrText$reset');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.serviceDumpBootstrap != null) {
|
||||
stderr.writeln('Fetching bootstrap script from ${args.serviceDumpBootstrap}...');
|
||||
final payload = <String, dynamic>{
|
||||
'command': 'cat /tmp/bootstrap.sh',
|
||||
};
|
||||
final result = await apiRequestCurl('/services/${args.serviceDumpBootstrap}/execute', 'POST', jsonEncode(payload), apiKey);
|
||||
|
||||
final bootstrap = result['stdout'] as String?;
|
||||
if (bootstrap != null && bootstrap.isNotEmpty) {
|
||||
if (args.serviceDumpFile != null) {
|
||||
try {
|
||||
await File(args.serviceDumpFile!).writeAsString(bootstrap);
|
||||
await Process.run('chmod', ['755', args.serviceDumpFile!]);
|
||||
print('Bootstrap saved to ${args.serviceDumpFile}');
|
||||
} catch (e) {
|
||||
stderr.writeln('${red}Error: Could not write to ${args.serviceDumpFile}: $e$reset');
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
stdout.write(bootstrap);
|
||||
}
|
||||
} else {
|
||||
stderr.writeln('${red}Error: Failed to fetch bootstrap (service not running or no bootstrap file)$reset');
|
||||
exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.serviceName != null) {
|
||||
final payload = <String, dynamic>{
|
||||
'name': args.serviceName!,
|
||||
|
|
@ -485,15 +533,27 @@ Args parseArgs(List<String> argv) {
|
|||
case '--tail':
|
||||
args.serviceTail = argv[++i];
|
||||
break;
|
||||
case '--sleep':
|
||||
case '--freeze':
|
||||
args.serviceSleep = argv[++i];
|
||||
break;
|
||||
case '--wake':
|
||||
case '--unfreeze':
|
||||
args.serviceWake = argv[++i];
|
||||
break;
|
||||
case '--destroy':
|
||||
args.serviceDestroy = argv[++i];
|
||||
break;
|
||||
case '--execute':
|
||||
args.serviceExecute = argv[++i];
|
||||
break;
|
||||
case '--command':
|
||||
args.serviceCommand = argv[++i];
|
||||
break;
|
||||
case '--dump-bootstrap':
|
||||
args.serviceDumpBootstrap = argv[++i];
|
||||
break;
|
||||
case '--dump-file':
|
||||
args.serviceDumpFile = argv[++i];
|
||||
break;
|
||||
case '--extend':
|
||||
args.keyExtend = true;
|
||||
break;
|
||||
|
|
@ -537,9 +597,13 @@ Service options:
|
|||
--info ID Get service details
|
||||
--logs ID Get all logs
|
||||
--tail ID Get last 9000 lines
|
||||
--sleep ID Freeze service
|
||||
--wake ID Unfreeze service
|
||||
--freeze ID Freeze service
|
||||
--unfreeze ID Unfreeze service
|
||||
--destroy ID Destroy service
|
||||
--execute ID Execute command in service
|
||||
--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:
|
||||
--extend Open browser to extend key
|
||||
|
|
|
|||
47
un.erl
47
un.erl
|
|
@ -128,14 +128,14 @@ service_command(["--logs", ServiceId | _]) ->
|
|||
Response = curl_get(ApiKey, "/services/" ++ ServiceId ++ "/logs"),
|
||||
io:format("~s~n", [Response]);
|
||||
|
||||
service_command(["--sleep", ServiceId | _]) ->
|
||||
service_command(["--freeze", ServiceId | _]) ->
|
||||
ApiKey = get_api_key(),
|
||||
TmpFile = write_temp_file("{}"),
|
||||
_ = curl_post(ApiKey, "/services/" ++ ServiceId ++ "/sleep", TmpFile),
|
||||
file:delete(TmpFile),
|
||||
io:format("\033[32mService sleeping: ~s\033[0m~n", [ServiceId]);
|
||||
|
||||
service_command(["--wake", ServiceId | _]) ->
|
||||
service_command(["--unfreeze", ServiceId | _]) ->
|
||||
ApiKey = get_api_key(),
|
||||
TmpFile = write_temp_file("{}"),
|
||||
_ = curl_post(ApiKey, "/services/" ++ ServiceId ++ "/wake", TmpFile),
|
||||
|
|
@ -147,6 +147,49 @@ service_command(["--destroy", ServiceId | _]) ->
|
|||
_ = curl_delete(ApiKey, "/services/" ++ ServiceId),
|
||||
io:format("\033[32mService destroyed: ~s\033[0m~n", [ServiceId]);
|
||||
|
||||
service_command(["--execute", ServiceId, "--command", Command | _]) ->
|
||||
ApiKey = get_api_key(),
|
||||
Json = "{\"command\":\"" ++ escape_json(Command) ++ "\"}",
|
||||
TmpFile = write_temp_file(Json),
|
||||
Response = curl_post(ApiKey, "/services/" ++ ServiceId ++ "/execute", TmpFile),
|
||||
file:delete(TmpFile),
|
||||
case extract_json_field(Response, "stdout") of
|
||||
"" -> ok;
|
||||
Stdout -> io:format("\033[34m~s\033[0m", [Stdout])
|
||||
end;
|
||||
|
||||
service_command(["--dump-bootstrap", ServiceId, File | _]) ->
|
||||
ApiKey = get_api_key(),
|
||||
io:format(standard_error, "Fetching bootstrap script from ~s...~n", [ServiceId]),
|
||||
Json = "{\"command\":\"cat /tmp/bootstrap.sh\"}",
|
||||
TmpFile = write_temp_file(Json),
|
||||
Response = curl_post(ApiKey, "/services/" ++ ServiceId ++ "/execute", TmpFile),
|
||||
file:delete(TmpFile),
|
||||
case extract_json_field(Response, "stdout") of
|
||||
"" ->
|
||||
io:format(standard_error, "\033[31mError: Failed to fetch bootstrap (service not running or no bootstrap file)\033[0m~n"),
|
||||
halt(1);
|
||||
Script ->
|
||||
file:write_file(File, Script),
|
||||
os:cmd("chmod 755 " ++ File),
|
||||
io:format("Bootstrap saved to ~s~n", [File])
|
||||
end;
|
||||
|
||||
service_command(["--dump-bootstrap", ServiceId | _]) ->
|
||||
ApiKey = get_api_key(),
|
||||
io:format(standard_error, "Fetching bootstrap script from ~s...~n", [ServiceId]),
|
||||
Json = "{\"command\":\"cat /tmp/bootstrap.sh\"}",
|
||||
TmpFile = write_temp_file(Json),
|
||||
Response = curl_post(ApiKey, "/services/" ++ ServiceId ++ "/execute", TmpFile),
|
||||
file:delete(TmpFile),
|
||||
case extract_json_field(Response, "stdout") of
|
||||
"" ->
|
||||
io:format(standard_error, "\033[31mError: Failed to fetch bootstrap (service not running or no bootstrap file)\033[0m~n"),
|
||||
halt(1);
|
||||
Script ->
|
||||
io:format("~s", [Script])
|
||||
end;
|
||||
|
||||
service_command(Args) ->
|
||||
case get_service_name(Args) of
|
||||
undefined ->
|
||||
|
|
|
|||
47
un.ex
47
un.ex
|
|
@ -166,13 +166,13 @@ defmodule Un do
|
|||
IO.puts(response)
|
||||
end
|
||||
|
||||
defp service_command(["--sleep", service_id | _]) do
|
||||
defp service_command(["--freeze", service_id | _]) do
|
||||
api_key = get_api_key()
|
||||
curl_post(api_key, "/services/#{service_id}/sleep", "{}")
|
||||
IO.puts("#{@green}Service sleeping: #{service_id}#{@reset}")
|
||||
end
|
||||
|
||||
defp service_command(["--wake", service_id | _]) do
|
||||
defp service_command(["--unfreeze", service_id | _]) do
|
||||
api_key = get_api_key()
|
||||
curl_post(api_key, "/services/#{service_id}/wake", "{}")
|
||||
IO.puts("#{@green}Service waking: #{service_id}#{@reset}")
|
||||
|
|
@ -184,6 +184,49 @@ defmodule Un do
|
|||
IO.puts("#{@green}Service destroyed: #{service_id}#{@reset}")
|
||||
end
|
||||
|
||||
defp service_command(["--execute", service_id, "--command", command | _]) do
|
||||
api_key = get_api_key()
|
||||
json = "{\"command\":\"#{escape_json(command)}\"}"
|
||||
response = curl_post(api_key, "/services/#{service_id}/execute", json)
|
||||
|
||||
case extract_json_value(response, "stdout") do
|
||||
nil -> :ok
|
||||
stdout -> IO.write("#{@blue}#{stdout}#{@reset}")
|
||||
end
|
||||
end
|
||||
|
||||
defp service_command(["--dump-bootstrap", service_id, file | _]) do
|
||||
api_key = get_api_key()
|
||||
IO.puts(:stderr, "Fetching bootstrap script from #{service_id}...")
|
||||
json = "{\"command\":\"cat /tmp/bootstrap.sh\"}"
|
||||
response = curl_post(api_key, "/services/#{service_id}/execute", json)
|
||||
|
||||
case extract_json_value(response, "stdout") do
|
||||
nil ->
|
||||
IO.puts(:stderr, "#{@red}Error: Failed to fetch bootstrap (service not running or no bootstrap file)#{@reset}")
|
||||
System.halt(1)
|
||||
script ->
|
||||
File.write!(file, script)
|
||||
System.cmd("chmod", ["755", file])
|
||||
IO.puts("Bootstrap saved to #{file}")
|
||||
end
|
||||
end
|
||||
|
||||
defp service_command(["--dump-bootstrap", service_id | _]) do
|
||||
api_key = get_api_key()
|
||||
IO.puts(:stderr, "Fetching bootstrap script from #{service_id}...")
|
||||
json = "{\"command\":\"cat /tmp/bootstrap.sh\"}"
|
||||
response = curl_post(api_key, "/services/#{service_id}/execute", json)
|
||||
|
||||
case extract_json_value(response, "stdout") do
|
||||
nil ->
|
||||
IO.puts(:stderr, "#{@red}Error: Failed to fetch bootstrap (service not running or no bootstrap file)#{@reset}")
|
||||
System.halt(1)
|
||||
script ->
|
||||
IO.write(script)
|
||||
end
|
||||
end
|
||||
|
||||
defp service_command(args) do
|
||||
name = get_opt(args, "--name", nil, nil)
|
||||
|
||||
|
|
|
|||
32
un.f90
32
un.f90
|
|
@ -248,12 +248,12 @@ contains
|
|||
if (i+1 <= command_argument_count()) then
|
||||
call get_command_argument(i+1, service_id)
|
||||
end if
|
||||
else if (trim(arg) == '--sleep') then
|
||||
else if (trim(arg) == '--freeze') then
|
||||
operation = 'sleep'
|
||||
if (i+1 <= command_argument_count()) then
|
||||
call get_command_argument(i+1, service_id)
|
||||
end if
|
||||
else if (trim(arg) == '--wake') then
|
||||
else if (trim(arg) == '--unfreeze') then
|
||||
operation = 'wake'
|
||||
if (i+1 <= command_argument_count()) then
|
||||
call get_command_argument(i+1, service_id)
|
||||
|
|
@ -263,6 +263,16 @@ contains
|
|||
if (i+1 <= command_argument_count()) then
|
||||
call get_command_argument(i+1, service_id)
|
||||
end if
|
||||
else if (trim(arg) == '--dump-bootstrap') then
|
||||
operation = 'dump-bootstrap'
|
||||
if (i+1 <= command_argument_count()) then
|
||||
call get_command_argument(i+1, service_id)
|
||||
end if
|
||||
else if (trim(arg) == '--dump-file') then
|
||||
operation = 'dump-file'
|
||||
if (i+1 <= command_argument_count()) then
|
||||
call get_command_argument(i+1, service_type)
|
||||
end if
|
||||
end if
|
||||
end do
|
||||
|
||||
|
|
@ -314,8 +324,24 @@ contains
|
|||
'-H "Authorization: Bearer ', trim(api_key), '" >/dev/null && ', &
|
||||
'echo -e "\x1b[32mService destroyed: ', trim(service_id), '\x1b[0m"'
|
||||
call execute_command_line(trim(full_cmd), wait=.true.)
|
||||
else if (trim(operation) == 'dump-bootstrap' .and. len_trim(service_id) > 0) then
|
||||
write(full_cmd, '(20A)') &
|
||||
'echo "Fetching bootstrap script from ', trim(service_id), '..." >&2; ', &
|
||||
'RESP=$(curl -s -X POST https://api.unsandbox.com/services/', &
|
||||
trim(service_id), '/execute ', &
|
||||
'-H "Content-Type: application/json" ', &
|
||||
'-H "Authorization: Bearer ', trim(api_key), '" ', &
|
||||
'-d ''{"command":"cat /tmp/bootstrap.sh"}''); ', &
|
||||
'STDOUT=$(echo "$RESP" | jq -r ".stdout // empty"); ', &
|
||||
'if [ -n "$STDOUT" ]; then ', &
|
||||
'if [ -n "', trim(service_type), '" ]; then ', &
|
||||
'echo "$STDOUT" > "', trim(service_type), '" && chmod 755 "', trim(service_type), '" && ', &
|
||||
'echo "Bootstrap saved to ', trim(service_type), '"; ', &
|
||||
'else echo "$STDOUT"; fi; ', &
|
||||
'else echo -e "\x1b[31mError: Failed to fetch bootstrap\x1b[0m" >&2; exit 1; fi'
|
||||
call execute_command_line(trim(full_cmd), wait=.true.)
|
||||
else
|
||||
write(0, '(A)') 'Error: Use --list, --info, --logs, --sleep, --wake, or --destroy'
|
||||
write(0, '(A)') 'Error: Use --list, --info, --logs, --freeze, --unfreeze, --destroy, or --dump-bootstrap'
|
||||
stop 1
|
||||
end if
|
||||
end subroutine handle_service
|
||||
|
|
|
|||
75
un.forth
75
un.forth
|
|
@ -258,6 +258,43 @@
|
|||
s" chmod +x /tmp/unsandbox_cmd.sh && /tmp/unsandbox_cmd.sh && rm -f /tmp/unsandbox_cmd.sh" system
|
||||
;
|
||||
|
||||
\ Service dump bootstrap
|
||||
: service-dump-bootstrap ( service-id-addr service-id-len file-addr file-len -- )
|
||||
get-api-key
|
||||
s" /tmp/unsandbox_cmd.sh" w/o create-file throw >r
|
||||
s" #!/bin/bash" r@ write-line throw
|
||||
s" echo 'Fetching bootstrap script from " r@ write-file throw
|
||||
2over r@ write-file throw
|
||||
s" ...' >&2" r@ write-line throw
|
||||
s" RESP=$(curl -s -X POST https://api.unsandbox.com/services/" r@ write-file throw
|
||||
2over r@ write-file throw
|
||||
s" /execute -H 'Content-Type: application/json' -H 'Authorization: Bearer " r@ write-file throw
|
||||
get-api-key r@ write-file throw
|
||||
s" ' -d '{\"command\":\"cat /tmp/bootstrap.sh\"}')" r@ write-line throw
|
||||
s" STDOUT=$(echo \"$RESP\" | jq -r '.stdout // empty')" r@ write-line throw
|
||||
s" if [ -n \"$STDOUT\" ]; then" r@ write-line throw
|
||||
2dup 0 0 d= if
|
||||
\ No file specified, print to stdout
|
||||
2drop
|
||||
s" echo \"$STDOUT\"" r@ write-line throw
|
||||
else
|
||||
\ File specified, save to file
|
||||
s" echo \"$STDOUT\" > '" r@ write-file throw
|
||||
r@ write-file throw
|
||||
s" ' && chmod 755 '" r@ write-file throw
|
||||
2dup r@ write-file throw
|
||||
s" ' && echo 'Bootstrap saved to " r@ write-file throw
|
||||
r@ write-file throw
|
||||
s" '" r@ write-line throw
|
||||
then
|
||||
s" else" r@ write-line throw
|
||||
s" echo -e '\\x1b[31mError: Failed to fetch bootstrap\\x1b[0m' >&2" r@ write-line throw
|
||||
s" exit 1" r@ write-line throw
|
||||
s" fi" r@ write-line throw
|
||||
r> close-file throw
|
||||
s" chmod +x /tmp/unsandbox_cmd.sh && /tmp/unsandbox_cmd.sh && rm -f /tmp/unsandbox_cmd.sh" system
|
||||
;
|
||||
|
||||
\ Service create (requires --name, optional --ports, --domains, --type, --bootstrap)
|
||||
: service-create ( -- )
|
||||
get-api-key
|
||||
|
|
@ -394,7 +431,7 @@
|
|||
\ Handle service subcommand
|
||||
: handle-service ( -- )
|
||||
argc @ 3 < if
|
||||
s" Error: Use --name (create), --list, --info, --logs, --sleep, --wake, or --destroy" type cr
|
||||
s" Error: Use --name (create), --list, --info, --logs, --freeze, --unfreeze, or --destroy" type cr
|
||||
1 (bye)
|
||||
then
|
||||
|
||||
|
|
@ -433,20 +470,20 @@
|
|||
0 (bye)
|
||||
then
|
||||
|
||||
2dup s" --sleep" compare 0= if
|
||||
2dup s" --freeze" compare 0= if
|
||||
2drop
|
||||
argc @ 4 < if
|
||||
s" Error: --sleep requires service ID" type cr
|
||||
s" Error: --freeze requires service ID" type cr
|
||||
1 (bye)
|
||||
then
|
||||
3 arg service-sleep
|
||||
0 (bye)
|
||||
then
|
||||
|
||||
2dup s" --wake" compare 0= if
|
||||
2dup s" --unfreeze" compare 0= if
|
||||
2drop
|
||||
argc @ 4 < if
|
||||
s" Error: --wake requires service ID" type cr
|
||||
s" Error: --unfreeze requires service ID" type cr
|
||||
1 (bye)
|
||||
then
|
||||
3 arg service-wake
|
||||
|
|
@ -463,8 +500,34 @@
|
|||
0 (bye)
|
||||
then
|
||||
|
||||
2dup s" --dump-bootstrap" compare 0= if
|
||||
2drop
|
||||
argc @ 4 < if
|
||||
s" Error: --dump-bootstrap requires service ID" type cr
|
||||
1 (bye)
|
||||
then
|
||||
3 arg
|
||||
\ Check for --dump-file
|
||||
argc @ 5 >= if
|
||||
4 arg 2dup s" --dump-file" compare 0= if
|
||||
2drop
|
||||
argc @ 6 < if
|
||||
s" Error: --dump-file requires filename" type cr
|
||||
1 (bye)
|
||||
then
|
||||
5 arg
|
||||
else
|
||||
2drop 0 0
|
||||
then
|
||||
else
|
||||
0 0
|
||||
then
|
||||
service-dump-bootstrap
|
||||
0 (bye)
|
||||
then
|
||||
|
||||
2drop
|
||||
s" Error: Use --name (create), --list, --info, --logs, --sleep, --wake, or --destroy" type cr
|
||||
s" Error: Use --name (create), --list, --info, --logs, --freeze, --unfreeze, --destroy, or --dump-bootstrap" type cr
|
||||
1 (bye)
|
||||
;
|
||||
|
||||
|
|
|
|||
55
un.fs
55
un.fs
|
|
@ -94,6 +94,10 @@ type Args = {
|
|||
mutable ServiceSleep: string option
|
||||
mutable ServiceWake: string option
|
||||
mutable ServiceDestroy: string option
|
||||
mutable ServiceExecute: string option
|
||||
mutable ServiceCommand: string option
|
||||
mutable ServiceDumpBootstrap: string option
|
||||
mutable ServiceDumpFile: string option
|
||||
mutable KeyExtend: bool
|
||||
}
|
||||
|
||||
|
|
@ -436,6 +440,37 @@ let cmdService (args: Args) =
|
|||
elif args.ServiceDestroy.IsSome then
|
||||
let result = apiRequest (sprintf "/services/%s" args.ServiceDestroy.Value) "DELETE" None apiKey
|
||||
printfn "%sService destroyed: %s%s" green args.ServiceDestroy.Value reset
|
||||
elif args.ServiceExecute.IsSome then
|
||||
let payload = [("command", box args.ServiceCommand.Value)]
|
||||
let result = apiRequest (sprintf "/services/%s/execute" args.ServiceExecute.Value) "POST" (Some payload) apiKey
|
||||
match result.TryFind "stdout" with
|
||||
| Some stdout when not (String.IsNullOrEmpty(stdout.ToString())) ->
|
||||
printf "%s%s%s" blue (stdout.ToString()) reset
|
||||
| _ -> ()
|
||||
match result.TryFind "stderr" with
|
||||
| Some stderr when not (String.IsNullOrEmpty(stderr.ToString())) ->
|
||||
eprintf "%s%s%s" red (stderr.ToString()) reset
|
||||
| _ -> ()
|
||||
elif args.ServiceDumpBootstrap.IsSome then
|
||||
eprintfn "Fetching bootstrap script from %s..." args.ServiceDumpBootstrap.Value
|
||||
let payload = [("command", box "cat /tmp/bootstrap.sh")]
|
||||
let result = apiRequest (sprintf "/services/%s/execute" args.ServiceDumpBootstrap.Value) "POST" (Some payload) apiKey
|
||||
|
||||
match result.TryFind "stdout" with
|
||||
| Some bootstrap when not (String.IsNullOrEmpty(bootstrap.ToString())) ->
|
||||
let bootstrapText = bootstrap.ToString()
|
||||
if args.ServiceDumpFile.IsSome then
|
||||
try
|
||||
File.WriteAllText(args.ServiceDumpFile.Value, bootstrapText)
|
||||
printfn "Bootstrap saved to %s" args.ServiceDumpFile.Value
|
||||
with ex ->
|
||||
eprintfn "%sError: Could not write to %s: %s%s" red args.ServiceDumpFile.Value ex.Message reset
|
||||
exit 1
|
||||
else
|
||||
printf "%s" bootstrapText
|
||||
| _ ->
|
||||
eprintfn "%sError: Failed to fetch bootstrap (service not running or no bootstrap file)%s" red reset
|
||||
exit 1
|
||||
elif args.ServiceName.IsSome then
|
||||
let mutable payload = [("name", box args.ServiceName.Value)]
|
||||
if args.ServicePorts.IsSome then
|
||||
|
|
@ -489,6 +524,10 @@ let parseArgs (argv: string[]) =
|
|||
ServiceSleep = None
|
||||
ServiceWake = None
|
||||
ServiceDestroy = None
|
||||
ServiceExecute = None
|
||||
ServiceCommand = None
|
||||
ServiceDumpBootstrap = None
|
||||
ServiceDumpFile = None
|
||||
KeyExtend = false
|
||||
}
|
||||
|
||||
|
|
@ -519,9 +558,13 @@ let parseArgs (argv: string[]) =
|
|||
| "--info" -> i <- i + 1; args.ServiceInfo <- Some argv.[i]
|
||||
| "--logs" -> i <- i + 1; args.ServiceLogs <- Some argv.[i]
|
||||
| "--tail" -> i <- i + 1; args.ServiceTail <- Some argv.[i]
|
||||
| "--sleep" -> i <- i + 1; args.ServiceSleep <- Some argv.[i]
|
||||
| "--wake" -> i <- i + 1; args.ServiceWake <- Some argv.[i]
|
||||
| "--freeze" -> i <- i + 1; args.ServiceSleep <- Some argv.[i]
|
||||
| "--unfreeze" -> i <- i + 1; args.ServiceWake <- Some argv.[i]
|
||||
| "--destroy" -> i <- i + 1; args.ServiceDestroy <- Some argv.[i]
|
||||
| "--execute" -> i <- i + 1; args.ServiceExecute <- Some argv.[i]
|
||||
| "--command" -> i <- i + 1; args.ServiceCommand <- Some argv.[i]
|
||||
| "--dump-bootstrap" -> i <- i + 1; args.ServiceDumpBootstrap <- Some argv.[i]
|
||||
| "--dump-file" -> i <- i + 1; args.ServiceDumpFile <- Some argv.[i]
|
||||
| "--extend" -> args.KeyExtend <- true
|
||||
| arg when not (arg.StartsWith("-")) -> args.SourceFile <- Some arg
|
||||
| _ -> ()
|
||||
|
|
@ -558,9 +601,13 @@ let printHelp () =
|
|||
printfn " --info ID Get service details"
|
||||
printfn " --logs ID Get all logs"
|
||||
printfn " --tail ID Get last 9000 lines"
|
||||
printfn " --sleep ID Freeze service"
|
||||
printfn " --wake ID Unfreeze service"
|
||||
printfn " --freeze ID Freeze service"
|
||||
printfn " --unfreeze ID Unfreeze service"
|
||||
printfn " --destroy ID Destroy service"
|
||||
printfn " --execute ID Execute command in service"
|
||||
printfn " --command CMD Command to execute (with --execute)"
|
||||
printfn " --dump-bootstrap ID Dump bootstrap script"
|
||||
printfn " --dump-file FILE File to save bootstrap (with --dump-bootstrap)"
|
||||
printfn ""
|
||||
printfn "Key options:"
|
||||
printfn " --extend Open browser to extend key"
|
||||
|
|
|
|||
86
un.groovy
86
un.groovy
|
|
@ -85,6 +85,10 @@ class Args {
|
|||
String serviceSleep = null
|
||||
String serviceWake = null
|
||||
String serviceDestroy = null
|
||||
String serviceExecute = null
|
||||
String serviceCommand = null
|
||||
String serviceDumpBootstrap = null
|
||||
String serviceDumpFile = null
|
||||
Boolean keyExtend = false
|
||||
}
|
||||
|
||||
|
|
@ -408,6 +412,64 @@ def cmdService(args) {
|
|||
return
|
||||
}
|
||||
|
||||
if (args.serviceExecute) {
|
||||
def json = """{"command":"${args.serviceCommand}"}"""
|
||||
def output = apiRequest("/services/${args.serviceExecute}/execute", 'POST', json, apiKey)
|
||||
def stdoutMatch = output =~ /"stdout":"((?:[^"\\\\]|\\\\.)*)"/
|
||||
def stderrMatch = output =~ /"stderr":"((?:[^"\\\\]|\\\\.)*)"/
|
||||
|
||||
if (stdoutMatch.find()) {
|
||||
def stdout = stdoutMatch.group(1)
|
||||
.replace('\\n', '\n')
|
||||
.replace('\\t', '\t')
|
||||
.replace('\\"', '"')
|
||||
.replace('\\\\', '\\')
|
||||
print("${BLUE}${stdout}${RESET}")
|
||||
}
|
||||
|
||||
if (stderrMatch.find()) {
|
||||
def stderr = stderrMatch.group(1)
|
||||
.replace('\\n', '\n')
|
||||
.replace('\\t', '\t')
|
||||
.replace('\\"', '"')
|
||||
.replace('\\\\', '\\')
|
||||
System.err.print("${RED}${stderr}${RESET}")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (args.serviceDumpBootstrap) {
|
||||
System.err.println("Fetching bootstrap script from ${args.serviceDumpBootstrap}...")
|
||||
def json = """{"command":"cat /tmp/bootstrap.sh"}"""
|
||||
def output = apiRequest("/services/${args.serviceDumpBootstrap}/execute", 'POST', json, apiKey)
|
||||
|
||||
def stdoutMatch = output =~ /"stdout":"((?:[^"\\\\]|\\\\.)*)"/
|
||||
if (stdoutMatch.find()) {
|
||||
def bootstrap = stdoutMatch.group(1)
|
||||
.replace('\\n', '\n')
|
||||
.replace('\\t', '\t')
|
||||
.replace('\\"', '"')
|
||||
.replace('\\\\', '\\')
|
||||
|
||||
if (args.serviceDumpFile) {
|
||||
try {
|
||||
new File(args.serviceDumpFile).text = bootstrap
|
||||
"chmod 755 ${args.serviceDumpFile}".execute().waitFor()
|
||||
println("Bootstrap saved to ${args.serviceDumpFile}")
|
||||
} catch (Exception e) {
|
||||
System.err.println("${RED}Error: Could not write to ${args.serviceDumpFile}: ${e.message}${RESET}")
|
||||
System.exit(1)
|
||||
}
|
||||
} else {
|
||||
print(bootstrap)
|
||||
}
|
||||
} else {
|
||||
System.err.println("${RED}Error: Failed to fetch bootstrap (service not running or no bootstrap file)${RESET}")
|
||||
System.exit(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (args.serviceName) {
|
||||
def json = """{"name":"${args.serviceName}""""
|
||||
if (args.servicePorts) {
|
||||
|
|
@ -524,15 +586,27 @@ def parseArgs(argv) {
|
|||
case '--tail':
|
||||
args.serviceTail = argv[++i]
|
||||
break
|
||||
case '--sleep':
|
||||
case '--freeze':
|
||||
args.serviceSleep = argv[++i]
|
||||
break
|
||||
case '--wake':
|
||||
case '--unfreeze':
|
||||
args.serviceWake = argv[++i]
|
||||
break
|
||||
case '--destroy':
|
||||
args.serviceDestroy = argv[++i]
|
||||
break
|
||||
case '--execute':
|
||||
args.serviceExecute = argv[++i]
|
||||
break
|
||||
case '--command':
|
||||
args.serviceCommand = argv[++i]
|
||||
break
|
||||
case '--dump-bootstrap':
|
||||
args.serviceDumpBootstrap = argv[++i]
|
||||
break
|
||||
case '--dump-file':
|
||||
args.serviceDumpFile = argv[++i]
|
||||
break
|
||||
case '--extend':
|
||||
args.keyExtend = true
|
||||
break
|
||||
|
|
@ -575,9 +649,13 @@ Service options:
|
|||
--info ID Get service details
|
||||
--logs ID Get all logs
|
||||
--tail ID Get last 9000 lines
|
||||
--sleep ID Freeze service
|
||||
--wake ID Unfreeze service
|
||||
--freeze ID Freeze service
|
||||
--unfreeze ID Unfreeze service
|
||||
--destroy ID Destroy service
|
||||
--execute ID Execute command in service
|
||||
--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:
|
||||
--extend Open browser to extend key
|
||||
|
|
|
|||
30
un.hs
30
un.hs
|
|
@ -145,6 +145,7 @@ data ServiceOpts = ServiceOpts
|
|||
|
||||
data ServiceAction = ServiceList | ServiceInfo String | ServiceLogs String
|
||||
| ServiceSleep String | ServiceWake String | ServiceDestroy String
|
||||
| ServiceExecute String String | ServiceDumpBootstrap String (Maybe String)
|
||||
| ServiceCreate
|
||||
|
||||
data KeyOpts = KeyOpts
|
||||
|
|
@ -187,9 +188,12 @@ parseService args = return $ parseServiceArgs args defaultServiceOpts
|
|||
parseServiceArgs ("--list":rest) opts = parseServiceArgs rest opts { svcAction = ServiceList }
|
||||
parseServiceArgs ("--info":id:rest) opts = parseServiceArgs rest opts { svcAction = ServiceInfo id }
|
||||
parseServiceArgs ("--logs":id:rest) opts = parseServiceArgs rest opts { svcAction = ServiceLogs id }
|
||||
parseServiceArgs ("--sleep":id:rest) opts = parseServiceArgs rest opts { svcAction = ServiceSleep id }
|
||||
parseServiceArgs ("--wake":id:rest) opts = parseServiceArgs rest opts { svcAction = ServiceWake id }
|
||||
parseServiceArgs ("--freeze":id:rest) opts = parseServiceArgs rest opts { svcAction = ServiceSleep id }
|
||||
parseServiceArgs ("--unfreeze":id:rest) opts = parseServiceArgs rest opts { svcAction = ServiceWake id }
|
||||
parseServiceArgs ("--destroy":id:rest) opts = parseServiceArgs rest opts { svcAction = ServiceDestroy id }
|
||||
parseServiceArgs ("--execute":id:"--command":cmd:rest) opts = parseServiceArgs rest opts { svcAction = ServiceExecute id cmd }
|
||||
parseServiceArgs ("--dump-bootstrap":id:file:rest) opts = parseServiceArgs rest opts { svcAction = ServiceDumpBootstrap id (Just file) }
|
||||
parseServiceArgs ("--dump-bootstrap":id:rest) opts = parseServiceArgs rest opts { svcAction = ServiceDumpBootstrap id Nothing }
|
||||
parseServiceArgs ("--name":n:rest) opts = parseServiceArgs rest opts { svcName = Just n }
|
||||
parseServiceArgs ("--ports":p:rest) opts = parseServiceArgs rest opts { svcPorts = Just p }
|
||||
parseServiceArgs ("--type":t:rest) opts = parseServiceArgs rest opts { svcType = Just t }
|
||||
|
|
@ -337,6 +341,28 @@ serviceCommand opts = do
|
|||
ServiceDestroy sid -> do
|
||||
(_, stdout, _) <- curlDelete apiKey ("https://api.unsandbox.com/services/" ++ sid)
|
||||
putStrLn $ green ++ "Service destroyed: " ++ sid ++ reset
|
||||
ServiceExecute sid cmd -> do
|
||||
let json = "{\"command\":\"" ++ escapeJSON cmd ++ "\"}"
|
||||
(_, stdout, _) <- curlPost apiKey ("https://api.unsandbox.com/services/" ++ sid ++ "/execute") json
|
||||
unless (null stdout) $ putStr $ blue ++ stdout ++ reset
|
||||
ServiceDumpBootstrap sid maybeFile -> do
|
||||
hPutStrLn stderr $ "Fetching bootstrap script from " ++ sid ++ "..."
|
||||
let json = "{\"command\":\"cat /tmp/bootstrap.sh\"}"
|
||||
(_, stdout, _) <- curlPost apiKey ("https://api.unsandbox.com/services/" ++ sid ++ "/execute") json
|
||||
-- Extract stdout from JSON response
|
||||
let bootstrapScript = extractJsonString stdout "stdout"
|
||||
case bootstrapScript of
|
||||
Just script | not (null script) -> do
|
||||
case maybeFile of
|
||||
Just file -> do
|
||||
writeFile file script
|
||||
perms <- getPermissions file
|
||||
setPermissions file (setOwnerExecutable True perms)
|
||||
putStrLn $ "Bootstrap saved to " ++ file
|
||||
Nothing -> putStr script
|
||||
_ -> do
|
||||
hPutStrLn stderr $ red ++ "Error: Failed to fetch bootstrap (service not running or no bootstrap file)" ++ reset
|
||||
exitFailure
|
||||
ServiceCreate -> do
|
||||
case svcName opts of
|
||||
Nothing -> do
|
||||
|
|
|
|||
38
un.jl
38
un.jl
|
|
@ -288,6 +288,34 @@ function cmd_service(args)
|
|||
return
|
||||
end
|
||||
|
||||
if args["dump-bootstrap"] !== nothing
|
||||
println(stderr, "Fetching bootstrap script from $(args["dump-bootstrap"])...")
|
||||
payload = Dict("command" => "cat /tmp/bootstrap.sh")
|
||||
result = api_request("/services/$(args["dump-bootstrap"])/execute", api_key, method="POST", data=payload)
|
||||
|
||||
if haskey(result, "stdout") && !isempty(result["stdout"])
|
||||
bootstrap = result["stdout"]
|
||||
if args["dump-file"] !== nothing
|
||||
# Write to file
|
||||
try
|
||||
write(args["dump-file"], bootstrap)
|
||||
chmod(args["dump-file"], 0o755)
|
||||
println("Bootstrap saved to $(args["dump-file"])")
|
||||
catch e
|
||||
println(stderr, "$(RED)Error: Could not write to $(args["dump-file"]): $e$(RESET)")
|
||||
exit(1)
|
||||
end
|
||||
else
|
||||
# Print to stdout
|
||||
print(bootstrap)
|
||||
end
|
||||
else
|
||||
println(stderr, "$(RED)Error: Failed to fetch bootstrap (service not running or no bootstrap file)$(RESET)")
|
||||
exit(1)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
# Create new service
|
||||
if args["name"] !== nothing
|
||||
payload = Dict("name" => args["name"])
|
||||
|
|
@ -332,7 +360,7 @@ function cmd_service(args)
|
|||
return
|
||||
end
|
||||
|
||||
println(stderr, "$(RED)Error: Use --name to create, or --list, --info, --logs, --sleep, --wake, --destroy$(RESET)")
|
||||
println(stderr, "$(RED)Error: Use --name to create, or --list, --info, --logs, --freeze, --unfreeze, --destroy$(RESET)")
|
||||
exit(1)
|
||||
end
|
||||
|
||||
|
|
@ -550,12 +578,16 @@ function main()
|
|||
help = "Get service details"
|
||||
"--logs"
|
||||
help = "Get all logs"
|
||||
"--sleep"
|
||||
"--freeze"
|
||||
help = "Freeze service"
|
||||
"--wake"
|
||||
"--unfreeze"
|
||||
help = "Unfreeze service"
|
||||
"--destroy"
|
||||
help = "Destroy service"
|
||||
"--dump-bootstrap"
|
||||
help = "Dump bootstrap script from service"
|
||||
"--dump-file"
|
||||
help = "File to save bootstrap (with --dump-bootstrap)"
|
||||
"--api-key", "-k"
|
||||
help = "API key"
|
||||
end
|
||||
|
|
|
|||
65
un.kt
65
un.kt
|
|
@ -94,6 +94,10 @@ data class Args(
|
|||
var serviceSleep: String? = null,
|
||||
var serviceWake: String? = null,
|
||||
var serviceDestroy: String? = null,
|
||||
var serviceExecute: String? = null,
|
||||
var serviceCommand: String? = null,
|
||||
var serviceDumpBootstrap: String? = null,
|
||||
var serviceDumpFile: String? = null,
|
||||
var keyExtend: Boolean = false
|
||||
)
|
||||
|
||||
|
|
@ -301,6 +305,51 @@ fun cmdService(args: Args) {
|
|||
return
|
||||
}
|
||||
|
||||
if (args.serviceExecute != null) {
|
||||
val payload = mutableMapOf<String, Any>("command" to args.serviceCommand!!)
|
||||
val result = apiRequest("/services/${args.serviceExecute}/execute", "POST", payload, apiKey)
|
||||
if (result.containsKey("stdout")) {
|
||||
val stdout = result["stdout"] as? String
|
||||
if (stdout != null && stdout.isNotEmpty()) {
|
||||
print("$BLUE$stdout$RESET")
|
||||
}
|
||||
}
|
||||
if (result.containsKey("stderr")) {
|
||||
val stderr = result["stderr"] as? String
|
||||
if (stderr != null && stderr.isNotEmpty()) {
|
||||
System.err.print("$RED$stderr$RESET")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (args.serviceDumpBootstrap != null) {
|
||||
System.err.println("Fetching bootstrap script from ${args.serviceDumpBootstrap}...")
|
||||
val payload = mutableMapOf<String, Any>("command" to "cat /tmp/bootstrap.sh")
|
||||
val result = apiRequest("/services/${args.serviceDumpBootstrap}/execute", "POST", payload, apiKey)
|
||||
|
||||
val bootstrap = result["stdout"] as? String
|
||||
if (bootstrap != null && bootstrap.isNotEmpty()) {
|
||||
if (args.serviceDumpFile != null) {
|
||||
try {
|
||||
val file = java.io.File(args.serviceDumpFile!!)
|
||||
file.writeText(bootstrap)
|
||||
file.setExecutable(true)
|
||||
println("Bootstrap saved to ${args.serviceDumpFile}")
|
||||
} catch (e: Exception) {
|
||||
System.err.println("${RED}Error: Could not write to ${args.serviceDumpFile}: ${e.message}${RESET}")
|
||||
exitProcess(1)
|
||||
}
|
||||
} else {
|
||||
print(bootstrap)
|
||||
}
|
||||
} else {
|
||||
System.err.println("${RED}Error: Failed to fetch bootstrap (service not running or no bootstrap file)${RESET}")
|
||||
exitProcess(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (args.serviceName != null) {
|
||||
val payload = mutableMapOf<String, Any>("name" to args.serviceName!!)
|
||||
if (args.servicePorts != null) {
|
||||
|
|
@ -597,9 +646,13 @@ fun parseArgs(args: Array<String>): Args {
|
|||
"--info" -> result.serviceInfo = args[++i]
|
||||
"--logs" -> result.serviceLogs = args[++i]
|
||||
"--tail" -> result.serviceTail = args[++i]
|
||||
"--sleep" -> result.serviceSleep = args[++i]
|
||||
"--wake" -> result.serviceWake = args[++i]
|
||||
"--freeze" -> result.serviceSleep = args[++i]
|
||||
"--unfreeze" -> result.serviceWake = args[++i]
|
||||
"--destroy" -> result.serviceDestroy = args[++i]
|
||||
"--execute" -> result.serviceExecute = args[++i]
|
||||
"--command" -> result.serviceCommand = args[++i]
|
||||
"--dump-bootstrap" -> result.serviceDumpBootstrap = args[++i]
|
||||
"--dump-file" -> result.serviceDumpFile = args[++i]
|
||||
"--extend" -> result.keyExtend = true
|
||||
else -> if (!args[i].startsWith("-")) result.sourceFile = args[i]
|
||||
}
|
||||
|
|
@ -638,9 +691,13 @@ Service options:
|
|||
--info ID Get service details
|
||||
--logs ID Get all logs
|
||||
--tail ID Get last 9000 lines
|
||||
--sleep ID Freeze service
|
||||
--wake ID Unfreeze service
|
||||
--freeze ID Freeze service
|
||||
--unfreeze ID Unfreeze service
|
||||
--destroy ID Destroy service
|
||||
--execute ID Execute command in service
|
||||
--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:
|
||||
--extend Open browser to extend key
|
||||
|
|
|
|||
34
un.lisp
34
un.lisp
|
|
@ -183,6 +183,30 @@
|
|||
((string= action "destroy")
|
||||
(curl-delete api-key (format nil "/services/~a" id))
|
||||
(format t "~aService destroyed: ~a~a~%" *green* id *reset*))
|
||||
((string= action "execute")
|
||||
(when (and id bootstrap)
|
||||
(let* ((json (format nil "{\"command\":\"~a\"}" (escape-json bootstrap)))
|
||||
(response (curl-post api-key (format nil "/services/~a/execute" id) json))
|
||||
(stdout-val (parse-json-field response "stdout")))
|
||||
(when stdout-val
|
||||
(format t "~a~a~a" *blue* stdout-val *reset*)))))
|
||||
((string= action "dump-bootstrap")
|
||||
(when id
|
||||
(format *error-output* "Fetching bootstrap script from ~a...~%" id)
|
||||
(let* ((json "{\"command\":\"cat /tmp/bootstrap.sh\"}")
|
||||
(response (curl-post api-key (format nil "/services/~a/execute" id) json))
|
||||
(stdout-val (parse-json-field response "stdout")))
|
||||
(if stdout-val
|
||||
(if service-type
|
||||
(progn
|
||||
(with-open-file (stream service-type :direction :output :if-exists :supersede)
|
||||
(write-string stdout-val stream))
|
||||
(uiop:run-program (list "chmod" "755" service-type))
|
||||
(format t "Bootstrap saved to ~a~%" service-type))
|
||||
(format t "~a" stdout-val))
|
||||
(progn
|
||||
(format *error-output* "~aError: Failed to fetch bootstrap (service not running or no bootstrap file)~a~%" *red* *reset*)
|
||||
(uiop:quit 1))))))
|
||||
((and (string= action "create") name)
|
||||
(let* ((ports-json (if ports (format nil ",\"ports\":[~a]" ports) ""))
|
||||
(bootstrap-json (if bootstrap (format nil ",\"bootstrap\":\"~a\"" (escape-json bootstrap)) ""))
|
||||
|
|
@ -307,12 +331,18 @@
|
|||
(service-cmd "info" (third args) nil nil nil nil))
|
||||
((and (> (length args) 2) (string= (second args) "--logs"))
|
||||
(service-cmd "logs" (third args) nil nil nil nil))
|
||||
((and (> (length args) 2) (string= (second args) "--sleep"))
|
||||
((and (> (length args) 2) (string= (second args) "--freeze"))
|
||||
(service-cmd "sleep" (third args) nil nil nil nil))
|
||||
((and (> (length args) 2) (string= (second args) "--wake"))
|
||||
((and (> (length args) 2) (string= (second args) "--unfreeze"))
|
||||
(service-cmd "wake" (third args) nil nil nil nil))
|
||||
((and (> (length args) 2) (string= (second args) "--destroy"))
|
||||
(service-cmd "destroy" (third args) nil nil nil nil))
|
||||
((and (> (length args) 3) (string= (second args) "--execute"))
|
||||
(service-cmd "execute" (third args) nil nil (fourth args) nil))
|
||||
((and (> (length args) 3) (string= (second args) "--dump-bootstrap"))
|
||||
(service-cmd "dump-bootstrap" (third args) nil nil nil (fourth args)))
|
||||
((and (> (length args) 2) (string= (second args) "--dump-bootstrap"))
|
||||
(service-cmd "dump-bootstrap" (third args) nil nil nil nil))
|
||||
((and (> (length args) 2) (string= (second args) "--name"))
|
||||
(let* ((name (third args))
|
||||
(rest-args (nthcdr 3 args))
|
||||
|
|
|
|||
43
un.m
43
un.m
|
|
@ -444,6 +444,8 @@ void cmdService(NSArray* args) {
|
|||
NSString* sleepId = nil;
|
||||
NSString* wakeId = nil;
|
||||
NSString* destroyId = nil;
|
||||
NSString* dumpBootstrapId = nil;
|
||||
NSString* dumpFile = nil;
|
||||
NSString* name = nil;
|
||||
NSString* ports = nil;
|
||||
NSString* type = nil;
|
||||
|
|
@ -460,12 +462,16 @@ void cmdService(NSArray* args) {
|
|||
infoId = args[++i];
|
||||
} else if ([arg isEqualToString:@"--logs"] && i + 1 < [args count]) {
|
||||
logsId = args[++i];
|
||||
} else if ([arg isEqualToString:@"--sleep"] && i + 1 < [args count]) {
|
||||
} else if ([arg isEqualToString:@"--freeze"] && i + 1 < [args count]) {
|
||||
sleepId = args[++i];
|
||||
} else if ([arg isEqualToString:@"--wake"] && i + 1 < [args count]) {
|
||||
} else if ([arg isEqualToString:@"--unfreeze"] && i + 1 < [args count]) {
|
||||
wakeId = args[++i];
|
||||
} else if ([arg isEqualToString:@"--destroy"] && i + 1 < [args count]) {
|
||||
destroyId = args[++i];
|
||||
} else if ([arg isEqualToString:@"--dump-bootstrap"] && i + 1 < [args count]) {
|
||||
dumpBootstrapId = args[++i];
|
||||
} else if ([arg isEqualToString:@"--dump-file"] && i + 1 < [args count]) {
|
||||
dumpFile = args[++i];
|
||||
} else if ([arg isEqualToString:@"--name"] && i + 1 < [args count]) {
|
||||
name = args[++i];
|
||||
} else if ([arg isEqualToString:@"--ports"] && i + 1 < [args count]) {
|
||||
|
|
@ -541,6 +547,39 @@ void cmdService(NSArray* args) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (dumpBootstrapId) {
|
||||
fprintf(stderr, "Fetching bootstrap script from %s...\n", [dumpBootstrapId UTF8String]);
|
||||
NSDictionary* payload = @{@"command": @"cat /tmp/bootstrap.sh"};
|
||||
NSString* endpoint = [NSString stringWithFormat:@"/services/%@/execute", dumpBootstrapId];
|
||||
NSDictionary* result = apiRequest(endpoint, @"POST", payload, apiKey);
|
||||
|
||||
if (result[@"stdout"] && [result[@"stdout"] length] > 0) {
|
||||
NSString* bootstrap = result[@"stdout"];
|
||||
if (dumpFile) {
|
||||
// Write to file
|
||||
NSError* error = nil;
|
||||
[bootstrap writeToFile:dumpFile atomically:YES encoding:NSUTF8StringEncoding error:&error];
|
||||
if (error) {
|
||||
fprintf(stderr, "%sError: Could not write to %s: %s%s\n",
|
||||
[RED UTF8String], [dumpFile UTF8String],
|
||||
[[error localizedDescription] UTF8String], [RESET UTF8String]);
|
||||
exit(1);
|
||||
}
|
||||
NSFileManager* fm = [NSFileManager defaultManager];
|
||||
[fm setAttributes:@{NSFilePosixPermissions: @0755} ofItemAtPath:dumpFile error:nil];
|
||||
printf("Bootstrap saved to %s\n", [dumpFile UTF8String]);
|
||||
} else {
|
||||
// Print to stdout
|
||||
printf("%s", [bootstrap UTF8String]);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "%sError: Failed to fetch bootstrap (service not running or no bootstrap file)%s\n",
|
||||
[RED UTF8String], [RESET UTF8String]);
|
||||
exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Create new service
|
||||
if (name) {
|
||||
NSMutableDictionary* payload = [NSMutableDictionary dictionaryWithDictionary:@{@"name": name}];
|
||||
|
|
|
|||
77
un.ml
77
un.ml
|
|
@ -402,7 +402,7 @@ let service_command action name ports bootstrap service_type network vcpu =
|
|||
Sys.remove tmp_file;
|
||||
Printf.printf "%sService sleeping: %s%s\n" green sid reset
|
||||
| None ->
|
||||
Printf.fprintf stderr "Error: --sleep requires service ID\n";
|
||||
Printf.fprintf stderr "Error: --freeze requires service ID\n";
|
||||
exit 1)
|
||||
| "wake" ->
|
||||
(match name with
|
||||
|
|
@ -417,7 +417,7 @@ let service_command action name ports bootstrap service_type network vcpu =
|
|||
Sys.remove tmp_file;
|
||||
Printf.printf "%sService waking: %s%s\n" green sid reset
|
||||
| None ->
|
||||
Printf.fprintf stderr "Error: --wake requires service ID\n";
|
||||
Printf.fprintf stderr "Error: --unfreeze requires service ID\n";
|
||||
exit 1)
|
||||
| "destroy" ->
|
||||
(match name with
|
||||
|
|
@ -427,6 +427,72 @@ let service_command action name ports bootstrap service_type network vcpu =
|
|||
| None ->
|
||||
Printf.fprintf stderr "Error: --destroy requires service ID\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 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 curl_cmd = Printf.sprintf "curl -s -X POST https://api.unsandbox.com/services/%s/execute -H 'Content-Type: application/json' -H 'Authorization: Bearer %s' -d @%s"
|
||||
sid api_key tmp_file in
|
||||
let ic = Unix.open_process_in curl_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;
|
||||
(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 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 curl_cmd = Printf.sprintf "curl -s -X POST https://api.unsandbox.com/services/%s/execute -H 'Content-Type: application/json' -H 'Authorization: Bearer %s' -d @%s"
|
||||
sid api_key tmp_file in
|
||||
let ic = Unix.open_process_in curl_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;
|
||||
(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 ->
|
||||
|
|
@ -488,9 +554,12 @@ let () =
|
|||
| "--list" :: rest -> parse_service "list" name ports bootstrap service_type network vcpu rest
|
||||
| "--info" :: id :: rest -> parse_service "info" (Some id) ports bootstrap service_type network vcpu rest
|
||||
| "--logs" :: id :: rest -> parse_service "logs" (Some id) ports bootstrap service_type network vcpu rest
|
||||
| "--sleep" :: id :: rest -> parse_service "sleep" (Some id) ports bootstrap service_type network vcpu rest
|
||||
| "--wake" :: id :: rest -> parse_service "wake" (Some id) ports bootstrap service_type network vcpu rest
|
||||
| "--freeze" :: id :: rest -> parse_service "sleep" (Some id) ports bootstrap service_type network vcpu rest
|
||||
| "--unfreeze" :: id :: rest -> parse_service "wake" (Some id) ports bootstrap service_type network vcpu rest
|
||||
| "--destroy" :: id :: rest -> parse_service "destroy" (Some id) ports bootstrap service_type network vcpu rest
|
||||
| "--execute" :: id :: "--command" :: cmd :: rest -> parse_service "execute" (Some id) ports (Some cmd) service_type network vcpu rest
|
||||
| "--dump-bootstrap" :: id :: file :: rest -> parse_service "dump_bootstrap" (Some id) ports bootstrap (Some file) network vcpu rest
|
||||
| "--dump-bootstrap" :: id :: rest -> parse_service "dump_bootstrap" (Some id) ports bootstrap service_type network vcpu rest
|
||||
| "--name" :: n :: rest -> parse_service "create" (Some n) ports bootstrap service_type network vcpu rest
|
||||
| "--ports" :: p :: rest -> parse_service action name (Some p) bootstrap service_type network vcpu rest
|
||||
| "--bootstrap" :: b :: rest -> parse_service action name ports (Some b) service_type network vcpu rest
|
||||
|
|
|
|||
84
un.nim
84
un.nim
|
|
@ -128,7 +128,7 @@ proc cmdSession(list: bool, kill, shell, network: string, vcpu: int, tmux, scree
|
|||
let cmd = fmt"""curl -s -X POST '{API_BASE}/sessions' -H 'Content-Type: application/json' -H 'Authorization: Bearer {apiKey}' -d '{json}'"""
|
||||
echo execCurl(cmd)
|
||||
|
||||
proc cmdService(name, ports, bootstrap, serviceType: string, list: bool, info, logs, tail, sleep, wake, destroy, network: string, vcpu: int, apiKey: string) =
|
||||
proc cmdService(name, ports, bootstrap, serviceType: string, list: bool, info, logs, tail, sleep, wake, destroy, execute, command, dumpBootstrap, dumpFile, network: string, vcpu: int, apiKey: string) =
|
||||
if list:
|
||||
let cmd = fmt"""curl -s -X GET '{API_BASE}/services' -H 'Authorization: Bearer {apiKey}'"""
|
||||
echo execCurl(cmd)
|
||||
|
|
@ -167,6 +167,76 @@ proc cmdService(name, ports, bootstrap, serviceType: string, list: bool, info, l
|
|||
echo GREEN & "Service destroyed: " & destroy & RESET
|
||||
return
|
||||
|
||||
if execute != "":
|
||||
let json = fmt"""{"command":"{escapeJson(command)}"}"""
|
||||
let cmd = fmt"""curl -s -X POST '{API_BASE}/services/{execute}/execute' -H 'Content-Type: application/json' -H 'Authorization: Bearer {apiKey}' -d '{json}'"""
|
||||
let result = execCurl(cmd)
|
||||
|
||||
# Simple parsing for stdout/stderr
|
||||
let stdoutStart = result.find("\"stdout\":\"")
|
||||
if stdoutStart >= 0:
|
||||
let start = stdoutStart + 10
|
||||
var endPos = start
|
||||
while endPos < result.len:
|
||||
if result[endPos] == '"' and (endPos == 0 or result[endPos-1] != '\\'):
|
||||
break
|
||||
inc endPos
|
||||
if endPos > start:
|
||||
var output = result[start..<endPos]
|
||||
output = output.replace("\\n", "\n").replace("\\r", "\r").replace("\\t", "\t").replace("\\\"", "\"").replace("\\\\", "\\")
|
||||
stdout.write(output)
|
||||
|
||||
let stderrStart = result.find("\"stderr\":\"")
|
||||
if stderrStart >= 0:
|
||||
let start = stderrStart + 10
|
||||
var endPos = start
|
||||
while endPos < result.len:
|
||||
if result[endPos] == '"' and (endPos == 0 or result[endPos-1] != '\\'):
|
||||
break
|
||||
inc endPos
|
||||
if endPos > start:
|
||||
var errout = result[start..<endPos]
|
||||
errout = errout.replace("\\n", "\n").replace("\\r", "\r").replace("\\t", "\t").replace("\\\"", "\"").replace("\\\\", "\\")
|
||||
stderr.write(errout)
|
||||
return
|
||||
|
||||
if dumpBootstrap != "":
|
||||
stderr.writeLine("Fetching bootstrap script from " & dumpBootstrap & "...")
|
||||
let cmd = fmt"""curl -s -X POST '{API_BASE}/services/{dumpBootstrap}/execute' -H 'Content-Type: application/json' -H 'Authorization: Bearer {apiKey}' -d '{{"command":"cat /tmp/bootstrap.sh"}}'"""
|
||||
let result = execCurl(cmd)
|
||||
|
||||
let stdoutStart = result.find("\"stdout\":\"")
|
||||
if stdoutStart >= 0:
|
||||
let start = stdoutStart + 10
|
||||
var endPos = start
|
||||
while endPos < result.len:
|
||||
if result[endPos] == '"' and (endPos == 0 or result[endPos-1] != '\\'):
|
||||
break
|
||||
inc endPos
|
||||
if endPos > start:
|
||||
var bootstrapScript = result[start..<endPos]
|
||||
bootstrapScript = bootstrapScript.replace("\\n", "\n").replace("\\r", "\r").replace("\\t", "\t").replace("\\\"", "\"").replace("\\\\", "\\")
|
||||
|
||||
if dumpFile != "":
|
||||
try:
|
||||
writeFile(dumpFile, bootstrapScript)
|
||||
when defined(posix):
|
||||
import os
|
||||
setFilePermissions(dumpFile, {fpUserExec, fpUserWrite, fpUserRead, fpGroupExec, fpGroupRead, fpOthersExec, fpOthersRead})
|
||||
echo "Bootstrap saved to " & dumpFile
|
||||
except IOError as e:
|
||||
stderr.writeLine(RED & "Error: Could not write to " & dumpFile & ": " & e.msg & RESET)
|
||||
quit(1)
|
||||
else:
|
||||
stdout.write(bootstrapScript)
|
||||
else:
|
||||
stderr.writeLine(RED & "Error: Failed to fetch bootstrap (service not running or no bootstrap file)" & RESET)
|
||||
quit(1)
|
||||
else:
|
||||
stderr.writeLine(RED & "Error: Failed to fetch bootstrap (service not running or no bootstrap file)" & RESET)
|
||||
quit(1)
|
||||
return
|
||||
|
||||
if name != "":
|
||||
var json = fmt"""{"name":"{name}""""
|
||||
if ports != "": json.add(fmt""","ports":[{ports}]""")
|
||||
|
|
@ -309,7 +379,7 @@ proc main() =
|
|||
if args[0] == "service":
|
||||
var name, ports, bootstrap, serviceType = ""
|
||||
var list = false
|
||||
var info, logs, tail, sleep, wake, destroy, network = ""
|
||||
var info, logs, tail, sleep, wake, destroy, execute, command, dumpBootstrap, dumpFile, network = ""
|
||||
var vcpu = 0
|
||||
var i = 1
|
||||
while i < args.len:
|
||||
|
|
@ -322,14 +392,18 @@ proc main() =
|
|||
of "--info": info = args[i+1]; inc i
|
||||
of "--logs": logs = args[i+1]; inc i
|
||||
of "--tail": tail = args[i+1]; inc i
|
||||
of "--sleep": sleep = args[i+1]; inc i
|
||||
of "--wake": wake = args[i+1]; inc i
|
||||
of "--freeze": sleep = args[i+1]; inc i
|
||||
of "--unfreeze": wake = args[i+1]; inc i
|
||||
of "--destroy": destroy = args[i+1]; inc i
|
||||
of "--execute": execute = args[i+1]; inc i
|
||||
of "--command": command = args[i+1]; inc i
|
||||
of "--dump-bootstrap": dumpBootstrap = args[i+1]; inc i
|
||||
of "--dump-file": dumpFile = args[i+1]; inc i
|
||||
of "-n": network = args[i+1]; inc i
|
||||
of "-v": vcpu = parseInt(args[i+1]); inc i
|
||||
of "-k": apiKey = args[i+1]; inc i
|
||||
inc i
|
||||
cmdService(name, ports, bootstrap, serviceType, list, info, logs, tail, sleep, wake, destroy, network, vcpu, apiKey)
|
||||
cmdService(name, ports, bootstrap, serviceType, list, info, logs, tail, sleep, wake, destroy, execute, command, dumpBootstrap, dumpFile, network, vcpu, apiKey)
|
||||
return
|
||||
|
||||
# Execute mode
|
||||
|
|
|
|||
26
un.pro
26
un.pro
|
|
@ -178,6 +178,21 @@ service_destroy(ServiceId) :-
|
|||
[ServiceId, ApiKey, ServiceId]),
|
||||
shell(Cmd, 0).
|
||||
|
||||
% Service dump bootstrap
|
||||
service_dump_bootstrap(ServiceId, DumpFile) :-
|
||||
get_api_key(ApiKey),
|
||||
( DumpFile = ''
|
||||
-> % No file specified, print to stdout
|
||||
format(atom(Cmd),
|
||||
'echo "Fetching bootstrap script from ~w..." >&2; RESP=$(curl -s -X POST https://api.unsandbox.com/services/~w/execute -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -d \'\'\'\'{"command":"cat /tmp/bootstrap.sh"}\'\'\'\'); STDOUT=$(echo "$RESP" | jq -r ".stdout // empty"); if [ -n "$STDOUT" ]; then echo "$STDOUT"; else echo -e "\\x1b[31mError: Failed to fetch bootstrap\\x1b[0m" >&2; exit 1; fi',
|
||||
[ServiceId, ServiceId, ApiKey])
|
||||
; % File specified, save to file
|
||||
format(atom(Cmd),
|
||||
'echo "Fetching bootstrap script from ~w..." >&2; RESP=$(curl -s -X POST https://api.unsandbox.com/services/~w/execute -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -d \'\'\'\'{"command":"cat /tmp/bootstrap.sh"}\'\'\'\'); STDOUT=$(echo "$RESP" | jq -r ".stdout // empty"); if [ -n "$STDOUT" ]; then echo "$STDOUT" > "~w" && chmod 755 "~w" && echo "Bootstrap saved to ~w"; else echo -e "\\x1b[31mError: Failed to fetch bootstrap\\x1b[0m" >&2; exit 1; fi',
|
||||
[ServiceId, ServiceId, ApiKey, DumpFile, DumpFile, DumpFile])
|
||||
),
|
||||
shell(Cmd, 0).
|
||||
|
||||
% Service create
|
||||
service_create(Name, Ports, Bootstrap, ServiceType) :-
|
||||
get_api_key(ApiKey),
|
||||
|
|
@ -244,16 +259,21 @@ parse_service_args([], Name, Ports, Bootstrap, ServiceType, create) :-
|
|||
parse_service_args([], _, _, _, _, Action) :-
|
||||
( Action = list
|
||||
-> service_list
|
||||
; write(user_error, 'Error: Use --list, --info, --logs, --sleep, --wake, --destroy, or --name\n'),
|
||||
; write(user_error, 'Error: Use --list, --info, --logs, --freeze, --unfreeze, --destroy, or --name\n'),
|
||||
halt(1)
|
||||
).
|
||||
parse_service_args(['--list'|_], _, _, _, _, _) :- service_list.
|
||||
parse_service_args(['-l'|_], _, _, _, _, _) :- service_list.
|
||||
parse_service_args(['--info', ServiceId|_], _, _, _, _, _) :- service_info(ServiceId).
|
||||
parse_service_args(['--logs', ServiceId|_], _, _, _, _, _) :- service_logs(ServiceId).
|
||||
parse_service_args(['--sleep', ServiceId|_], _, _, _, _, _) :- service_sleep(ServiceId).
|
||||
parse_service_args(['--wake', ServiceId|_], _, _, _, _, _) :- service_wake(ServiceId).
|
||||
parse_service_args(['--freeze', ServiceId|_], _, _, _, _, _) :- service_sleep(ServiceId).
|
||||
parse_service_args(['--unfreeze', ServiceId|_], _, _, _, _, _) :- service_wake(ServiceId).
|
||||
parse_service_args(['--destroy', ServiceId|_], _, _, _, _, _) :- service_destroy(ServiceId).
|
||||
parse_service_args(['--dump-bootstrap', ServiceId|Rest], _, _, _, _, _) :-
|
||||
( Rest = ['--dump-file', DumpFile|_]
|
||||
-> service_dump_bootstrap(ServiceId, DumpFile)
|
||||
; service_dump_bootstrap(ServiceId, '')
|
||||
).
|
||||
parse_service_args(['--name', Name|Rest], _, Ports, Bootstrap, ServiceType, _) :-
|
||||
parse_service_args(Rest, Name, Ports, Bootstrap, ServiceType, create).
|
||||
parse_service_args(['--ports', PortsList|Rest], Name, _, Bootstrap, ServiceType, Action) :-
|
||||
|
|
|
|||
60
un.ps1
60
un.ps1
|
|
@ -248,16 +248,16 @@ function Invoke-Service {
|
|||
return
|
||||
}
|
||||
|
||||
if ($Args -contains "--sleep") {
|
||||
$idx = [array]::IndexOf($Args, "--sleep")
|
||||
if ($Args -contains "--freeze") {
|
||||
$idx = [array]::IndexOf($Args, "--freeze")
|
||||
$serviceId = $Args[$idx + 1]
|
||||
Invoke-Api -Endpoint "/services/$serviceId/sleep" -Method "POST" -Body "{}"
|
||||
Write-Host "`e[32mService sleeping: $serviceId`e[0m"
|
||||
return
|
||||
}
|
||||
|
||||
if ($Args -contains "--wake") {
|
||||
$idx = [array]::IndexOf($Args, "--wake")
|
||||
if ($Args -contains "--unfreeze") {
|
||||
$idx = [array]::IndexOf($Args, "--unfreeze")
|
||||
$serviceId = $Args[$idx + 1]
|
||||
Invoke-Api -Endpoint "/services/$serviceId/wake" -Method "POST" -Body "{}"
|
||||
Write-Host "`e[32mService waking: $serviceId`e[0m"
|
||||
|
|
@ -272,6 +272,36 @@ function Invoke-Service {
|
|||
return
|
||||
}
|
||||
|
||||
if ($Args -contains "--dump-bootstrap") {
|
||||
$idx = [array]::IndexOf($Args, "--dump-bootstrap")
|
||||
$serviceId = $Args[$idx + 1]
|
||||
Write-Host "Fetching bootstrap script from $serviceId..." -ForegroundColor Yellow
|
||||
|
||||
$payload = @{ command = "cat /tmp/bootstrap.sh" } | ConvertTo-Json
|
||||
$result = Invoke-Api -Endpoint "/services/$serviceId/execute" -Method "POST" -Body $payload
|
||||
|
||||
if ($result.stdout -and $result.stdout.Length -gt 0) {
|
||||
$bootstrap = $result.stdout
|
||||
if ($Args -contains "--dump-file") {
|
||||
$dumpIdx = [array]::IndexOf($Args, "--dump-file")
|
||||
$dumpFile = $Args[$dumpIdx + 1]
|
||||
# Write to file
|
||||
$bootstrap | Set-Content -Path $dumpFile -NoNewline
|
||||
if ($IsLinux -or $IsMacOS) {
|
||||
& chmod 755 $dumpFile
|
||||
}
|
||||
Write-Host "Bootstrap saved to $dumpFile"
|
||||
} else {
|
||||
# Print to stdout
|
||||
Write-Host $bootstrap -NoNewline
|
||||
}
|
||||
} else {
|
||||
Write-Error "Error: Failed to fetch bootstrap (service not running or no bootstrap file)"
|
||||
exit 1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
# Create service
|
||||
if ($Args -contains "--name") {
|
||||
$idx = [array]::IndexOf($Args, "--name")
|
||||
|
|
@ -324,16 +354,18 @@ Session options:
|
|||
--shell NAME Shell/REPL to use
|
||||
|
||||
Service options:
|
||||
--name NAME Service name
|
||||
--ports PORTS Comma-separated ports
|
||||
--type TYPE Service type (minecraft, mumble, teamspeak, source, tcp, udp)
|
||||
--bootstrap CMD Bootstrap command
|
||||
--list, -l List services
|
||||
--info ID Get service info
|
||||
--logs ID Get logs
|
||||
--sleep ID Freeze service
|
||||
--wake ID Unfreeze service
|
||||
--destroy ID Destroy service
|
||||
--name NAME Service name
|
||||
--ports PORTS Comma-separated ports
|
||||
--type TYPE Service type (minecraft, mumble, teamspeak, source, tcp, udp)
|
||||
--bootstrap CMD Bootstrap command
|
||||
--list, -l List services
|
||||
--info ID Get service info
|
||||
--logs ID Get logs
|
||||
--freeze ID Freeze service
|
||||
--unfreeze ID Unfreeze service
|
||||
--destroy ID Destroy service
|
||||
--dump-bootstrap ID Dump bootstrap script from service
|
||||
--dump-file FILE Save bootstrap to file (with --dump-bootstrap)
|
||||
|
||||
Key options:
|
||||
--extend Open browser to extend key
|
||||
|
|
|
|||
44
un.r
44
un.r
|
|
@ -354,6 +354,34 @@ cmd_service <- function(args) {
|
|||
return()
|
||||
}
|
||||
|
||||
if (!is.null(args$dump_bootstrap)) {
|
||||
cat(sprintf("Fetching bootstrap script from %s...\n", args$dump_bootstrap), file = stderr())
|
||||
payload <- list(command = "cat /tmp/bootstrap.sh")
|
||||
result <- api_request(paste0("/services/", args$dump_bootstrap, "/execute"), api_key, method = "POST", data = payload)
|
||||
|
||||
if (!is.null(result$stdout) && result$stdout != "") {
|
||||
bootstrap <- result$stdout
|
||||
if (!is.null(args$dump_file)) {
|
||||
# Write to file
|
||||
tryCatch({
|
||||
writeLines(bootstrap, args$dump_file)
|
||||
Sys.chmod(args$dump_file, mode = "0755")
|
||||
cat(sprintf("Bootstrap saved to %s\n", args$dump_file))
|
||||
}, error = function(e) {
|
||||
cat(sprintf("%sError: Could not write to %s: %s%s\n", RED, args$dump_file, e$message, RESET), file = stderr())
|
||||
quit(status = 1)
|
||||
})
|
||||
} else {
|
||||
# Print to stdout
|
||||
cat(bootstrap)
|
||||
}
|
||||
} else {
|
||||
cat(sprintf("%sError: Failed to fetch bootstrap (service not running or no bootstrap file)%s\n", RED, RESET), file = stderr())
|
||||
quit(status = 1)
|
||||
}
|
||||
return()
|
||||
}
|
||||
|
||||
if (!is.null(args$name)) {
|
||||
payload <- list(name = args$name)
|
||||
|
||||
|
|
@ -396,7 +424,7 @@ cmd_service <- function(args) {
|
|||
return()
|
||||
}
|
||||
|
||||
cat(sprintf("%sError: Use --name to create, or --list, --info, --logs, --sleep, --wake, --destroy%s\n", RED, RESET), file = stderr())
|
||||
cat(sprintf("%sError: Use --name to create, or --list, --info, --logs, --freeze, --unfreeze, --destroy%s\n", RED, RESET), file = stderr())
|
||||
quit(status = 1)
|
||||
}
|
||||
|
||||
|
|
@ -419,6 +447,8 @@ parse_args <- function() {
|
|||
sleep = NULL,
|
||||
wake = NULL,
|
||||
destroy = NULL,
|
||||
dump_bootstrap = NULL,
|
||||
dump_file = NULL,
|
||||
name = NULL,
|
||||
ports = NULL,
|
||||
domains = NULL,
|
||||
|
|
@ -479,11 +509,11 @@ parse_args <- function() {
|
|||
i <- i + 1
|
||||
result$logs <- args[i]
|
||||
i <- i + 1
|
||||
} else if (arg == "--sleep") {
|
||||
} else if (arg == "--freeze") {
|
||||
i <- i + 1
|
||||
result$sleep <- args[i]
|
||||
i <- i + 1
|
||||
} else if (arg == "--wake") {
|
||||
} else if (arg == "--unfreeze") {
|
||||
i <- i + 1
|
||||
result$wake <- args[i]
|
||||
i <- i + 1
|
||||
|
|
@ -491,6 +521,14 @@ parse_args <- function() {
|
|||
i <- i + 1
|
||||
result$destroy <- args[i]
|
||||
i <- i + 1
|
||||
} else if (arg == "--dump-bootstrap") {
|
||||
i <- i + 1
|
||||
result$dump_bootstrap <- args[i]
|
||||
i <- i + 1
|
||||
} else if (arg == "--dump-file") {
|
||||
i <- i + 1
|
||||
result$dump_file <- args[i]
|
||||
i <- i + 1
|
||||
} else if (arg == "--name") {
|
||||
i <- i + 1
|
||||
result$name <- args[i]
|
||||
|
|
|
|||
37
un.raku
37
un.raku
|
|
@ -319,6 +319,8 @@ sub cmd-service(@args) {
|
|||
my $sleep-id = '';
|
||||
my $wake-id = '';
|
||||
my $destroy-id = '';
|
||||
my $dump-bootstrap-id = '';
|
||||
my $dump-file = '';
|
||||
my $name = '';
|
||||
my $ports = '';
|
||||
my $type = '';
|
||||
|
|
@ -341,11 +343,11 @@ sub cmd-service(@args) {
|
|||
$i++;
|
||||
$logs-id = @args[$i];
|
||||
}
|
||||
when '--sleep' {
|
||||
when '--freeze' {
|
||||
$i++;
|
||||
$sleep-id = @args[$i];
|
||||
}
|
||||
when '--wake' {
|
||||
when '--unfreeze' {
|
||||
$i++;
|
||||
$wake-id = @args[$i];
|
||||
}
|
||||
|
|
@ -353,6 +355,14 @@ sub cmd-service(@args) {
|
|||
$i++;
|
||||
$destroy-id = @args[$i];
|
||||
}
|
||||
when '--dump-bootstrap' {
|
||||
$i++;
|
||||
$dump-bootstrap-id = @args[$i];
|
||||
}
|
||||
when '--dump-file' {
|
||||
$i++;
|
||||
$dump-file = @args[$i];
|
||||
}
|
||||
when '--name' {
|
||||
$i++;
|
||||
$name = @args[$i];
|
||||
|
|
@ -428,6 +438,29 @@ sub cmd-service(@args) {
|
|||
return;
|
||||
}
|
||||
|
||||
if $dump-bootstrap-id {
|
||||
note "Fetching bootstrap script from $dump-bootstrap-id...";
|
||||
my %payload = command => "cat /tmp/bootstrap.sh";
|
||||
my %result = api-request("/services/$dump-bootstrap-id/execute", 'POST', %payload, :$api-key);
|
||||
|
||||
if %result<stdout> && %result<stdout> ne '' {
|
||||
my $bootstrap = %result<stdout>;
|
||||
if $dump-file {
|
||||
# Write to file
|
||||
$dump-file.IO.spurt($bootstrap);
|
||||
run 'chmod', '755', $dump-file;
|
||||
say "Bootstrap saved to $dump-file";
|
||||
} else {
|
||||
# Print to stdout
|
||||
print $bootstrap;
|
||||
}
|
||||
} else {
|
||||
note "{$RED}Error: Failed to fetch bootstrap (service not running or no bootstrap file){$RESET}";
|
||||
exit 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
# Create new service
|
||||
if $name {
|
||||
my %payload = name => $name;
|
||||
|
|
|
|||
59
un.rs
59
un.rs
|
|
@ -348,6 +348,10 @@ fn cmd_service(
|
|||
sleep: Option<&str>,
|
||||
wake: Option<&str>,
|
||||
destroy: Option<&str>,
|
||||
execute: Option<&str>,
|
||||
command: Option<&str>,
|
||||
dump_bootstrap: Option<&str>,
|
||||
dump_file: Option<&str>,
|
||||
network: Option<&str>,
|
||||
vcpu: Option<i32>,
|
||||
api_key: &str,
|
||||
|
|
@ -394,6 +398,53 @@ fn cmd_service(
|
|||
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), api_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), api_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);
|
||||
|
|
@ -621,9 +672,13 @@ fn main() {
|
|||
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 == "--sleep").and_then(|p| args.get(p + 1)).map(|s| s.as_str()),
|
||||
args.iter().position(|x| x == "--wake").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 == "--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,
|
||||
&key,
|
||||
|
|
|
|||
34
un.scm
34
un.scm
|
|
@ -279,6 +279,30 @@
|
|||
((equal? action "destroy")
|
||||
(curl-delete api-key (format #f "/services/~a" id))
|
||||
(format #t "~aService destroyed: ~a~a\n" green id reset))
|
||||
((equal? action "execute")
|
||||
(when (and id bootstrap)
|
||||
(let* ((json (format #f "{\"command\":\"~a\"}" (escape-json bootstrap)))
|
||||
(response (curl-post api-key (format #f "/services/~a/execute" id) json))
|
||||
(stdout-val (json-extract-string response "stdout")))
|
||||
(when stdout-val
|
||||
(display (format #f "~a~a~a" blue stdout-val reset))))))
|
||||
((equal? action "dump-bootstrap")
|
||||
(when id
|
||||
(format (current-error-port) "Fetching bootstrap script from ~a...\n" id)
|
||||
(let* ((json "{\"command\":\"cat /tmp/bootstrap.sh\"}")
|
||||
(response (curl-post api-key (format #f "/services/~a/execute" id) json))
|
||||
(stdout-val (json-extract-string response "stdout")))
|
||||
(if stdout-val
|
||||
(if type
|
||||
(begin
|
||||
(call-with-output-file type
|
||||
(lambda (port) (display stdout-val port)))
|
||||
(system (format #f "chmod 755 ~a" type))
|
||||
(format #t "Bootstrap saved to ~a\n" type))
|
||||
(display stdout-val))
|
||||
(begin
|
||||
(format (current-error-port) "~aError: Failed to fetch bootstrap (service not running or no bootstrap file)~a\n" red reset)
|
||||
(exit 1))))))
|
||||
((and (equal? action "create") name)
|
||||
(let* ((ports-json (if ports (format #f ",\"ports\":[~a]" ports) ""))
|
||||
(bootstrap-json (if bootstrap (format #f ",\"bootstrap\":\"~a\"" (escape-json bootstrap)) ""))
|
||||
|
|
@ -318,12 +342,18 @@
|
|||
(service-cmd "info" (caddr args) #f #f #f #f))
|
||||
((and (> (length args) 2) (equal? (cadr args) "--logs"))
|
||||
(service-cmd "logs" (caddr args) #f #f #f #f))
|
||||
((and (> (length args) 2) (equal? (cadr args) "--sleep"))
|
||||
((and (> (length args) 2) (equal? (cadr args) "--freeze"))
|
||||
(service-cmd "sleep" (caddr args) #f #f #f #f))
|
||||
((and (> (length args) 2) (equal? (cadr args) "--wake"))
|
||||
((and (> (length args) 2) (equal? (cadr args) "--unfreeze"))
|
||||
(service-cmd "wake" (caddr args) #f #f #f #f))
|
||||
((and (> (length args) 2) (equal? (cadr args) "--destroy"))
|
||||
(service-cmd "destroy" (caddr args) #f #f #f #f))
|
||||
((and (> (length args) 3) (equal? (cadr args) "--execute"))
|
||||
(service-cmd "execute" (caddr args) #f #f (list-ref args 3) #f))
|
||||
((and (> (length args) 3) (equal? (cadr args) "--dump-bootstrap"))
|
||||
(service-cmd "dump-bootstrap" (caddr args) #f #f #f (list-ref args 3)))
|
||||
((and (> (length args) 2) (equal? (cadr args) "--dump-bootstrap"))
|
||||
(service-cmd "dump-bootstrap" (caddr args) #f #f #f #f))
|
||||
((and (> (length args) 2) (equal? (cadr args) "--name"))
|
||||
(let ((name (caddr args))
|
||||
(ports (if (and (> (length args) 4) (equal? (list-ref args 3) "--ports"))
|
||||
|
|
|
|||
39
un.tcl
39
un.tcl
|
|
@ -445,6 +445,8 @@ proc cmd_service {args} {
|
|||
set sleep_id ""
|
||||
set wake_id ""
|
||||
set destroy_id ""
|
||||
set dump_bootstrap_id ""
|
||||
set dump_file ""
|
||||
set name ""
|
||||
set ports ""
|
||||
set service_type ""
|
||||
|
|
@ -467,11 +469,11 @@ proc cmd_service {args} {
|
|||
incr i
|
||||
set logs_id [lindex $args $i]
|
||||
}
|
||||
--sleep {
|
||||
--freeze {
|
||||
incr i
|
||||
set sleep_id [lindex $args $i]
|
||||
}
|
||||
--wake {
|
||||
--unfreeze {
|
||||
incr i
|
||||
set wake_id [lindex $args $i]
|
||||
}
|
||||
|
|
@ -479,6 +481,14 @@ proc cmd_service {args} {
|
|||
incr i
|
||||
set destroy_id [lindex $args $i]
|
||||
}
|
||||
--dump-bootstrap {
|
||||
incr i
|
||||
set dump_bootstrap_id [lindex $args $i]
|
||||
}
|
||||
--dump-file {
|
||||
incr i
|
||||
set dump_file [lindex $args $i]
|
||||
}
|
||||
--name {
|
||||
incr i
|
||||
set name [lindex $args $i]
|
||||
|
|
@ -557,6 +567,31 @@ proc cmd_service {args} {
|
|||
return
|
||||
}
|
||||
|
||||
if {$dump_bootstrap_id ne ""} {
|
||||
puts stderr "Fetching bootstrap script from $dump_bootstrap_id..."
|
||||
set payload [list command [::json::write string "cat /tmp/bootstrap.sh"]]
|
||||
set result [api_request "/services/$dump_bootstrap_id/execute" "POST" $payload $api_key]
|
||||
|
||||
if {[dict exists $result stdout] && [dict get $result stdout] ne ""} {
|
||||
set bootstrap [dict get $result stdout]
|
||||
if {$dump_file ne ""} {
|
||||
# Write to file
|
||||
set fp [open $dump_file w]
|
||||
puts -nonewline $fp $bootstrap
|
||||
close $fp
|
||||
file attributes $dump_file -permissions 0755
|
||||
puts "Bootstrap saved to $dump_file"
|
||||
} else {
|
||||
# Print to stdout
|
||||
puts -nonewline $bootstrap
|
||||
}
|
||||
} else {
|
||||
puts stderr "${::RED}Error: Failed to fetch bootstrap (service not running or no bootstrap file)${::RESET}"
|
||||
exit 1
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
# Create new service
|
||||
if {$name ne ""} {
|
||||
set payload [list name [::json::write string $name]]
|
||||
|
|
|
|||
68
un.v
68
un.v
|
|
@ -268,7 +268,7 @@ fn cmd_session(list bool, kill string, shell string, network string, vcpu int, t
|
|||
println(exec_curl(cmd))
|
||||
}
|
||||
|
||||
fn cmd_service(name string, ports string, service_type string, bootstrap string, list bool, info string, logs string, tail string, sleep string, wake string, destroy string, network string, vcpu int, api_key string) {
|
||||
fn cmd_service(name string, ports string, service_type string, bootstrap string, list bool, info string, logs string, tail string, sleep string, wake string, destroy string, execute string, command string, dump_bootstrap string, dump_file string, network string, vcpu int, api_key string) {
|
||||
if list {
|
||||
cmd := "curl -s -X GET '${api_base}/services' -H 'Authorization: Bearer ${api_key}'"
|
||||
println(exec_curl(cmd))
|
||||
|
|
@ -314,6 +314,46 @@ fn cmd_service(name string, ports string, service_type string, bootstrap string,
|
|||
return
|
||||
}
|
||||
|
||||
if execute != '' {
|
||||
json := '{"command":"${escape_json(command)}"}'
|
||||
cmd := "curl -s -X POST '${api_base}/services/${execute}/execute' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${api_key}' -d '${json}'"
|
||||
result := exec_curl(cmd)
|
||||
|
||||
stdout_str := extract_json_string(result, 'stdout')
|
||||
stderr_str := extract_json_string(result, 'stderr')
|
||||
if stdout_str != '' {
|
||||
print(stdout_str)
|
||||
}
|
||||
if stderr_str != '' {
|
||||
eprint(stderr_str)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if dump_bootstrap != '' {
|
||||
eprintln('Fetching bootstrap script from ${dump_bootstrap}...')
|
||||
cmd := "curl -s -X POST '${api_base}/services/${dump_bootstrap}/execute' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${api_key}' -d '{\"command\":\"cat /tmp/bootstrap.sh\"}'"
|
||||
result := exec_curl(cmd)
|
||||
|
||||
bootstrap_script := extract_json_string(result, 'stdout')
|
||||
if bootstrap_script != '' {
|
||||
if dump_file != '' {
|
||||
os.write_file(dump_file, bootstrap_script) or {
|
||||
eprintln('${red}Error: Could not write to ${dump_file}: ${err}${reset}')
|
||||
exit(1)
|
||||
}
|
||||
os.chmod(dump_file, 0o755) or {}
|
||||
println('Bootstrap saved to ${dump_file}')
|
||||
} else {
|
||||
print(bootstrap_script)
|
||||
}
|
||||
} else {
|
||||
eprintln('${red}Error: Failed to fetch bootstrap (service not running or no bootstrap file)${reset}')
|
||||
exit(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if name != '' {
|
||||
mut json := '{"name":"${name}"'
|
||||
if ports != '' {
|
||||
|
|
@ -415,6 +455,10 @@ fn main() {
|
|||
mut sleep := ''
|
||||
mut wake := ''
|
||||
mut destroy := ''
|
||||
mut execute := ''
|
||||
mut command := ''
|
||||
mut dump_bootstrap := ''
|
||||
mut dump_file := ''
|
||||
mut network := ''
|
||||
mut vcpu := 0
|
||||
|
||||
|
|
@ -450,11 +494,11 @@ fn main() {
|
|||
i++
|
||||
tail = os.args[i]
|
||||
}
|
||||
'--sleep' {
|
||||
'--freeze' {
|
||||
i++
|
||||
sleep = os.args[i]
|
||||
}
|
||||
'--wake' {
|
||||
'--unfreeze' {
|
||||
i++
|
||||
wake = os.args[i]
|
||||
}
|
||||
|
|
@ -462,6 +506,22 @@ fn main() {
|
|||
i++
|
||||
destroy = os.args[i]
|
||||
}
|
||||
'--execute' {
|
||||
i++
|
||||
execute = os.args[i]
|
||||
}
|
||||
'--command' {
|
||||
i++
|
||||
command = os.args[i]
|
||||
}
|
||||
'--dump-bootstrap' {
|
||||
i++
|
||||
dump_bootstrap = os.args[i]
|
||||
}
|
||||
'--dump-file' {
|
||||
i++
|
||||
dump_file = os.args[i]
|
||||
}
|
||||
'-n' {
|
||||
i++
|
||||
network = os.args[i]
|
||||
|
|
@ -479,7 +539,7 @@ fn main() {
|
|||
i++
|
||||
}
|
||||
|
||||
cmd_service(name, ports, service_type, bootstrap, list, info, logs, tail, sleep, wake, destroy, network,
|
||||
cmd_service(name, ports, service_type, bootstrap, list, info, logs, tail, sleep, wake, destroy, execute, command, dump_bootstrap, dump_file, network,
|
||||
vcpu, api_key)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
67
un.zig
67
un.zig
|
|
@ -121,6 +121,10 @@ pub fn main() !u8 {
|
|||
var ports: ?[]const u8 = null;
|
||||
var service_type: ?[]const u8 = null;
|
||||
var info: ?[]const u8 = null;
|
||||
var execute: ?[]const u8 = null;
|
||||
var command: ?[]const u8 = null;
|
||||
var dump_bootstrap: ?[]const u8 = null;
|
||||
var dump_file: ?[]const u8 = null;
|
||||
var i: usize = 2;
|
||||
while (i < args.len) : (i += 1) {
|
||||
if (mem.eql(u8, args[i], "--list")) {
|
||||
|
|
@ -137,6 +141,18 @@ pub fn main() !u8 {
|
|||
} else if (mem.eql(u8, args[i], "--info") and i + 1 < args.len) {
|
||||
i += 1;
|
||||
info = args[i];
|
||||
} else if (mem.eql(u8, args[i], "--execute") and i + 1 < args.len) {
|
||||
i += 1;
|
||||
execute = args[i];
|
||||
} else if (mem.eql(u8, args[i], "--command") and i + 1 < args.len) {
|
||||
i += 1;
|
||||
command = args[i];
|
||||
} else if (mem.eql(u8, args[i], "--dump-bootstrap") and i + 1 < args.len) {
|
||||
i += 1;
|
||||
dump_bootstrap = args[i];
|
||||
} else if (mem.eql(u8, args[i], "--dump-file") and i + 1 < args.len) {
|
||||
i += 1;
|
||||
dump_file = args[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -150,6 +166,57 @@ pub fn main() !u8 {
|
|||
defer allocator.free(cmd);
|
||||
_ = std.c.system(cmd.ptr);
|
||||
std.debug.print("\n", .{});
|
||||
} else if (execute) |exec_id| {
|
||||
const cmd_text = command orelse "";
|
||||
const cmd = try std.fmt.allocPrint(allocator, "curl -s -X POST '{s}/services/{s}/execute' -H 'Content-Type: application/json' -H 'Authorization: Bearer {s}' -d '{{\"command\":\"{s}\"}}'", .{ API_BASE, exec_id, api_key, cmd_text });
|
||||
defer allocator.free(cmd);
|
||||
_ = std.c.system(cmd.ptr);
|
||||
std.debug.print("\n", .{});
|
||||
} else if (dump_bootstrap) |bootstrap_id| {
|
||||
std.debug.print("Fetching bootstrap script from {s}...\n", .{bootstrap_id});
|
||||
const tmp_file = "/tmp/unsandbox_bootstrap_dump.txt";
|
||||
const cmd = try std.fmt.allocPrint(allocator, "curl -s -X POST '{s}/services/{s}/execute' -H 'Content-Type: application/json' -H 'Authorization: Bearer {s}' -d '{{\"command\":\"cat /tmp/bootstrap.sh\"}}' -o {s}", .{ API_BASE, bootstrap_id, api_key, tmp_file });
|
||||
defer allocator.free(cmd);
|
||||
_ = std.c.system(cmd.ptr);
|
||||
|
||||
// Read the JSON response
|
||||
const json_content = fs.cwd().readFileAlloc(allocator, tmp_file, 1024 * 1024) catch |err| {
|
||||
std.debug.print("\x1b[31mError reading response: {}\x1b[0m\n", .{err});
|
||||
std.fs.cwd().deleteFile(tmp_file) catch {};
|
||||
return 1;
|
||||
};
|
||||
defer allocator.free(json_content);
|
||||
std.fs.cwd().deleteFile(tmp_file) catch {};
|
||||
|
||||
// Extract stdout from JSON (simple string search)
|
||||
const stdout_prefix = "\"stdout\":\"";
|
||||
if (mem.indexOf(u8, json_content, stdout_prefix)) |start_idx| {
|
||||
const value_start = start_idx + stdout_prefix.len;
|
||||
if (mem.indexOfPos(u8, json_content, value_start, "\"")) |end_idx| {
|
||||
const bootstrap_content = json_content[value_start..end_idx];
|
||||
|
||||
if (dump_file) |file_path| {
|
||||
const file = try std.fs.cwd().createFile(file_path, .{});
|
||||
defer file.close();
|
||||
try file.writeAll(bootstrap_content);
|
||||
// Set permissions (Unix only)
|
||||
if (@import("builtin").os.tag != .windows) {
|
||||
const chmod_cmd = try std.fmt.allocPrint(allocator, "chmod 755 {s}", .{file_path});
|
||||
defer allocator.free(chmod_cmd);
|
||||
_ = std.c.system(chmod_cmd.ptr);
|
||||
}
|
||||
std.debug.print("Bootstrap saved to {s}\n", .{file_path});
|
||||
} else {
|
||||
std.debug.print("{s}", .{bootstrap_content});
|
||||
}
|
||||
} else {
|
||||
std.debug.print("\x1b[31mError: Failed to parse bootstrap response\x1b[0m\n", .{});
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
std.debug.print("\x1b[31mError: Failed to fetch bootstrap (service not running or no bootstrap file)\x1b[0m\n", .{});
|
||||
return 1;
|
||||
}
|
||||
} else if (name) |n| {
|
||||
var json_buf: [4096]u8 = undefined;
|
||||
var json_stream = std.io.fixedBufferStream(&json_buf);
|
||||
|
|
|
|||
|
|
@ -426,12 +426,12 @@ async function cmdService(args: string[]) {
|
|||
logsId = args[++i];
|
||||
}
|
||||
break;
|
||||
case "--sleep":
|
||||
case "--freeze":
|
||||
if (i + 1 < args.length) {
|
||||
sleepId = args[++i];
|
||||
}
|
||||
break;
|
||||
case "--wake":
|
||||
case "--unfreeze":
|
||||
if (i + 1 < args.length) {
|
||||
wakeId = args[++i];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -640,8 +640,8 @@ int main(int argc, char *argv[]) {
|
|||
else if (strcmp(argv[i], "--info") == 0 && i + 1 < argc) info = argv[++i];
|
||||
else if (strcmp(argv[i], "--logs") == 0 && i + 1 < argc) logs = argv[++i];
|
||||
else if (strcmp(argv[i], "--tail") == 0 && i + 1 < argc) tail = argv[++i];
|
||||
else if (strcmp(argv[i], "--sleep") == 0 && i + 1 < argc) sleep_svc = argv[++i];
|
||||
else if (strcmp(argv[i], "--wake") == 0 && i + 1 < argc) wake = argv[++i];
|
||||
else if (strcmp(argv[i], "--freeze") == 0 && i + 1 < argc) sleep_svc = argv[++i];
|
||||
else if (strcmp(argv[i], "--unfreeze") == 0 && i + 1 < argc) wake = argv[++i];
|
||||
else if (strcmp(argv[i], "--destroy") == 0 && i + 1 < argc) destroy = argv[++i];
|
||||
else if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) network = argv[++i];
|
||||
else if (strcmp(argv[i], "-v") == 0 && i + 1 < argc) vcpu = atoi(argv[++i]);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue