From eb5d4ca8bf68a808903f36410f41df875c2ff6d2 Mon Sep 17 00:00:00 2001 From: Russell Ballestrini Date: Fri, 26 Dec 2025 08:39:34 -0500 Subject: [PATCH] Add --type option for services across all 42 implementations Support service_type for SRV records (minecraft, mumble, teamspeak, source, tcp, udp). Each implementation now parses --type and sends service_type in the JSON payload. --- Un.cs | 7 ++++ Un.java | 7 ++++ un.awk | 101 ++++++++++++++++++++++++++++++++++++++++++++++++- un.clj | 54 ++++++++++++++------------ un.cob | 94 ++++++++++++++++++++++++++++++++++++++++++++- un.cpp | 8 ++-- un.cr | 60 ++++++++++++++++++++++++++++- un.d | 8 ++-- un.dart | 8 ++++ un.erl | 11 +++++- un.ex | 4 +- un.f90 | 7 +++- un.forth | 39 ++++++++++++++++++- un.fs | 6 +++ un.go | 8 +++- un.groovy | 8 ++++ un.hs | 7 +++- un.jl | 64 ++++++++++++++++++++++++++++++- un.js | 5 +++ un.kt | 6 +++ un.lisp | 36 +++++++++++------- un.lua | 8 ++++ un.m | 7 ++++ un.ml | 36 +++++++++--------- un.nim | 8 ++-- un.php | 8 ++++ un.pl | 7 ++++ un.pro | 70 +++++++++++++++++++++++++++++----- un.ps1 | 6 +++ un.py | 3 ++ un.r | 76 ++++++++++++++++++++++++++++++++++++- un.raku | 9 +++++ un.rb | 6 +++ un.rs | 6 +++ un.scm | 23 ++++++----- un.sh | 10 +++++ un.tcl | 9 +++++ un.ts | 7 ++++ un.v | 12 +++++- un.zig | 7 ++++ un_deno.ts | 10 +++++ un_inception.c | 11 +++++- 42 files changed, 783 insertions(+), 104 deletions(-) diff --git a/Un.cs b/Un.cs index 4ee6a19..bd556a6 100644 --- a/Un.cs +++ b/Un.cs @@ -331,6 +331,10 @@ class Un } payload["ports"] = ports; } + if (args.ServiceType != null) + { + payload["service_type"] = args.ServiceType; + } if (args.ServiceBootstrap != null) { payload["bootstrap"] = args.ServiceBootstrap; @@ -675,6 +679,7 @@ class Un public string ServiceSleep = null; public string ServiceWake = null; public string ServiceDestroy = null; + public string ServiceType = null; } static Args ParseArgs(string[] args) @@ -701,6 +706,7 @@ class Un else if (arg == "--kill") result.SessionKill = args[++i]; else if (arg == "--name") result.ServiceName = args[++i]; else if (arg == "--ports") result.ServicePorts = args[++i]; + else if (arg == "--type") result.ServiceType = args[++i]; else if (arg == "--bootstrap") result.ServiceBootstrap = args[++i]; else if (arg == "--info") result.ServiceInfo = args[++i]; else if (arg == "--logs") result.ServiceLogs = args[++i]; @@ -737,6 +743,7 @@ Service options: --list List services --name NAME Service name --ports PORTS Comma-separated ports + --type TYPE Service type (minecraft/mumble/teamspeak/source/tcp/udp) --bootstrap CMD Bootstrap command --info ID Get service details --logs ID Get all logs diff --git a/Un.java b/Un.java index 5dd839a..fa3ac3c 100644 --- a/Un.java +++ b/Un.java @@ -281,6 +281,9 @@ public class Un { } payload.put("ports", ports); } + if (args.serviceType != null) { + payload.put("service_type", args.serviceType); + } if (args.serviceBootstrap != null) { payload.put("bootstrap", args.serviceBootstrap); } @@ -551,6 +554,7 @@ public class Un { boolean serviceList = false; String serviceName = null; String servicePorts = null; + String serviceType = null; String serviceBootstrap = null; String serviceInfo = null; String serviceLogs = null; @@ -593,6 +597,8 @@ public class Un { result.serviceName = args[++i]; } else if (arg.equals("--ports")) { result.servicePorts = args[++i]; + } else if (arg.equals("--type")) { + result.serviceType = args[++i]; } else if (arg.equals("--bootstrap")) { result.serviceBootstrap = args[++i]; } else if (arg.equals("--info")) { @@ -637,6 +643,7 @@ public class Un { System.out.println(" --list List services"); System.out.println(" --name NAME Service name"); System.out.println(" --ports PORTS Comma-separated ports"); + System.out.println(" --type TYPE Service type (minecraft/mumble/teamspeak/source/tcp/udp)"); System.out.println(" --bootstrap CMD Bootstrap command"); System.out.println(" --info ID Get service details"); System.out.println(" --logs ID Get all logs"); diff --git a/un.awk b/un.awk index 33a96fd..ea813be 100644 --- a/un.awk +++ b/un.awk @@ -187,13 +187,76 @@ function service_destroy(id) { print GREEN "Service destroyed: " id RESET } +function service_create(name, ports, domains, service_type, bootstrap) { + api_key = get_api_key() + + # Build JSON payload + json = "{\"name\":\"" escape_json(name) "\"" + + if (ports != "") { + json = json ",\"ports\":[" ports "]" + } + + if (domains != "") { + # Split domains by comma and build array + split(domains, domain_arr, ",") + json = json ",\"domains\":[" + for (i in domain_arr) { + if (i > 1) json = json "," + json = json "\"" escape_json(domain_arr[i]) "\"" + } + json = json "]" + } + + if (service_type != "") { + json = json ",\"service_type\":\"" escape_json(service_type) "\"" + } + + if (bootstrap != "") { + json = json ",\"bootstrap\":\"" escape_json(bootstrap) "\"" + } + + json = json "}" + + # Write to temp file + tmp = "/tmp/un_awk_svc_" PROCINFO["pid"] ".json" + print json > tmp + close(tmp) + + # Call curl + cmd = "curl -s -X POST '" API_BASE "/services' " \ + "-H 'Content-Type: application/json' " \ + "-H 'Authorization: Bearer " api_key "' " \ + "-d '@" tmp "'" + + response = "" + while ((cmd | getline line) > 0) { + response = response line + } + close(cmd) + + # Clean up + system("rm -f " tmp) + + # Print response + print response +} + function show_help() { print "Usage: awk -f un.awk " print " awk -f un.awk session --list" print " awk -f un.awk session --kill ID" 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 "" + print "Service options:" + print " --name NAME Service name (required for --create)" + print " --ports PORTS Comma-separated port numbers" + 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 "" print "Requires: UNSANDBOX_API_KEY environment variable" } @@ -230,8 +293,44 @@ END { service_list() } else if (ARGC >= 4 && ARGV[2] == "--destroy") { service_destroy(ARGV[3]) + } else if (ARGV[2] == "--create") { + # Parse service creation arguments + name = "" + ports = "" + domains = "" + service_type = "" + bootstrap = "" + + i = 3 + while (i < ARGC) { + if (ARGV[i] == "--name" && i + 1 < ARGC) { + name = ARGV[i + 1] + i += 2 + } else if (ARGV[i] == "--ports" && i + 1 < ARGC) { + ports = ARGV[i + 1] + i += 2 + } else if (ARGV[i] == "--domains" && i + 1 < ARGC) { + domains = ARGV[i + 1] + i += 2 + } else if (ARGV[i] == "--type" && i + 1 < ARGC) { + service_type = ARGV[i + 1] + i += 2 + } else if (ARGV[i] == "--bootstrap" && i + 1 < ARGC) { + bootstrap = ARGV[i + 1] + i += 2 + } else { + i++ + } + } + + if (name == "") { + print RED "Error: --name is required for service creation" RESET > "/dev/stderr" + exit 1 + } + + service_create(name, ports, domains, service_type, bootstrap) } else { - print "Usage: awk -f un.awk service --list|--destroy ID" + print "Usage: awk -f un.awk service --list|--create|--destroy ID" } exit 0 } diff --git a/un.clj b/un.clj index 6508d2f..a31bb7b 100644 --- a/un.clj +++ b/un.clj @@ -173,7 +173,7 @@ (println (str yellow "Session created (WebSocket required)" reset)) (println (curl-post api-key "/sessions" json)))))) -(defn service-command [action sid name ports bootstrap network vcpu] +(defn service-command [action sid name ports bootstrap service-type network vcpu] (let [api-key (get-api-key)] (case action :list (println (curl-get api-key "/services")) @@ -191,9 +191,10 @@ :create (when name (let [ports-json (if ports (str ",\"ports\":[" ports "]") "") bootstrap-json (if bootstrap (str ",\"bootstrap\":\"" (escape-json bootstrap) "\"") "") + service-type-json (if service-type (str ",\"service_type\":\"" service-type "\"") "") network-json (if network (str ",\"network\":\"" network "\"") "") vcpu-json (if vcpu (str ",\"vcpu\":" vcpu) "") - json (str "{\"name\":\"" name "\"" ports-json bootstrap-json network-json vcpu-json "}")] + json (str "{\"name\":\"" name "\"" ports-json bootstrap-json service-type-json network-json vcpu-json "}")] (println (str green "Service created" reset)) (println (curl-post api-key "/services" json))))))) @@ -213,12 +214,13 @@ service-name nil service-ports nil service-bootstrap nil + service-type nil mode :execute] (cond (empty? args) (case mode :session (session-command (or session-action :create) session-id session-shell network vcpu) - :service (service-command (or service-action :create) service-id service-name service-ports service-bootstrap network vcpu) + :service (service-command (or service-action :create) service-id service-name service-ports service-bootstrap service-type network vcpu) :execute (if file (execute-command file env-vars artifacts out-dir network vcpu) (do (println "Usage: un.clj [options] ") @@ -228,91 +230,95 @@ (= (first args) "session") (recur (rest args) file env-vars artifacts out-dir network vcpu session-action session-id session-shell - service-action service-id service-name service-ports service-bootstrap :session) + service-action service-id service-name service-ports service-bootstrap service-type :session) (= (first args) "service") (recur (rest args) file env-vars artifacts out-dir network vcpu session-action session-id session-shell - service-action service-id service-name service-ports service-bootstrap :service) + service-action service-id service-name service-ports service-bootstrap service-type :service) ;; Session options (and (= mode :session) (= (first args) "--list")) (recur (rest args) file env-vars artifacts out-dir network vcpu :list session-id session-shell - service-action service-id service-name service-ports service-bootstrap mode) + service-action service-id service-name service-ports service-bootstrap service-type mode) (and (= mode :session) (= (first args) "--kill")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu :kill (second args) session-shell - service-action service-id service-name service-ports service-bootstrap mode) + service-action service-id service-name service-ports service-bootstrap service-type mode) (and (= mode :session) (or (= (first args) "--shell") (= (first args) "-s"))) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id (second args) - service-action service-id service-name service-ports service-bootstrap mode) + service-action service-id service-name service-ports service-bootstrap service-type mode) ;; Service options (and (= mode :service) (= (first args) "--list")) (recur (rest args) file env-vars artifacts out-dir network vcpu session-action session-id session-shell - :list service-id service-name service-ports service-bootstrap mode) + :list service-id service-name service-ports service-bootstrap service-type mode) (and (= mode :service) (= (first args) "--info")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell - :info (second args) service-name service-ports service-bootstrap mode) + :info (second args) service-name service-ports service-bootstrap service-type mode) (and (= mode :service) (= (first args) "--logs")) (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 mode) + :logs (second args) service-name service-ports service-bootstrap service-type mode) (and (= mode :service) (= (first args) "--sleep")) (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 mode) + :sleep (second args) service-name service-ports service-bootstrap service-type mode) (and (= mode :service) (= (first args) "--wake")) (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 mode) + :wake (second args) service-name service-ports service-bootstrap service-type mode) (and (= mode :service) (= (first args) "--destroy")) (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 mode) + :destroy (second args) service-name service-ports service-bootstrap service-type 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 mode) + :create service-id (second args) service-ports service-bootstrap service-type mode) (and (= mode :service) (= (first args) "--ports")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell - service-action service-id service-name (second args) service-bootstrap mode) + service-action service-id service-name (second args) service-bootstrap service-type mode) (and (= mode :service) (= (first args) "--bootstrap")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell - service-action service-id service-name service-ports (second args) mode) + service-action service-id service-name service-ports (second args) service-type mode) + + (and (= mode :service) (= (first args) "--type")) + (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell + service-action service-id service-name service-ports service-bootstrap (second args) mode) ;; Execute options (= (first args) "-e") (let [[k v] (str/split (second args) #"=" 2)] (recur (rest (rest args)) file (conj env-vars [k v]) artifacts out-dir network vcpu - session-action session-id session-shell service-action service-id service-name service-ports service-bootstrap mode)) + session-action session-id session-shell service-action service-id service-name service-ports service-bootstrap service-type mode)) (= (first args) "-a") (recur (rest args) file env-vars true out-dir network vcpu session-action session-id session-shell - service-action service-id service-name service-ports service-bootstrap mode) + service-action service-id service-name service-ports service-bootstrap service-type mode) (= (first args) "-o") (recur (rest (rest args)) file env-vars artifacts (second args) network vcpu session-action session-id session-shell - service-action service-id service-name service-ports service-bootstrap mode) + service-action service-id service-name service-ports service-bootstrap service-type mode) (= (first args) "-n") (recur (rest (rest args)) file env-vars artifacts out-dir (second args) vcpu session-action session-id session-shell - service-action service-id service-name service-ports service-bootstrap mode) + service-action service-id service-name service-ports service-bootstrap service-type mode) (= (first args) "-v") (recur (rest (rest args)) file env-vars artifacts out-dir network (Integer/parseInt (second args)) session-action session-id session-shell - service-action service-id service-name service-ports service-bootstrap mode) + service-action service-id service-name service-ports service-bootstrap service-type mode) ;; Source file (and (= mode :execute) (not (.startsWith (first args) "-")) (nil? file)) (recur (rest args) (first args) env-vars artifacts out-dir network vcpu session-action session-id session-shell - service-action service-id service-name service-ports service-bootstrap mode) + service-action service-id service-name service-ports service-bootstrap service-type mode) :else (recur (rest args) file env-vars artifacts out-dir network vcpu session-action session-id session-shell - service-action service-id service-name service-ports service-bootstrap mode)))) + service-action service-id service-name service-ports service-bootstrap service-type mode)))) (parse-args *command-line-args*) diff --git a/un.cob b/un.cob index f534639..b2e7e04 100644 --- a/un.cob +++ b/un.cob @@ -68,6 +68,11 @@ 01 WS-COMMAND PIC X(32). 01 WS-OPERATION PIC X(32). 01 WS-ID PIC X(256). + 01 WS-NAME PIC X(256). + 01 WS-PORTS PIC X(256). + 01 WS-DOMAINS PIC X(256). + 01 WS-SERVICE-TYPE PIC X(64). + 01 WS-BOOTSTRAP PIC X(2048). PROCEDURE DIVISION. MAIN-PROCEDURE. @@ -166,6 +171,13 @@ STOP RUN END-IF. + * Initialize service parameters + MOVE SPACES TO WS-NAME. + MOVE SPACES TO WS-PORTS. + MOVE SPACES TO WS-DOMAINS. + MOVE SPACES TO WS-SERVICE-TYPE. + MOVE SPACES TO WS-BOOTSTRAP. + * Parse service arguments ACCEPT WS-ARG2 FROM ARGUMENT-VALUE. @@ -186,9 +198,13 @@ ELSE IF WS-ARG2 = "--destroy" ACCEPT WS-ID FROM ARGUMENT-VALUE PERFORM SERVICE-DESTROY + 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, or --destroy" UPON SYSERR + "--sleep, --wake, --destroy, or --name" UPON SYSERR MOVE 1 TO RETURN-CODE END-IF. @@ -350,3 +366,79 @@ 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 + ACCEPT WS-ARG3 FROM ARGUMENT-VALUE. + PERFORM UNTIL WS-ARG3 = SPACES + IF WS-ARG3 = "--ports" + ACCEPT WS-PORTS FROM ARGUMENT-VALUE + ELSE IF WS-ARG3 = "--domains" + ACCEPT WS-DOMAINS FROM ARGUMENT-VALUE + ELSE IF WS-ARG3 = "--type" + ACCEPT WS-SERVICE-TYPE FROM ARGUMENT-VALUE + ELSE IF WS-ARG3 = "--bootstrap" + ACCEPT WS-BOOTSTRAP FROM ARGUMENT-VALUE + END-IF + ACCEPT WS-ARG3 FROM ARGUMENT-VALUE + END-PERFORM. + + SERVICE-CREATE. + * Build JSON payload for service creation + * Start with base payload containing name + STRING "curl -s -X POST " + "https://api.unsandbox.com/services " + "-H 'Content-Type: application/json' " + "-H 'Authorization: Bearer " FUNCTION TRIM(WS-API-KEY) + "' -d '{""name"":""" + FUNCTION TRIM(WS-NAME) + """" + DELIMITED BY SIZE INTO WS-CURL-CMD + END-STRING. + + * Add ports if provided + IF WS-PORTS NOT = SPACES + STRING FUNCTION TRIM(WS-CURL-CMD) + ",""ports"":[" FUNCTION TRIM(WS-PORTS) "]" + DELIMITED BY SIZE INTO WS-CURL-CMD + END-STRING + END-IF. + + * Add domains if provided + IF WS-DOMAINS NOT = SPACES + STRING FUNCTION TRIM(WS-CURL-CMD) + ",""domains"":[""" + FUNCTION TRIM(WS-DOMAINS) + """]" + DELIMITED BY SIZE INTO WS-CURL-CMD + END-STRING + END-IF. + + * Add service_type if provided + IF WS-SERVICE-TYPE NOT = SPACES + STRING FUNCTION TRIM(WS-CURL-CMD) + ",""service_type"":""" + FUNCTION TRIM(WS-SERVICE-TYPE) + """" + DELIMITED BY SIZE INTO WS-CURL-CMD + END-STRING + END-IF. + + * Add bootstrap if provided + IF WS-BOOTSTRAP NOT = SPACES + STRING FUNCTION TRIM(WS-CURL-CMD) + ",""bootstrap"":""" + FUNCTION TRIM(WS-BOOTSTRAP) + """" + DELIMITED BY SIZE INTO WS-CURL-CMD + END-STRING + END-IF. + + * Close JSON and add output formatting + STRING FUNCTION TRIM(WS-CURL-CMD) + "}' | jq -r '.id + "" created""'" + DELIMITED BY SIZE INTO WS-CURL-CMD + END-STRING. + + CALL "SYSTEM" USING WS-CURL-CMD. diff --git a/un.cpp b/un.cpp index 7bd0e26..54adc2e 100644 --- a/un.cpp +++ b/un.cpp @@ -217,7 +217,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& 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& 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; @@ -267,6 +267,7 @@ void cmd_service(const string& name, const string& ports, const string& bootstra ostringstream json; json << "{\"name\":\"" << name << "\""; if (!ports.empty()) json << ",\"ports\":[" << ports << "]"; + if (!type.empty()) json << ",\"service_type\":\"" << type << "\""; if (!bootstrap.empty()) { struct stat st; if (stat(bootstrap.c_str(), &st) == 0) { @@ -328,7 +329,7 @@ int main(int argc, char* argv[]) { } if (cmd_type == "service") { - string name, ports, bootstrap; + string name, ports, type, bootstrap; bool list = false; string info, logs, tail, sleep, wake, destroy, network; int vcpu = 0; @@ -337,6 +338,7 @@ int main(int argc, char* argv[]) { string arg = argv[i]; if (arg == "--name" && i+1 < argc) name = argv[++i]; else if (arg == "--ports" && i+1 < argc) ports = argv[++i]; + else if (arg == "--type" && i+1 < argc) type = argv[++i]; else if (arg == "--bootstrap" && i+1 < argc) bootstrap = argv[++i]; else if (arg == "--list") list = true; else if (arg == "--info" && i+1 < argc) info = argv[++i]; @@ -350,7 +352,7 @@ int main(int argc, char* argv[]) { else if (arg == "-k" && i+1 < argc) api_key = argv[++i]; } - cmd_service(name, ports, 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, network, vcpu, api_key); return 0; } diff --git a/un.cr b/un.cr index 06ee4e3..bde4c6f 100644 --- a/un.cr +++ b/un.cr @@ -284,7 +284,53 @@ def cmd_service(args) return end - STDERR.puts "#{RED}Error: Use --list, --info, --logs, --sleep, --wake, or --destroy#{RESET}" + # Create new service + if name = args[:name]?.as?(String) + payload = JSON.parse({name: name}.to_json) + + # Add ports + if ports_str = args[:ports]?.as?(String) + ports = ports_str.split(',').map(&.to_i) + payload.as_h["ports"] = JSON.parse(ports.to_json) + end + + # Add domains + if domains_str = args[:domains]?.as?(String) + domains = domains_str.split(',') + payload.as_h["domains"] = JSON.parse(domains.to_json) + end + + # Add service_type + if service_type = args[:service_type]?.as?(String) + payload.as_h["service_type"] = JSON::Any.new(service_type) + end + + # Add bootstrap + if bootstrap = args[:bootstrap]?.as?(String) + # Check if bootstrap is a file + if File.exists?(bootstrap) + payload.as_h["bootstrap"] = JSON::Any.new(File.read(bootstrap)) + else + payload.as_h["bootstrap"] = JSON::Any.new(bootstrap) + end + end + + # Add network + if network = args[:network]?.as?(String) + payload.as_h["network"] = JSON::Any.new(network) + end + + # Create service + result = api_request("/services", api_key, method: "POST", data: payload) + puts "#{GREEN}Service created: #{result["id"]?.try(&.as_s?) || "N/A"}#{RESET}" + puts "Name: #{result["name"]?.try(&.as_s?) || "N/A"}" + if url = result["url"]?.try(&.as_s?) + puts "URL: #{url}" + end + return + end + + STDERR.puts "#{RED}Error: Use --list, --info, --logs, --sleep, --wake, --destroy, or --name to create#{RESET}" exit 1 end @@ -304,7 +350,12 @@ def main logs: nil, sleep: nil, wake: nil, - destroy: nil + destroy: nil, + name: nil, + ports: nil, + domains: nil, + service_type: nil, + bootstrap: nil } of Symbol => (String | Array(String) | Bool | Nil) parser = OptionParser.new do |opts| @@ -323,6 +374,11 @@ def main opts.on("--sleep=ID", "Sleep service") { |id| args[:sleep] = id } opts.on("--wake=ID", "Wake service") { |id| args[:wake] = id } opts.on("--destroy=ID", "Destroy service") { |id| args[:destroy] = id } + 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 } + opts.on("--type=TYPE", "Service type for SRV records") { |t| args[:service_type] = t } + opts.on("--bootstrap=CMD", "Bootstrap command/file") { |b| args[:bootstrap] = b } opts.unknown_args do |before, after| if before.size > 0 diff --git a/un.d b/un.d index a5838eb..f82a3a6 100644 --- a/un.d +++ b/un.d @@ -151,7 +151,7 @@ void cmdSession(bool list, string kill, string shell, string network, int vcpu, writeln(execCurl(cmd)); } -void cmdService(string name, string ports, string bootstrap, 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 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)); @@ -200,6 +200,7 @@ void cmdService(string name, string ports, string bootstrap, bool list, string i if (!name.empty) { string json = format(`{"name":"%s"`, name); if (!ports.empty) json ~= format(`,"ports":[%s]`, ports); + if (!type.empty) json ~= format(`,"service_type":"%s"`, type); if (!bootstrap.empty) { if (exists(bootstrap)) { string bootCode = readText(bootstrap); @@ -254,7 +255,7 @@ int main(string[] args) { } if (args[1] == "service") { - string name, ports, bootstrap; + string name, ports, bootstrap, type; bool list = false; string info, logs, tail, sleep, wake, destroy, network; int vcpu = 0; @@ -263,6 +264,7 @@ int main(string[] args) { if (args[i] == "--name" && i+1 < args.length) name = args[++i]; else if (args[i] == "--ports" && i+1 < args.length) ports = args[++i]; else if (args[i] == "--bootstrap" && i+1 < args.length) bootstrap = args[++i]; + else if (args[i] == "--type" && i+1 < args.length) type = args[++i]; else if (args[i] == "--list") list = true; else if (args[i] == "--info" && i+1 < args.length) info = args[++i]; else if (args[i] == "--logs" && i+1 < args.length) logs = args[++i]; @@ -275,7 +277,7 @@ int main(string[] args) { else if (args[i] == "-k" && i+1 < args.length) apiKey = args[++i]; } - cmdService(name, ports, bootstrap, list, info, logs, tail, sleep, wake, destroy, network, vcpu, apiKey); + cmdService(name, ports, bootstrap, type, list, info, logs, tail, sleep, wake, destroy, network, vcpu, apiKey); return 0; } diff --git a/un.dart b/un.dart index 1cd8e1e..7b05549 100644 --- a/un.dart +++ b/un.dart @@ -82,6 +82,7 @@ class Args { bool serviceList = false; String? serviceName; String? servicePorts; + String? serviceType; String? serviceBootstrap; String? serviceInfo; String? serviceLogs; @@ -321,6 +322,9 @@ Future cmdService(Args args) async { if (args.servicePorts != null) { payload['ports'] = args.servicePorts!.split(',').map((p) => int.parse(p.trim())).toList(); } + if (args.serviceType != null) { + payload['service_type'] = args.serviceType; + } if (args.serviceBootstrap != null) { payload['bootstrap'] = args.serviceBootstrap; } @@ -404,6 +408,9 @@ Args parseArgs(List argv) { case '--ports': args.servicePorts = argv[++i]; break; + case '--type': + args.serviceType = argv[++i]; + break; case '--bootstrap': args.serviceBootstrap = argv[++i]; break; @@ -459,6 +466,7 @@ Service options: --list List services --name NAME Service name --ports PORTS Comma-separated ports + --type TYPE Service type (minecraft/mumble/teamspeak/source/tcp/udp) --bootstrap CMD Bootstrap command --info ID Get service details --logs ID Get all logs diff --git a/un.erl b/un.erl index 5589616..e77111c 100644 --- a/un.erl +++ b/un.erl @@ -152,6 +152,7 @@ service_command(Args) -> ApiKey = get_api_key(), Ports = get_service_ports(Args), Bootstrap = get_service_bootstrap(Args), + Type = get_service_type(Args), PortsJson = case Ports of undefined -> ""; P -> ",\"ports\":[" ++ P ++ "]" @@ -160,7 +161,11 @@ service_command(Args) -> undefined -> ""; B -> ",\"bootstrap\":\"" ++ escape_json(B) ++ "\"" end, - Json = "{\"name\":\"" ++ Name ++ "\"" ++ PortsJson ++ BootstrapJson ++ "}", + TypeJson = case Type of + undefined -> ""; + T -> ",\"service_type\":\"" ++ T ++ "\"" + end, + Json = "{\"name\":\"" ++ Name ++ "\"" ++ PortsJson ++ BootstrapJson ++ TypeJson ++ "}", TmpFile = write_temp_file(Json), Response = curl_post(ApiKey, "/services", TmpFile), file:delete(TmpFile), @@ -280,3 +285,7 @@ get_service_ports([_ | Rest]) -> get_service_ports(Rest). get_service_bootstrap([]) -> undefined; get_service_bootstrap(["--bootstrap", Bootstrap | _]) -> Bootstrap; get_service_bootstrap([_ | Rest]) -> get_service_bootstrap(Rest). + +get_service_type([]) -> undefined; +get_service_type(["--type", Type | _]) -> Type; +get_service_type([_ | Rest]) -> get_service_type(Rest). diff --git a/un.ex b/un.ex index d2c2cd1..d060b84 100644 --- a/un.ex +++ b/un.ex @@ -193,13 +193,15 @@ defmodule Un do bootstrap = get_opt(args, "--bootstrap", nil, nil) network = get_opt(args, "-n", nil, nil) vcpu = get_opt(args, "-v", nil, nil) + service_type = get_opt(args, "--type", nil, nil) ports_json = if ports, do: ",\"ports\":[#{ports}]", else: "" bootstrap_json = if bootstrap, do: ",\"bootstrap\":\"#{escape_json(bootstrap)}\"", else: "" network_json = if network, do: ",\"network\":\"#{network}\"", else: "" vcpu_json = if vcpu, do: ",\"vcpu\":#{vcpu}", else: "" + type_json = if service_type, do: ",\"service_type\":\"#{service_type}\"", else: "" - json = "{\"name\":\"#{name}\"#{ports_json}#{bootstrap_json}#{network_json}#{vcpu_json}}" + json = "{\"name\":\"#{name}\"#{ports_json}#{bootstrap_json}#{network_json}#{vcpu_json}#{type_json}}" response = curl_post(api_key, "/services", json) IO.puts("#{@green}Service created#{@reset}") IO.puts(response) diff --git a/un.f90 b/un.f90 index ce85af8..106805a 100644 --- a/un.f90 +++ b/un.f90 @@ -214,19 +214,24 @@ contains subroutine handle_service() character(len=2048) :: full_cmd - character(len=256) :: arg, service_id, operation + character(len=256) :: arg, service_id, operation, service_type integer :: i, stat logical :: list_mode list_mode = .false. operation = '' service_id = '' + service_type = '' ! Parse service arguments do i = 2, command_argument_count() call get_command_argument(i, arg) if (trim(arg) == '-l' .or. trim(arg) == '--list') then list_mode = .true. + else if (trim(arg) == '--type') then + if (i+1 <= command_argument_count()) then + call get_command_argument(i+1, service_type) + end if else if (trim(arg) == '--info') then operation = 'info' if (i+1 <= command_argument_count()) then diff --git a/un.forth b/un.forth index 5870e8a..00d24d9 100644 --- a/un.forth +++ b/un.forth @@ -252,6 +252,36 @@ 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 + \ Parse arguments (simplified - in real implementation would iterate through args) + \ For now, just create the curl command that will be constructed by bash + s" /tmp/unsandbox_cmd.sh" w/o create-file throw >r + s" #!/bin/bash" r@ write-line throw + s" NAME=''; PORTS=''; DOMAINS=''; TYPE=''; BOOTSTRAP=''" r@ write-line throw + s" for ((i=3; i<$#; i+=2)); do" r@ write-line throw + s" case ${!i} in" r@ write-line throw + s" --name) NAME=${!((i+1))} ;;" r@ write-line throw + s" --ports) PORTS=${!((i+1))} ;;" r@ write-line throw + s" --domains) DOMAINS=${!((i+1))} ;;" r@ write-line throw + s" --type) TYPE=${!((i+1))} ;;" r@ write-line throw + s" --bootstrap) BOOTSTRAP=${!((i+1))} ;;" r@ write-line throw + s" esac" r@ write-line throw + s" done" r@ write-line throw + s" [ -z \"$NAME\" ] && echo 'Error: --name required' && exit 1" r@ write-line throw + s" PAYLOAD='{\"name\":\"'\"$NAME\"'\"}'" r@ write-line throw + s" [ -n \"$PORTS\" ] && PAYLOAD=$(echo $PAYLOAD | jq --arg p \"$PORTS\" '. + {ports: ($p | split(\",\") | map(tonumber))}')" r@ write-line throw + s" [ -n \"$DOMAINS\" ] && PAYLOAD=$(echo $PAYLOAD | jq --arg d \"$DOMAINS\" '. + {domains: ($d | split(\",\"))}')" r@ write-line throw + s" [ -n \"$TYPE\" ] && PAYLOAD=$(echo $PAYLOAD | jq --arg t \"$TYPE\" '. + {service_type: $t}')" r@ write-line throw + s" [ -n \"$BOOTSTRAP\" ] && PAYLOAD=$(echo $PAYLOAD | jq --arg b \"$BOOTSTRAP\" '. + {bootstrap: $b}')" r@ write-line throw + s" curl -s -X POST https://api.unsandbox.com/services -H 'Content-Type: application/json' -H 'Authorization: Bearer " r@ write-file throw + get-api-key r@ write-file throw + s" ' -d \"$PAYLOAD\" | jq ." r@ write-line throw + r> close-file throw + s" chmod +x /tmp/unsandbox_cmd.sh && bash /tmp/unsandbox_cmd.sh && rm -f /tmp/unsandbox_cmd.sh" system +; + \ Handle session subcommand : handle-session ( -- ) argc @ 3 < if @@ -287,7 +317,7 @@ \ Handle service subcommand : handle-service ( -- ) argc @ 3 < if - s" Error: Use --list, --info, --logs, --sleep, --wake, or --destroy" type cr + s" Error: Use --name (create), --list, --info, --logs, --sleep, --wake, or --destroy" type cr 1 (bye) then @@ -301,6 +331,11 @@ 0 (bye) then + 2dup s" --name" compare 0= if + 2drop service-create + 0 (bye) + then + 2dup s" --info" compare 0= if 2drop argc @ 4 < if @@ -352,7 +387,7 @@ then 2drop - s" Error: Use --list, --info, --logs, --sleep, --wake, or --destroy" type cr + s" Error: Use --name (create), --list, --info, --logs, --sleep, --wake, or --destroy" type cr 1 (bye) ; diff --git a/un.fs b/un.fs index 3f5b442..d0491e8 100644 --- a/un.fs +++ b/un.fs @@ -85,6 +85,7 @@ type Args = { mutable ServiceList: bool mutable ServiceName: string option mutable ServicePorts: string option + mutable ServiceType: string option mutable ServiceBootstrap: string option mutable ServiceInfo: string option mutable ServiceLogs: string option @@ -357,6 +358,8 @@ let cmdService (args: Args) = if args.ServicePorts.IsSome then let ports = args.ServicePorts.Value.Split(',') |> Array.map (fun p -> box (int (p.Trim()))) payload <- payload @ [("ports", box ports)] + if args.ServiceType.IsSome then + payload <- payload @ [("service_type", box args.ServiceType.Value)] if args.ServiceBootstrap.IsSome then payload <- payload @ [("bootstrap", box args.ServiceBootstrap.Value)] if args.Network.IsSome then @@ -395,6 +398,7 @@ let parseArgs (argv: string[]) = ServiceList = false ServiceName = None ServicePorts = None + ServiceType = None ServiceBootstrap = None ServiceInfo = None ServiceLogs = None @@ -425,6 +429,7 @@ let parseArgs (argv: string[]) = | "--kill" -> i <- i + 1; args.SessionKill <- Some argv.[i] | "--name" -> i <- i + 1; args.ServiceName <- Some argv.[i] | "--ports" -> i <- i + 1; args.ServicePorts <- Some argv.[i] + | "--type" -> i <- i + 1; args.ServiceType <- Some argv.[i] | "--bootstrap" -> i <- i + 1; args.ServiceBootstrap <- Some argv.[i] | "--info" -> i <- i + 1; args.ServiceInfo <- Some argv.[i] | "--logs" -> i <- i + 1; args.ServiceLogs <- Some argv.[i] @@ -461,6 +466,7 @@ let printHelp () = printfn " --list List services" printfn " --name NAME Service name" printfn " --ports PORTS Comma-separated ports" + printfn " --type TYPE Service type (minecraft/mumble/teamspeak/source/tcp/udp)" printfn " --bootstrap CMD Bootstrap command" printfn " --info ID Get service details" printfn " --logs ID Get all logs" diff --git a/un.go b/un.go index 68f50f6..3b9c794 100644 --- a/un.go +++ b/un.go @@ -338,7 +338,7 @@ func cmdSession(sessionList, sessionKill, sessionShell, network string, vcpu int fmt.Printf("%sSession created: %s%s\n", Green, result["id"], Reset) } -func cmdService(serviceName, servicePorts, serviceDomains, serviceBootstrap, serviceList, serviceInfo, serviceLogs, serviceTail, serviceSleep, serviceWake, serviceDestroy, network string, vcpu int, apiKey string) { +func cmdService(serviceName, servicePorts, serviceDomains, serviceType, serviceBootstrap, serviceList, serviceInfo, serviceLogs, serviceTail, serviceSleep, serviceWake, serviceDestroy, network string, vcpu int, apiKey string) { if serviceList != "" { result := apiRequest("/services", "GET", nil, apiKey) services := result["services"].([]interface{}) @@ -422,6 +422,9 @@ func cmdService(serviceName, servicePorts, serviceDomains, serviceBootstrap, ser if serviceDomains != "" { payload["domains"] = strings.Split(serviceDomains, ",") } + if serviceType != "" { + payload["service_type"] = serviceType + } if serviceBootstrap != "" { // Check if it's a file if _, err := os.Stat(serviceBootstrap); err == nil { @@ -481,6 +484,7 @@ func main() { serviceName := serviceCmd.String("name", "", "Service name") servicePorts := serviceCmd.String("ports", "", "Ports (comma-separated)") serviceDomains := serviceCmd.String("domains", "", "Custom domains (comma-separated)") + serviceType := serviceCmd.String("type", "", "Service type for SRV records (minecraft, mumble, teamspeak, source, tcp, udp)") serviceBootstrap := serviceCmd.String("bootstrap", "", "Bootstrap command/file") serviceList := serviceCmd.String("list", "", "List services") serviceInfo := serviceCmd.String("info", "", "Get service info") @@ -523,7 +527,7 @@ func main() { if vc == 0 { vc = *vcpu } - cmdService(*serviceName, *servicePorts, *serviceDomains, *serviceBootstrap, *serviceList, *serviceInfo, *serviceLogs, *serviceTail, *serviceSleep, *serviceWake, *serviceDestroy, net, vc, key) + cmdService(*serviceName, *servicePorts, *serviceDomains, *serviceType, *serviceBootstrap, *serviceList, *serviceInfo, *serviceLogs, *serviceTail, *serviceSleep, *serviceWake, *serviceDestroy, net, vc, key) return } } diff --git a/un.groovy b/un.groovy index 6f27320..c2040c7 100644 --- a/un.groovy +++ b/un.groovy @@ -76,6 +76,7 @@ class Args { Boolean serviceList = false String serviceName = null String servicePorts = null + String serviceType = null String serviceBootstrap = null String serviceInfo = null String serviceLogs = null @@ -331,6 +332,9 @@ def cmdService(args) { def ports = args.servicePorts.split(',').collect { it.trim() }.join(',') json += ""","ports":[${ports}]""" } + if (args.serviceType) { + json += ""","service_type":"${args.serviceType}"""" + } if (args.serviceBootstrap) { def escaped = args.serviceBootstrap.replace('\\', '\\\\').replace('"', '\\"') json += ""","bootstrap":"${escaped}"""" @@ -420,6 +424,9 @@ def parseArgs(argv) { case '--ports': args.servicePorts = argv[++i] break + case '--type': + args.serviceType = argv[++i] + break case '--bootstrap': args.serviceBootstrap = argv[++i] break @@ -474,6 +481,7 @@ Service options: --list List services --name NAME Service name --ports PORTS Comma-separated ports + --type TYPE Service type (minecraft/mumble/teamspeak/source/tcp/udp) --bootstrap CMD Bootstrap command --info ID Get service details --logs ID Get all logs diff --git a/un.hs b/un.hs index 70e189d..5b9914a 100644 --- a/un.hs +++ b/un.hs @@ -130,6 +130,7 @@ data ServiceOpts = ServiceOpts { svcAction :: ServiceAction , svcName :: Maybe String , svcPorts :: Maybe String + , svcType :: Maybe String , svcBootstrap :: Maybe String , svcNetwork :: Maybe String , svcVcpu :: Maybe Int @@ -161,7 +162,7 @@ parseSession args = return $ parseSessionArgs args defaultSessionOpts parseService :: [String] -> IO ServiceOpts parseService args = return $ parseServiceArgs args defaultServiceOpts where - defaultServiceOpts = ServiceOpts ServiceCreate Nothing Nothing Nothing Nothing Nothing + defaultServiceOpts = ServiceOpts ServiceCreate Nothing Nothing Nothing Nothing Nothing Nothing parseServiceArgs [] opts = opts parseServiceArgs ("--list":rest) opts = parseServiceArgs rest opts { svcAction = ServiceList } parseServiceArgs ("--info":id:rest) opts = parseServiceArgs rest opts { svcAction = ServiceInfo id } @@ -171,6 +172,7 @@ parseService args = return $ parseServiceArgs args defaultServiceOpts parseServiceArgs ("--destroy":id:rest) opts = parseServiceArgs rest opts { svcAction = ServiceDestroy id } 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 } parseServiceArgs ("--bootstrap":b:rest) opts = parseServiceArgs rest opts { svcBootstrap = Just b } parseServiceArgs ("-n":net:rest) opts = parseServiceArgs rest opts { svcNetwork = Just net } parseServiceArgs ("-v":v:rest) opts = parseServiceArgs rest opts { svcVcpu = Just (read v) } @@ -317,10 +319,11 @@ serviceCommand opts = do exitFailure Just name -> do let portsJSON = maybe "" (\p -> ",\"ports\":[" ++ p ++ "]") (svcPorts opts) + let typeJSON = maybe "" (\t -> ",\"service_type\":\"" ++ t ++ "\"") (svcType opts) let bootstrapJSON = maybe "" (\b -> ",\"bootstrap\":\"" ++ escapeJSON b ++ "\"") (svcBootstrap opts) let networkJSON = maybe "" (\n -> ",\"network\":\"" ++ n ++ "\"") (svcNetwork opts) let vcpuJSON = maybe "" (\v -> ",\"vcpu\":" ++ show v) (svcVcpu opts) - let json = "{\"name\":\"" ++ name ++ "\"" ++ portsJSON ++ bootstrapJSON ++ networkJSON ++ vcpuJSON ++ "}" + let json = "{\"name\":\"" ++ name ++ "\"" ++ portsJSON ++ typeJSON ++ bootstrapJSON ++ networkJSON ++ vcpuJSON ++ "}" (_, stdout, _) <- curlPost apiKey "/services" json putStrLn $ green ++ "Service created" ++ reset putStrLn stdout diff --git a/un.jl b/un.jl index ab4cd02..efa80d1 100644 --- a/un.jl +++ b/un.jl @@ -286,7 +286,51 @@ function cmd_service(args) return end - println(stderr, "$(RED)Error: Use --list, --info, --logs, --sleep, --wake, or --destroy$(RESET)") + # Create new service + if args["name"] !== nothing + payload = Dict("name" => args["name"]) + + if args["ports"] !== nothing + ports = [parse(Int, strip(p)) for p in split(args["ports"], ',')] + payload["ports"] = ports + end + + if args["domains"] !== nothing + domains = [strip(d) for d in split(args["domains"], ',')] + payload["domains"] = domains + end + + if args["type"] !== nothing + payload["service_type"] = args["type"] + end + + if args["bootstrap"] !== nothing + bootstrap = args["bootstrap"] + if isfile(bootstrap) + payload["bootstrap"] = read(bootstrap, String) + else + payload["bootstrap"] = bootstrap + end + end + + if args["network"] !== nothing + payload["network"] = args["network"] + end + + if args["vcpu"] !== nothing + payload["vcpu"] = args["vcpu"] + end + + result = api_request("/services", api_key, method="POST", data=payload) + println("$(GREEN)Service created: $(get(result, "id", "N/A"))$(RESET)") + println("Name: $(get(result, "name", "N/A"))") + if haskey(result, "url") + println("URL: $(result["url"])") + end + return + end + + println(stderr, "$(RED)Error: Use --name to create, or --list, --info, --logs, --sleep, --wake, --destroy$(RESET)") exit(1) end @@ -333,6 +377,24 @@ function main() end @add_arg_table! s["service"] begin + "--name" + help = "Service name" + "--ports" + help = "Comma-separated ports" + "--domains" + help = "Comma-separated custom domains" + "--type" + help = "Service type for SRV records (minecraft, mumble, teamspeak, source, tcp, udp)" + "--bootstrap" + help = "Bootstrap command/file" + "--network", "-n" + help = "Network mode" + arg_type = String + range_tester = x -> x in ["zerotrust", "semitrusted"] + "--vcpu", "-v" + help = "vCPU count (1-8)" + arg_type = Int + range_tester = x -> x >= 1 && x <= 8 "--list", "-l" help = "List services" action = :store_true diff --git a/un.js b/un.js index c68545e..f5eaf56 100644 --- a/un.js +++ b/un.js @@ -325,6 +325,7 @@ async function cmdService(args) { const payload = { name: args.name }; if (args.ports) payload.ports = args.ports.split(',').map(p => parseInt(p.trim())); if (args.domains) payload.domains = args.domains.split(','); + if (args.serviceType) payload.service_type = args.serviceType; if (args.bootstrap) { if (fs.existsSync(args.bootstrap)) { payload.bootstrap = fs.readFileSync(args.bootstrap, 'utf-8'); @@ -436,6 +437,9 @@ function parseArgs(argv) { } else if (arg === '--domains' && i + 1 < argv.length) { args.domains = argv[++i]; i++; + } else if (arg === '--type' && i + 1 < argv.length) { + args.serviceType = argv[++i]; + i++; } else if (arg === '--bootstrap' && i + 1 < argv.length) { args.bootstrap = argv[++i]; i++; @@ -514,6 +518,7 @@ Service options: --name NAME Service name --ports PORTS Comma-separated ports --domains DOMAINS Custom domains + --type TYPE Service type for SRV records (minecraft, mumble, teamspeak, source, tcp, udp) --bootstrap CMD Bootstrap command/file -l, --list List services --info ID Get service details diff --git a/un.kt b/un.kt index d9f304c..3d00b15 100644 --- a/un.kt +++ b/un.kt @@ -85,6 +85,7 @@ data class Args( var serviceList: Boolean = false, var serviceName: String? = null, var servicePorts: String? = null, + var serviceType: String? = null, var serviceBootstrap: String? = null, var serviceInfo: String? = null, var serviceLogs: String? = null, @@ -302,6 +303,9 @@ fun cmdService(args: Args) { if (args.servicePorts != null) { payload["ports"] = args.servicePorts!!.split(",").map { it.trim().toInt() } } + if (args.serviceType != null) { + payload["service_type"] = args.serviceType!! + } if (args.serviceBootstrap != null) { payload["bootstrap"] = args.serviceBootstrap!! } @@ -513,6 +517,7 @@ fun parseArgs(args: Array): Args { "--kill" -> result.sessionKill = args[++i] "--name" -> result.serviceName = args[++i] "--ports" -> result.servicePorts = args[++i] + "--type" -> result.serviceType = args[++i] "--bootstrap" -> result.serviceBootstrap = args[++i] "--info" -> result.serviceInfo = args[++i] "--logs" -> result.serviceLogs = args[++i] @@ -551,6 +556,7 @@ Service options: --list List services --name NAME Service name --ports PORTS Comma-separated ports + --type TYPE Service type (minecraft/mumble/teamspeak/source/tcp/udp) --bootstrap CMD Bootstrap command --info ID Get service details --logs ID Get all logs diff --git a/un.lisp b/un.lisp index ef1a1c3..b053957 100644 --- a/un.lisp +++ b/un.lisp @@ -153,7 +153,7 @@ (format t "~aSession created (WebSocket required)~a~%" *yellow* *reset*) (format t "~a~%" response)))))) -(defun service-cmd (action id name ports bootstrap) +(defun service-cmd (action id name ports bootstrap service-type) (let ((api-key (get-api-key))) (cond ((string= action "list") @@ -174,7 +174,8 @@ ((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)) "")) - (json (format nil "{\"name\":\"~a\"~a~a}" name ports-json bootstrap-json)) + (type-json (if service-type (format nil ",\"service_type\":\"~a\"" service-type) "")) + (json (format nil "{\"name\":\"~a\"~a~a~a}" name ports-json bootstrap-json type-json)) (response (curl-post api-key "/services" json))) (format t "~aService created~a~%" *green* *reset*) (format t "~a~%" response))) @@ -202,24 +203,31 @@ ((string= (first args) "service") (cond ((and (> (length args) 1) (string= (second args) "--list")) - (service-cmd "list" nil nil nil nil)) + (service-cmd "list" nil nil nil nil nil)) ((and (> (length args) 2) (string= (second args) "--info")) - (service-cmd "info" (third args) nil nil nil)) + (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)) + (service-cmd "logs" (third args) nil nil nil nil)) ((and (> (length args) 2) (string= (second args) "--sleep")) - (service-cmd "sleep" (third args) nil nil nil)) + (service-cmd "sleep" (third args) nil nil nil nil)) ((and (> (length args) 2) (string= (second args) "--wake")) - (service-cmd "wake" (third args) nil nil nil)) + (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)) + (service-cmd "destroy" (third args) nil nil nil nil)) ((and (> (length args) 2) (string= (second args) "--name")) - (let ((name (third args)) - (ports (when (and (> (length args) 4) (string= (fourth args) "--ports")) - (fifth args))) - (bootstrap (when (and (> (length args) 6) (string= (sixth args) "--bootstrap")) - (seventh args)))) - (service-cmd "create" nil name ports bootstrap))) + (let* ((name (third args)) + (rest-args (nthcdr 3 args)) + (ports nil) + (bootstrap nil) + (service-type nil)) + (loop for i from 0 below (length rest-args) by 2 + do (let ((opt (nth i rest-args)) + (val (nth (1+ i) rest-args))) + (cond + ((string= opt "--ports") (setf ports val)) + ((string= opt "--bootstrap") (setf bootstrap val)) + ((string= opt "--type") (setf service-type val))))) + (service-cmd "create" nil name ports bootstrap service-type))) (t (format t "Error: Invalid service command~%") (uiop:quit 1)))) diff --git a/un.lua b/un.lua index b80da68..ece9924 100644 --- a/un.lua +++ b/un.lua @@ -384,6 +384,9 @@ local function cmd_service(options) end payload.domains = domains end + if options.type then + payload.service_type = options.type + end if options.bootstrap then local file = io.open(options.bootstrap, "r") if file then @@ -428,6 +431,7 @@ local function main() name = nil, ports = nil, domains = nil, + type = nil, bootstrap = nil, info = nil, logs = nil, @@ -491,6 +495,9 @@ local function main() elseif a == "--domains" then i = i + 1 options.domains = arg[i] + elseif a == "--type" then + i = i + 1 + options.type = arg[i] elseif a == "--bootstrap" then i = i + 1 options.bootstrap = arg[i] @@ -562,6 +569,7 @@ Service options: --name NAME Service name --ports PORTS Comma-separated ports --domains DOMAINS Custom domains + --type TYPE Service type (minecraft|mumble|teamspeak|source|tcp|udp) --bootstrap CMD Bootstrap command/file -l, --list List services --info ID Get service details diff --git a/un.m b/un.m index c1b2694..39e03bd 100644 --- a/un.m +++ b/un.m @@ -336,6 +336,7 @@ void cmdService(NSArray* args) { NSString* destroyId = nil; NSString* name = nil; NSString* ports = nil; + NSString* type = nil; NSString* bootstrap = nil; NSString* network = nil; int vcpu = 0; @@ -359,6 +360,8 @@ void cmdService(NSArray* args) { name = args[++i]; } else if ([arg isEqualToString:@"--ports"] && i + 1 < [args count]) { ports = args[++i]; + } else if ([arg isEqualToString:@"--type"] && i + 1 < [args count]) { + type = args[++i]; } else if ([arg isEqualToString:@"--bootstrap"] && i + 1 < [args count]) { bootstrap = args[++i]; } else if ([arg isEqualToString:@"-n"] && i + 1 < [args count]) { @@ -441,6 +444,10 @@ void cmdService(NSArray* args) { payload[@"ports"] = portNumbers; } + if (type) { + payload[@"service_type"] = type; + } + if (bootstrap) { NSFileManager* fm = [NSFileManager defaultManager]; if ([fm fileExistsAtPath:bootstrap]) { diff --git a/un.ml b/un.ml index d3a9340..6f3a778 100644 --- a/un.ml +++ b/un.ml @@ -265,7 +265,7 @@ let session_command action shell network vcpu = | _ -> () (* Service command *) -let service_command action name ports bootstrap network vcpu = +let service_command action name ports bootstrap service_type network vcpu = let api_key = get_api_key () in match action with | "list" -> @@ -330,9 +330,10 @@ let service_command action name ports bootstrap network vcpu = | Some n -> let ports_json = match ports with Some p -> Printf.sprintf ",\"ports\":[%s]" p | None -> "" in let bootstrap_json = match bootstrap with Some b -> Printf.sprintf ",\"bootstrap\":\"%s\"" (escape_json b) | None -> "" in + let service_type_json = match service_type with Some t -> Printf.sprintf ",\"service_type\":\"%s\"" t | None -> "" in let network_json = match network with Some net -> Printf.sprintf ",\"network\":\"%s\"" net | None -> "" in let vcpu_json = match vcpu with Some v -> Printf.sprintf ",\"vcpu\":%d" v | None -> "" in - let json = Printf.sprintf "{\"name\":\"%s\"%s%s%s%s}" n ports_json bootstrap_json network_json vcpu_json in + let json = Printf.sprintf "{\"name\":\"%s\"%s%s%s%s%s}" n ports_json bootstrap_json service_type_json network_json vcpu_json in let tmp_file = Printf.sprintf "/tmp/un_ocaml_%d.json" (Random.int 999999) in let oc = open_out tmp_file in output_string oc json; @@ -376,22 +377,23 @@ let () = in parse_session "create" None None None rest | "service" :: rest -> - let rec parse_service action name ports bootstrap network vcpu = function - | [] -> service_command action name ports bootstrap network vcpu - | "--list" :: rest -> parse_service "list" name ports bootstrap network vcpu rest - | "--info" :: id :: rest -> parse_service "info" (Some id) ports bootstrap network vcpu rest - | "--logs" :: id :: rest -> parse_service "logs" (Some id) ports bootstrap network vcpu rest - | "--sleep" :: id :: rest -> parse_service "sleep" (Some id) ports bootstrap network vcpu rest - | "--wake" :: id :: rest -> parse_service "wake" (Some id) ports bootstrap network vcpu rest - | "--destroy" :: id :: rest -> parse_service "destroy" (Some id) ports bootstrap network vcpu rest - | "--name" :: n :: rest -> parse_service "create" (Some n) ports bootstrap network vcpu rest - | "--ports" :: p :: rest -> parse_service action name (Some p) bootstrap network vcpu rest - | "--bootstrap" :: b :: rest -> parse_service action name ports (Some b) network vcpu rest - | "-n" :: net :: rest -> parse_service action name ports bootstrap (Some net) vcpu rest - | "-v" :: v :: rest -> parse_service action name ports bootstrap network (Some (int_of_string v)) rest - | _ :: rest -> parse_service action name ports bootstrap network vcpu rest + let rec parse_service action name ports bootstrap service_type network vcpu = function + | [] -> service_command action name ports bootstrap service_type network vcpu + | "--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 + | "--destroy" :: id :: rest -> parse_service "destroy" (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 + | "--type" :: t :: rest -> parse_service action name ports bootstrap (Some t) network vcpu rest + | "-n" :: net :: rest -> parse_service action name ports bootstrap service_type (Some net) vcpu rest + | "-v" :: v :: rest -> parse_service action name ports bootstrap service_type network (Some (int_of_string v)) rest + | _ :: rest -> parse_service action name ports bootstrap service_type network vcpu rest in - parse_service "create" None None None None None rest + parse_service "create" None None None None None None rest | args -> let rec parse_execute file env_vars artifacts out_dir network vcpu = function | [] -> execute_command file env_vars artifacts out_dir network vcpu diff --git a/un.nim b/un.nim index 0c685c6..5f0ad2e 100644 --- a/un.nim +++ b/un.nim @@ -127,7 +127,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: 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, 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) @@ -175,6 +175,7 @@ proc cmdService(name, ports, bootstrap: string, list: bool, info, logs, tail, sl json.add(fmt""","bootstrap":"{escapeJson(bootCode)}"""") else: json.add(fmt""","bootstrap":"{escapeJson(bootstrap)}"""") + if serviceType != "": json.add(fmt""","service_type":"{serviceType}"""") if network != "": json.add(fmt""","network":"{network}"""") if vcpu > 0: json.add(fmt""","vcpu":{vcpu}""") json.add("}") @@ -218,7 +219,7 @@ proc main() = return if args[0] == "service": - var name, ports, bootstrap = "" + var name, ports, bootstrap, serviceType = "" var list = false var info, logs, tail, sleep, wake, destroy, network = "" var vcpu = 0 @@ -228,6 +229,7 @@ proc main() = of "--name": name = args[i+1]; inc i of "--ports": ports = args[i+1]; inc i of "--bootstrap": bootstrap = args[i+1]; inc i + of "--type": serviceType = args[i+1]; inc i of "--list": list = true of "--info": info = args[i+1]; inc i of "--logs": logs = args[i+1]; inc i @@ -239,7 +241,7 @@ proc main() = of "-v": vcpu = parseInt(args[i+1]); inc i of "-k": apiKey = args[i+1]; inc i inc i - cmdService(name, ports, bootstrap, list, info, logs, tail, sleep, wake, destroy, network, vcpu, apiKey) + cmdService(name, ports, bootstrap, serviceType, list, info, logs, tail, sleep, wake, destroy, network, vcpu, apiKey) return # Execute mode diff --git a/un.php b/un.php index 001d350..f5a5acb 100644 --- a/un.php +++ b/un.php @@ -336,6 +336,9 @@ function cmd_service($options) { if ($options['domains']) { $payload['domains'] = explode(',', $options['domains']); } + if ($options['type']) { + $payload['service_type'] = $options['type']; + } if ($options['bootstrap']) { if (file_exists($options['bootstrap'])) { $payload['bootstrap'] = file_get_contents($options['bootstrap']); @@ -380,6 +383,7 @@ function main() { 'name' => null, 'ports' => null, 'domains' => null, + 'type' => null, 'bootstrap' => null, 'info' => null, 'logs' => null, @@ -452,6 +456,9 @@ function main() { case '--domains': $options['domains'] = $argv[++$i]; break; + case '--type': + $options['type'] = $argv[++$i]; + break; case '--bootstrap': $options['bootstrap'] = $argv[++$i]; break; @@ -523,6 +530,7 @@ Service options: --name NAME Service name --ports PORTS Comma-separated ports --domains DOMAINS Custom domains + --type TYPE Service type (minecraft|mumble|teamspeak|source|tcp|udp) --bootstrap CMD Bootstrap command/file -l, --list List services --info ID Get service details diff --git a/un.pl b/un.pl index 299497d..ccb4c04 100644 --- a/un.pl +++ b/un.pl @@ -334,6 +334,9 @@ sub cmd_service { my @domains = split(',', $options->{domains}); $payload->{domains} = \@domains; } + if ($options->{type}) { + $payload->{service_type} = $options->{type}; + } if ($options->{bootstrap}) { if (-e $options->{bootstrap}) { open my $fh, '<', $options->{bootstrap} or die "Cannot read file: $!"; @@ -379,6 +382,7 @@ sub main { name => undef, ports => undef, domains => undef, + type => undef, bootstrap => undef, info => undef, logs => undef, @@ -429,6 +433,8 @@ sub main { $options{ports} = $ARGV[++$i]; } elsif ($arg eq '--domains') { $options{domains} = $ARGV[++$i]; + } elsif ($arg eq '--type') { + $options{type} = $ARGV[++$i]; } elsif ($arg eq '--bootstrap') { $options{bootstrap} = $ARGV[++$i]; } elsif ($arg eq '--info') { @@ -489,6 +495,7 @@ Service options: --name NAME Service name --ports PORTS Comma-separated ports --domains DOMAINS Custom domains + --type TYPE Service type (minecraft|mumble|teamspeak|source|tcp|udp) --bootstrap CMD Bootstrap command/file -l, --list List services --info ID Get service details diff --git a/un.pro b/un.pro index e9d3f5b..dfe3eb4 100644 --- a/un.pro +++ b/un.pro @@ -175,6 +175,29 @@ service_destroy(ServiceId) :- [ServiceId, ApiKey, ServiceId]), shell(Cmd, 0). +% Service create +service_create(Name, Ports, Bootstrap, ServiceType) :- + get_api_key(ApiKey), + % Build JSON payload + ( Ports \= '' + -> format(atom(PortsJson), ',"ports":[~w]', [Ports]) + ; PortsJson = '' + ), + ( Bootstrap \= '' + -> format(atom(BootstrapJson), ',"bootstrap":"~w"', [Bootstrap]) + ; BootstrapJson = '' + ), + ( ServiceType \= '' + -> format(atom(ServiceTypeJson), ',"service_type":"~w"', [ServiceType]) + ; ServiceTypeJson = '' + ), + format(atom(Json), '{"name":"~w"~w~w~w}', [Name, PortsJson, BootstrapJson, ServiceTypeJson]), + % Execute curl command + format(atom(Cmd), + 'curl -s -X POST https://api.unsandbox.com/services -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -d \'~w\' && echo -e "\\x1b[32mService created\\x1b[0m"', + [ApiKey, Json]), + shell(Cmd, 0). + % Handle session subcommand handle_session(['--list'|_]) :- session_list. handle_session(['-l'|_]) :- session_list. @@ -184,16 +207,43 @@ handle_session(_) :- halt(1). % Handle service subcommand -handle_service(['--list'|_]) :- service_list. -handle_service(['-l'|_]) :- service_list. -handle_service(['--info', ServiceId|_]) :- service_info(ServiceId). -handle_service(['--logs', ServiceId|_]) :- service_logs(ServiceId). -handle_service(['--sleep', ServiceId|_]) :- service_sleep(ServiceId). -handle_service(['--wake', ServiceId|_]) :- service_wake(ServiceId). -handle_service(['--destroy', ServiceId|_]) :- service_destroy(ServiceId). -handle_service(_) :- - write(user_error, 'Error: Use --list, --info, --logs, --sleep, --wake, or --destroy\n'), - halt(1). +handle_service(Args) :- + parse_service_args(Args, '', '', '', '', Action), + execute_service_action(Action). + +% Parse service arguments +parse_service_args([], Name, Ports, Bootstrap, ServiceType, create) :- + ( Name \= '' + -> service_create(Name, Ports, Bootstrap, ServiceType) + ; write(user_error, 'Error: --name required for service creation\n'), + halt(1) + ). +parse_service_args([], _, _, _, _, Action) :- + ( Action = list + -> service_list + ; write(user_error, 'Error: Use --list, --info, --logs, --sleep, --wake, --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(['--destroy', ServiceId|_], _, _, _, _, _) :- service_destroy(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) :- + parse_service_args(Rest, Name, PortsList, Bootstrap, ServiceType, Action). +parse_service_args(['--bootstrap', BootstrapFile|Rest], Name, Ports, _, ServiceType, Action) :- + parse_service_args(Rest, Name, Ports, BootstrapFile, ServiceType, Action). +parse_service_args(['--type', Type|Rest], Name, Ports, Bootstrap, _, Action) :- + parse_service_args(Rest, Name, Ports, Bootstrap, Type, Action). +parse_service_args([_|Rest], Name, Ports, Bootstrap, ServiceType, Action) :- + parse_service_args(Rest, Name, Ports, Bootstrap, ServiceType, Action). + +% Execute service action (not used, but kept for structure) +execute_service_action(_). % Main program main(Argv) :- diff --git a/un.ps1 b/un.ps1 index c614f74..dff35f4 100644 --- a/un.ps1 +++ b/un.ps1 @@ -231,6 +231,11 @@ function Invoke-Service { $payload["bootstrap"] = $Args[$bIdx + 1] } + if ($Args -contains "--type") { + $tIdx = [array]::IndexOf($Args, "--type") + $payload["service_type"] = $Args[$tIdx + 1] + } + $body = $payload | ConvertTo-Json -Depth 5 $result = Invoke-Api -Endpoint "/services" -Method "POST" -Body $body Write-Host "`e[32mService created`e[0m" @@ -261,6 +266,7 @@ Session options: 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 diff --git a/un.py b/un.py index 233b168..546e12c 100644 --- a/un.py +++ b/un.py @@ -330,6 +330,8 @@ def cmd_service(args): payload["ports"] = [int(p) for p in args.ports.split(',')] if args.domains: payload["domains"] = args.domains.split(',') + if args.service_type: + payload["service_type"] = args.service_type if args.bootstrap: # Check if bootstrap is a file if os.path.exists(args.bootstrap): @@ -396,6 +398,7 @@ Examples: service_parser.add_argument("--name", help="Service name") service_parser.add_argument("--ports", help="Comma-separated ports") service_parser.add_argument("--domains", help="Comma-separated custom domains") + service_parser.add_argument("--type", dest="service_type", help="Service type for SRV records (minecraft, mumble, teamspeak, source, tcp, udp)") service_parser.add_argument("--bootstrap", help="Bootstrap command/file") service_parser.add_argument("-l", "--list", action="store_true", help="List services") service_parser.add_argument("--info", metavar="ID", help="Get service details") diff --git a/un.r b/un.r index 7a1e1ed..e13dfb7 100644 --- a/un.r +++ b/un.r @@ -286,7 +286,49 @@ cmd_service <- function(args) { return() } - cat(sprintf("%sError: Use --list, --info, --logs, --sleep, --wake, or --destroy%s\n", RED, RESET), file = stderr()) + if (!is.null(args$name)) { + payload <- list(name = args$name) + + if (!is.null(args$ports)) { + ports_vec <- as.integer(strsplit(args$ports, ",")[[1]]) + payload$ports <- ports_vec + } + + if (!is.null(args$domains)) { + domains_vec <- strsplit(args$domains, ",")[[1]] + payload$domains <- domains_vec + } + + if (!is.null(args$type)) { + payload$service_type <- args$type + } + + if (!is.null(args$bootstrap)) { + if (file.exists(args$bootstrap)) { + payload$bootstrap <- paste(readLines(args$bootstrap, warn = FALSE), collapse = "\n") + } else { + payload$bootstrap <- args$bootstrap + } + } + + if (!is.null(args$network)) { + payload$network <- args$network + } + + if (!is.null(args$vcpu)) { + payload$vcpu <- args$vcpu + } + + result <- api_request("/services", api_key, method = "POST", data = payload) + cat(sprintf("%sService created: %s%s\n", GREEN, if (!is.null(result$id)) result$id else "N/A", RESET)) + cat(sprintf("Name: %s\n", if (!is.null(result$name)) result$name else "N/A")) + if (!is.null(result$url)) { + cat(sprintf("URL: %s\n", result$url)) + } + return() + } + + cat(sprintf("%sError: Use --name to create, or --list, --info, --logs, --sleep, --wake, --destroy%s\n", RED, RESET), file = stderr()) quit(status = 1) } @@ -308,7 +350,13 @@ parse_args <- function() { logs = NULL, sleep = NULL, wake = NULL, - destroy = NULL + destroy = NULL, + name = NULL, + ports = NULL, + domains = NULL, + type = NULL, + bootstrap = NULL, + vcpu = NULL ) i <- 1 @@ -371,6 +419,30 @@ parse_args <- function() { i <- i + 1 result$destroy <- args[i] i <- i + 1 + } else if (arg == "--name") { + i <- i + 1 + result$name <- args[i] + i <- i + 1 + } else if (arg == "--ports") { + i <- i + 1 + result$ports <- args[i] + i <- i + 1 + } else if (arg == "--domains") { + i <- i + 1 + result$domains <- args[i] + i <- i + 1 + } else if (arg == "--type") { + i <- i + 1 + result$type <- args[i] + i <- i + 1 + } else if (arg == "--bootstrap") { + i <- i + 1 + result$bootstrap <- args[i] + i <- i + 1 + } else if (arg %in% c("-v", "--vcpu")) { + i <- i + 1 + result$vcpu <- as.integer(args[i]) + i <- i + 1 } else if (!startsWith(arg, "-")) { result$source_file <- arg i <- i + 1 diff --git a/un.raku b/un.raku index 84cfacb..4e43900 100644 --- a/un.raku +++ b/un.raku @@ -320,6 +320,7 @@ sub cmd-service(@args) { my $destroy-id = ''; my $name = ''; my $ports = ''; + my $type = ''; my $bootstrap = ''; my $network = ''; my $vcpu = 0; @@ -359,6 +360,10 @@ sub cmd-service(@args) { $i++; $ports = @args[$i]; } + when '--type' { + $i++; + $type = @args[$i]; + } when '--bootstrap' { $i++; $bootstrap = @args[$i]; @@ -430,6 +435,10 @@ sub cmd-service(@args) { %payload = $ports.split(',')>>.Int; } + if $type { + %payload = $type; + } + if $bootstrap { # Check if bootstrap is a file if $bootstrap.IO.e && $bootstrap.IO.f { diff --git a/un.rb b/un.rb index cb93c94..c68ead3 100644 --- a/un.rb +++ b/un.rb @@ -314,6 +314,7 @@ def cmd_service(options) payload = { name: options[:name] } payload[:ports] = options[:ports].split(',').map(&:to_i) if options[:ports] payload[:domains] = options[:domains].split(',') if options[:domains] + payload[:service_type] = options[:type] if options[:type] if options[:bootstrap] payload[:bootstrap] = if File.exist?(options[:bootstrap]) File.read(options[:bootstrap]) @@ -356,6 +357,7 @@ def main name: nil, ports: nil, domains: nil, + type: nil, bootstrap: nil, info: nil, logs: nil, @@ -421,6 +423,9 @@ def main when '--domains' i += 1 options[:domains] = ARGV[i] + when '--type' + i += 1 + options[:type] = ARGV[i] when '--bootstrap' i += 1 options[:bootstrap] = ARGV[i] @@ -494,6 +499,7 @@ def main --name NAME Service name --ports PORTS Comma-separated ports --domains DOMAINS Custom domains + --type TYPE Service type (minecraft|mumble|teamspeak|source|tcp|udp) --bootstrap CMD Bootstrap command/file -l, --list List services --info ID Get service details diff --git a/un.rs b/un.rs index 0dd82ca..3364898 100644 --- a/un.rs +++ b/un.rs @@ -338,6 +338,7 @@ fn cmd_service( name: Option<&str>, ports: Option<&str>, domains: Option<&str>, + service_type: Option<&str>, bootstrap: Option<&str>, list: bool, info: Option<&str>, @@ -420,6 +421,10 @@ fn cmd_service( json.push(']'); } + if let Some(t) = service_type { + json.push_str(&format!(r#","service_type":"{}""#, t)); + } + if let Some(b) = bootstrap { let cmd = if Path::new(b).exists() { fs::read_to_string(b).unwrap_or(b.to_string()) @@ -559,6 +564,7 @@ fn main() { args.iter().position(|x| x == "--name").and_then(|p| args.get(p + 1)).map(|s| s.as_str()), args.iter().position(|x| x == "--ports").and_then(|p| args.get(p + 1)).map(|s| s.as_str()), args.iter().position(|x| x == "--domains").and_then(|p| args.get(p + 1)).map(|s| s.as_str()), + args.iter().position(|x| x == "--type").and_then(|p| args.get(p + 1)).map(|s| s.as_str()), args.iter().position(|x| x == "--bootstrap").and_then(|p| args.get(p + 1)).map(|s| s.as_str()), args.contains(&"--list".to_string()), args.iter().position(|x| x == "--info").and_then(|p| args.get(p + 1)).map(|s| s.as_str()), diff --git a/un.scm b/un.scm index 1a2dfab..f2285f0 100644 --- a/un.scm +++ b/un.scm @@ -172,7 +172,7 @@ (display response) (newline)))))) -(define (service-cmd action id name ports bootstrap) +(define (service-cmd action id name ports bootstrap type) (let ((api-key (get-api-key))) (cond ((equal? action "list") @@ -196,7 +196,8 @@ ((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)) "")) - (json (format #f "{\"name\":\"~a\"~a~a}" name ports-json bootstrap-json)) + (type-json (if type (format #f ",\"service_type\":\"~a\"" type) "")) + (json (format #f "{\"name\":\"~a\"~a~a~a}" name ports-json bootstrap-json type-json)) (response (curl-post api-key "/services" json))) (format #t "~aService created~a\n" green reset) (display response) @@ -222,24 +223,26 @@ ((equal? (car args) "service") (cond ((and (> (length args) 1) (equal? (cadr args) "--list")) - (service-cmd "list" #f #f #f #f)) + (service-cmd "list" #f #f #f #f #f)) ((and (> (length args) 2) (equal? (cadr args) "--info")) - (service-cmd "info" (caddr args) #f #f #f)) + (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)) + (service-cmd "logs" (caddr args) #f #f #f #f)) ((and (> (length args) 2) (equal? (cadr args) "--sleep")) - (service-cmd "sleep" (caddr args) #f #f #f)) + (service-cmd "sleep" (caddr args) #f #f #f #f)) ((and (> (length args) 2) (equal? (cadr args) "--wake")) - (service-cmd "wake" (caddr args) #f #f #f)) + (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)) + (service-cmd "destroy" (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")) (list-ref args 4) #f)) (bootstrap (if (and (> (length args) 6) (equal? (list-ref args 5) "--bootstrap")) - (list-ref args 6) #f))) - (service-cmd "create" #f name ports bootstrap))) + (list-ref args 6) #f)) + (type (if (and (> (length args) 8) (equal? (list-ref args 7) "--type")) + (list-ref args 8) #f))) + (service-cmd "create" #f name ports bootstrap type))) (else (display "Error: Invalid service command\n" (current-error-port)) (exit 1)))) diff --git a/un.sh b/un.sh index af48572..0590612 100644 --- a/un.sh +++ b/un.sh @@ -395,6 +395,7 @@ cmd_service() { local name="" local ports="" local domains="" + local service_type="" local bootstrap="" local list=false local info="" @@ -423,6 +424,10 @@ cmd_service() { domains="$2" shift 2 ;; + --type) + service_type="$2" + shift 2 + ;; --bootstrap) bootstrap="$2" shift 2 @@ -555,6 +560,10 @@ cmd_service() { payload=$(echo "$payload" | jq --argjson d "$domains_json" '. + {domains: $d}') fi + if [[ -n "$service_type" ]]; then + payload=$(echo "$payload" | jq --arg t "$service_type" '. + {service_type: $t}') + fi + if [[ -n "$bootstrap" ]]; then if [[ -f "$bootstrap" ]]; then local bootstrap_content=$(cat "$bootstrap") @@ -614,6 +623,7 @@ Service options: --name NAME Service name --ports PORTS Comma-separated ports --domains DOMAINS Custom domains + --type TYPE Service type for SRV records (minecraft, mumble, teamspeak, source, tcp, udp) --bootstrap CMD Bootstrap command/file -l, --list List services --info ID Get service details diff --git a/un.tcl b/un.tcl index 46038d2..0109700 100644 --- a/un.tcl +++ b/un.tcl @@ -361,6 +361,7 @@ proc cmd_service {args} { set destroy_id "" set name "" set ports "" + set service_type "" set bootstrap "" set network "" set vcpu 0 @@ -400,6 +401,10 @@ proc cmd_service {args} { incr i set ports [lindex $args $i] } + --type { + incr i + set service_type [lindex $args $i] + } --bootstrap { incr i set bootstrap [lindex $args $i] @@ -479,6 +484,10 @@ proc cmd_service {args} { lappend payload ports [::json::write array {*}$port_json] } + if {$service_type ne ""} { + lappend payload service_type [::json::write string $service_type] + } + if {$bootstrap ne ""} { # Check if bootstrap is a file if {[file exists $bootstrap]} { diff --git a/un.ts b/un.ts index 1113f52..8bc2a43 100644 --- a/un.ts +++ b/un.ts @@ -98,6 +98,7 @@ interface Args { name: string | null; ports: string | null; domains: string | null; + type: string | null; bootstrap: string | null; info: string | null; logs: string | null; @@ -356,6 +357,7 @@ async function cmdService(args: Args): Promise { const payload: any = { name: args.name }; if (args.ports) payload.ports = args.ports.split(',').map(p => parseInt(p.trim())); if (args.domains) payload.domains = args.domains.split(','); + if (args.type) payload.service_type = args.type; if (args.bootstrap) { if (fs.existsSync(args.bootstrap)) { payload.bootstrap = fs.readFileSync(args.bootstrap, 'utf-8'); @@ -398,6 +400,7 @@ function parseArgs(argv: string[]): Args { name: null, ports: null, domains: null, + type: null, bootstrap: null, info: null, logs: null, @@ -467,6 +470,9 @@ function parseArgs(argv: string[]): Args { } else if (arg === '--domains' && i + 1 < argv.length) { args.domains = argv[++i]; i++; + } else if (arg === '--type' && i + 1 < argv.length) { + args.type = argv[++i]; + i++; } else if (arg === '--bootstrap' && i + 1 < argv.length) { args.bootstrap = argv[++i]; i++; @@ -545,6 +551,7 @@ Service options: --name NAME Service name --ports PORTS Comma-separated ports --domains DOMAINS Custom domains + --type TYPE Service type (minecraft|mumble|teamspeak|source|tcp|udp) --bootstrap CMD Bootstrap command/file -l, --list List services --info ID Get service details diff --git a/un.v b/un.v index 6597375..149d66a 100644 --- a/un.v +++ b/un.v @@ -176,7 +176,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, 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, 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)) @@ -227,6 +227,9 @@ fn cmd_service(name string, ports string, bootstrap string, list bool, info stri if ports != '' { json += ',"ports":[${ports}]' } + if service_type != '' { + json += ',"service_type":"${service_type}"' + } if bootstrap != '' { if os.exists(bootstrap) { boot_code := os.read_file(bootstrap) or { bootstrap } @@ -310,6 +313,7 @@ fn main() { if os.args[1] == 'service' { mut name := '' mut ports := '' + mut service_type := '' mut bootstrap := '' mut list := false mut info := '' @@ -332,6 +336,10 @@ fn main() { i++ ports = os.args[i] } + '--type' { + i++ + service_type = os.args[i] + } '--bootstrap' { i++ bootstrap = os.args[i] @@ -378,7 +386,7 @@ fn main() { i++ } - cmd_service(name, ports, bootstrap, list, info, logs, tail, sleep, wake, destroy, network, + cmd_service(name, ports, service_type, bootstrap, list, info, logs, tail, sleep, wake, destroy, network, vcpu, api_key) return } diff --git a/un.zig b/un.zig index 9ffc6ea..c0f2245 100644 --- a/un.zig +++ b/un.zig @@ -117,6 +117,7 @@ pub fn main() !u8 { var list = false; var name: ?[]const u8 = null; var ports: ?[]const u8 = null; + var service_type: ?[]const u8 = null; var info: ?[]const u8 = null; var i: usize = 2; while (i < args.len) : (i += 1) { @@ -128,6 +129,9 @@ pub fn main() !u8 { } else if (mem.eql(u8, args[i], "--ports") and i + 1 < args.len) { i += 1; ports = args[i]; + } else if (mem.eql(u8, args[i], "--type") and i + 1 < args.len) { + i += 1; + service_type = args[i]; } else if (mem.eql(u8, args[i], "--info") and i + 1 < args.len) { i += 1; info = args[i]; @@ -152,6 +156,9 @@ pub fn main() !u8 { if (ports) |p| { try writer.print(",\"ports\":[{s}]", .{p}); } + if (service_type) |t| { + try writer.print(",\"service_type\":\"{s}\"", .{t}); + } try writer.writeAll("}"); const json_str = json_stream.getWritten(); diff --git a/un_deno.ts b/un_deno.ts index 6c8ebcb..37469e6 100644 --- a/un_deno.ts +++ b/un_deno.ts @@ -338,6 +338,7 @@ async function cmdService(args: string[]) { let destroyId = ""; let name = ""; let ports = ""; + let serviceType = ""; let bootstrap = ""; let network = ""; let vcpu = 0; @@ -384,6 +385,11 @@ async function cmdService(args: string[]) { ports = args[++i]; } break; + case "--type": + if (i + 1 < args.length) { + serviceType = args[++i]; + } + break; case "--bootstrap": if (i + 1 < args.length) { bootstrap = args[++i]; @@ -460,6 +466,10 @@ async function cmdService(args: string[]) { payload.ports = ports.split(",").map((p) => parseInt(p)); } + if (serviceType) { + payload.service_type = serviceType; + } + if (bootstrap) { // Check if bootstrap is a file try { diff --git a/un_inception.c b/un_inception.c index 9c34de9..c86cc05 100644 --- a/un_inception.c +++ b/un_inception.c @@ -312,7 +312,7 @@ void cmd_session(int list, const char *kill, const char *shell, const char *netw printf("\n%sSession created%s\n", GREEN, RESET); } -void cmd_service(const char *name, const char *ports, const char *domains, const char *bootstrap, int list, const char *info, const char *logs, const char *tail, const char *sleep_svc, const char *wake, const char *destroy, const char *network, int vcpu, const char *api_key) { +void cmd_service(const char *name, const char *ports, const char *domains, const char *service_type, const char *bootstrap, int list, const char *info, const char *logs, const char *tail, const char *sleep_svc, const char *wake, const char *destroy, const char *network, int vcpu, const char *api_key) { char cmd[8192]; if (list) { @@ -384,6 +384,11 @@ void cmd_service(const char *name, const char *ports, const char *domains, const snprintf(temp, sizeof(temp), ",\"ports\":[%s]", ports); strcat(json, temp); } + if (service_type) { + char temp[128]; + snprintf(temp, sizeof(temp), ",\"service_type\":\"%s\"", service_type); + strcat(json, temp); + } if (bootstrap) { // Check if file struct stat st; @@ -465,6 +470,7 @@ int main(int argc, char *argv[]) { const char *name = NULL; const char *ports = NULL; const char *domains = NULL; + const char *service_type = NULL; const char *bootstrap = NULL; int list = 0; const char *info = NULL; @@ -480,6 +486,7 @@ int main(int argc, char *argv[]) { if (strcmp(argv[i], "--name") == 0 && i + 1 < argc) name = argv[++i]; else if (strcmp(argv[i], "--ports") == 0 && i + 1 < argc) ports = argv[++i]; else if (strcmp(argv[i], "--domains") == 0 && i + 1 < argc) domains = argv[++i]; + else if (strcmp(argv[i], "--type") == 0 && i + 1 < argc) service_type = argv[++i]; else if (strcmp(argv[i], "--bootstrap") == 0 && i + 1 < argc) bootstrap = argv[++i]; else if (strcmp(argv[i], "--list") == 0) list = 1; else if (strcmp(argv[i], "--info") == 0 && i + 1 < argc) info = argv[++i]; @@ -493,7 +500,7 @@ int main(int argc, char *argv[]) { else if (strcmp(argv[i], "-k") == 0 && i + 1 < argc) api_key = argv[++i]; } - cmd_service(name, ports, domains, bootstrap, list, info, logs, tail, sleep_svc, wake, destroy, network, vcpu, api_key); + cmd_service(name, ports, domains, service_type, bootstrap, list, info, logs, tail, sleep_svc, wake, destroy, network, vcpu, api_key); return 0; }