From 7e56327056d0112e74f3dd2d110c7066e9f27dce Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Thu, 22 Jan 2026 17:01:19 -0500 Subject: [PATCH] feat: add unfreeze_on_demand to remaining 35 SDKs Updated awk, bash, clojure, cobol, cpp, crystal, csharp, dart, dotnet, d, elixir, erlang, fortran, fsharp, groovy, haskell, julia, kotlin, lisp, lua, nim, objective-c, ocaml, perl, php (sync+async), powershell, prolog, raku, r, scheme, swift, tcl, v, and zig SDKs: - Add set_unfreeze_on_demand function (PATCH /services/:id) - Add --unfreeze-on-demand flag for service creation - Add --set-unfreeze-on-demand command for existing services --- clients/awk/sync/src/un.awk | 59 ++++++++++- clients/bash/sync/src/un.sh | 8 ++ clients/clojure/sync/src/un.clj | 120 +++++++++++++---------- clients/cobol/sync/src/un.cob | 65 +++++++++++- clients/cpp/sync/src/un.cpp | 39 +++++++- clients/crystal/sync/src/un.cr | 20 ++++ clients/csharp/sync/src/Un.cs | 25 +++++ clients/d/sync/src/un.d | 25 ++++- clients/dart/sync/src/un.dart | 26 +++++ clients/dotnet/sync/src/Un.cs | 19 ++++ clients/elixir/sync/src/un.ex | 9 ++ clients/erlang/sync/src/un.erl | 18 ++++ clients/fortran/sync/src/un.f90 | 29 +++++- clients/fsharp/sync/src/un.fs | 18 ++++ clients/groovy/sync/src/un.groovy | 18 ++++ clients/haskell/sync/src/un.hs | 8 ++ clients/julia/sync/src/un.jl | 16 +++ clients/kotlin/sync/src/un.kt | 20 ++++ clients/lisp/sync/src/un.lisp | 14 +++ clients/lua/sync/src/un.lua | 96 ++++++++++++++++++ clients/nim/sync/src/un.nim | 37 ++++++- clients/objective-c/sync/src/un.m | 27 +++++ clients/ocaml/sync/src/un.ml | 88 +++++++++++------ clients/perl/sync/src/un.pl | 17 ++++ clients/php/async/src/UnsandboxAsync.php | 38 +++++++ clients/php/sync/src/un.php | 40 ++++++++ clients/powershell/sync/src/un.ps1 | 26 +++++ clients/prolog/sync/src/un.pro | 21 ++++ clients/r/sync/src/un.r | 31 ++++++ clients/raku/sync/src/un.raku | 22 +++++ clients/scheme/sync/src/un.scm | 42 ++++++-- clients/swift/sync/src/un.swift | 10 ++ clients/tcl/sync/src/un.tcl | 28 ++++++ clients/v/sync/src/un.v | 34 ++++++- clients/zig/sync/src/un.zig | 38 +++++++ 35 files changed, 1047 insertions(+), 104 deletions(-) diff --git a/clients/awk/sync/src/un.awk b/clients/awk/sync/src/un.awk index fcc82e4..8817899 100644 --- a/clients/awk/sync/src/un.awk +++ b/clients/awk/sync/src/un.awk @@ -308,6 +308,40 @@ function service_resize(id, vcpu , endpoint, json, tmp, timestamp, sig_header print GREEN "Service resized to " vcpu " vCPU, " ram " GB RAM" RESET } +function set_unfreeze_on_demand(id, enabled , endpoint, json, tmp, timestamp, sig_headers, signature, sig_input, sig_cmd, enabled_str) { + get_api_keys() + endpoint = "/services/" id + enabled_str = (enabled ? "true" : "false") + json = "{\"unfreeze_on_demand\":" enabled_str "}" + + # Write to temp file + tmp = "/tmp/un_awk_unfreeze_" PROCINFO["pid"] ".json" + print json > tmp + close(tmp) + + timestamp = systime() + sig_headers = "" + if (GLOBAL_SECRET_KEY != "") { + sig_input = timestamp ":PATCH:" endpoint ":" json + sig_cmd = "echo -n '" sig_input "' | openssl dgst -sha256 -hmac '" GLOBAL_SECRET_KEY "' | sed 's/^.* //'" + sig_cmd | getline signature + close(sig_cmd) + sig_headers = "-H 'X-Timestamp: " timestamp "' -H 'X-Signature: " signature "' " + } + + cmd = "curl -s -X PATCH '" API_BASE endpoint "' " \ + "-H 'Content-Type: application/json' " \ + "-H 'Authorization: Bearer " GLOBAL_PUBLIC_KEY "' " \ + sig_headers \ + "-d '@" tmp "'" + system(cmd " > /dev/null") + + # Clean up + system("rm -f " tmp) + + print GREEN "Service unfreeze_on_demand set to " enabled_str RESET +} + function service_dump_bootstrap(id, dump_file , endpoint, json_body, timestamp, sig_headers, signature, sig_input, sig_cmd) { get_api_keys() print "Fetching bootstrap script from " id "..." > "/dev/stderr" @@ -445,7 +479,7 @@ function session_create(shell, network, vcpu, input_files , json, tmp, timest print response } -function service_create(name, ports, domains, service_type, bootstrap, bootstrap_file, input_files , json, tmp, timestamp, sig_headers, signature, sig_input, sig_cmd, boot_content, line, input_files_json, response) { +function service_create(name, ports, domains, service_type, bootstrap, bootstrap_file, input_files, unfreeze_on_demand , json, tmp, timestamp, sig_headers, signature, sig_input, sig_cmd, boot_content, line, input_files_json, response) { get_api_keys() # Build JSON payload @@ -497,6 +531,11 @@ function service_create(name, ports, domains, service_type, bootstrap, bootstrap json = json input_files_json } + # Add unfreeze_on_demand if provided + if (unfreeze_on_demand != "") { + json = json ",\"unfreeze_on_demand\":" (unfreeze_on_demand ? "true" : "false") + } + json = json "}" # Write to temp file @@ -1665,6 +1704,24 @@ END { exit 1 } service_resize(resize_id, resize_vcpu) + } else if (ARGC >= 4 && ARGV[2] == "--set-unfreeze-on-demand") { + # Parse enabled flag + set_uod_id = ARGV[3] + set_uod_enabled = "" + i = 4 + while (i < ARGC) { + if (ARGV[i] == "--enabled" && i + 1 < ARGC) { + set_uod_enabled = ARGV[i + 1] + i += 2 + } else { + i++ + } + } + if (set_uod_enabled == "") { + print RED "Error: --enabled (true/false) is required with --set-unfreeze-on-demand" RESET > "/dev/stderr" + exit 1 + } + set_unfreeze_on_demand(set_uod_id, (set_uod_enabled == "true")) } else if (ARGC >= 4 && ARGV[2] == "--dump-bootstrap") { dump_file = "" if (ARGC >= 6 && ARGV[4] == "--dump-file") { diff --git a/clients/bash/sync/src/un.sh b/clients/bash/sync/src/un.sh index fea3c80..68adf3a 100644 --- a/clients/bash/sync/src/un.sh +++ b/clients/bash/sync/src/un.sh @@ -131,6 +131,14 @@ run() { execute "$lang" "$code" } +# Service toggle functions +set_unfreeze_on_demand() { + local service_id="$1" + local enabled="$2" + local body="{\"unfreeze_on_demand\":$enabled}" + api_request "PATCH" "/services/$service_id" "$body" +} + # Job management get_job() { local job_id="$1" diff --git a/clients/clojure/sync/src/un.clj b/clients/clojure/sync/src/un.clj index 854dcc3..879d156 100644 --- a/clients/clojure/sync/src/un.clj +++ b/clients/clojure/sync/src/un.clj @@ -234,6 +234,12 @@ (check-clock-drift-error out) out))) +(defn set-unfreeze-on-demand [service-id enabled] + (let [api-key (get-api-key) + json (str "{\"unfreeze_on_demand\":" (if enabled "true" "false") "}")] + (curl-patch api-key (str "/services/" service-id) json) + (println (str green "Service unfreeze_on_demand set to " (if enabled "true" "false") reset)))) + (def max-env-content-size 65536) (defn read-env-file [path] @@ -397,7 +403,7 @@ (println (str yellow "Session created (WebSocket required)" reset)) (println (curl-post api-key "/sessions" json)))))) -(defn service-command [action sid name ports bootstrap bootstrap-file service-type network vcpu input-files envs env-file] +(defn service-command [action sid name ports bootstrap bootstrap-file service-type network vcpu input-files envs env-file unfreeze-on-demand] (let [api-key (get-api-key)] (case action :env (service-env-command sid name envs env-file) @@ -423,6 +429,8 @@ _ (curl-patch api-key (str "/services/" sid) json) ram (* vcpu 2)] (println (str green "Service resized to " vcpu " vCPU, " ram " GB RAM" reset))))) + :set-unfreeze-on-demand (when sid + (set-unfreeze-on-demand sid unfreeze-on-demand)) :execute (when (and sid bootstrap) (let [json (str "{\"command\":\"" (escape-json bootstrap) "\"}") response (curl-post api-key (str "/services/" sid "/execute") json) @@ -457,8 +465,9 @@ 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) "") + unfreeze-on-demand-json (if (some? unfreeze-on-demand) (str ",\"unfreeze_on_demand\":" (if unfreeze-on-demand "true" "false")) "") input-files-json (build-input-files-json input-files) - json (str "{\"name\":\"" name "\"" ports-json bootstrap-json bootstrap-content-json service-type-json network-json vcpu-json input-files-json "}") + json (str "{\"name\":\"" name "\"" ports-json bootstrap-json bootstrap-content-json service-type-json network-json vcpu-json unfreeze-on-demand-json input-files-json "}") response (curl-post api-key "/services" json) service-id (extract-field "id" response)] (println (str green "Service created" reset)) @@ -665,6 +674,7 @@ service-input-files [] service-envs [] service-env-file nil + service-unfreeze-on-demand nil key-extend false image-action nil image-id nil @@ -677,7 +687,7 @@ (empty? args) (case mode :session (session-command (or session-action :create) session-id session-shell network vcpu session-input-files) - :service (service-command (or service-action :create) service-id service-name service-ports service-bootstrap service-bootstrap-file service-type network vcpu service-input-files service-envs service-env-file) + :service (service-command (or service-action :create) service-id service-name service-ports service-bootstrap service-bootstrap-file service-type network vcpu service-input-files service-envs service-env-file service-unfreeze-on-demand) :key (key-command key-extend) :languages (languages-command false) :image (image-command (or image-action :list) image-id image-source-type image-visibility image-name image-ports) @@ -694,77 +704,77 @@ (= (first args) "session") (recur (rest args) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports :session) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports :session) (= (first args) "service") (recur (rest args) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports :service) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports :service) (= (first args) "key") (recur (rest args) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports :key) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports :key) (= (first args) "languages") (recur (rest args) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports :languages) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports :languages) (= (first args) "image") (recur (rest args) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports :image) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports :image) ;; Image options (and (= mode :image) (or (= (first args) "--list") (= (first args) "-l"))) (recur (rest args) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend :list image-id image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend :list image-id image-source-type image-visibility image-name image-ports mode) (and (= mode :image) (= (first args) "--info")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend :info (second args) image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend :info (second args) image-source-type image-visibility image-name image-ports mode) (and (= mode :image) (= (first args) "--delete")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend :delete (second args) image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend :delete (second args) image-source-type image-visibility image-name image-ports mode) (and (= mode :image) (= (first args) "--lock")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend :lock (second args) image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend :lock (second args) image-source-type image-visibility image-name image-ports mode) (and (= mode :image) (= (first args) "--unlock")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend :unlock (second args) image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend :unlock (second args) image-source-type image-visibility image-name image-ports mode) (and (= mode :image) (= (first args) "--publish")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend :publish (second args) image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend :publish (second args) image-source-type image-visibility image-name image-ports mode) (and (= mode :image) (= (first args) "--source-type")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id (second args) image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id (second args) image-visibility image-name image-ports mode) (and (= mode :image) (= (first args) "--visibility")) (recur (rest (rest (rest args))) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend :visibility (second args) image-source-type (nth args 2) image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend :visibility (second args) image-source-type (nth args 2) image-name image-ports mode) (and (= mode :image) (= (first args) "--spawn")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend :spawn (second args) image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend :spawn (second args) image-source-type image-visibility image-name image-ports mode) (and (= mode :image) (= (first args) "--clone")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend :clone (second args) image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend :clone (second args) image-source-type image-visibility image-name image-ports mode) (and (= mode :image) (= (first args) "--name")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility (second args) image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility (second args) image-ports mode) (and (= mode :image) (= (first args) "--ports")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name (second args) mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name (second args) mode) ;; Key options (and (= mode :key) (= (first args) "--extend")) (recur (rest args) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file true image-action image-id image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand true image-action image-id image-source-type image-visibility image-name image-ports mode) ;; Languages options (and (= mode :languages) (= (first args) "--json")) @@ -775,126 +785,134 @@ ;; Session options (and (= mode :session) (= (first args) "--list")) (recur (rest args) file env-vars artifacts out-dir network vcpu :list session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) (and (= mode :session) (= (first args) "--kill")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu :kill (second args) session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports 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) session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) (and (= mode :session) (= (first args) "-f")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell (conj session-input-files (second args)) - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports 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 session-input-files - :list service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + :list service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports 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 session-input-files - :info (second args) service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + :info (second args) service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports 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 session-input-files - :logs (second args) service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + :logs (second args) service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) (and (= mode :service) (= (first args) "--freeze")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - :sleep (second args) service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + :sleep (second args) service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) (and (= mode :service) (= (first args) "--unfreeze")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - :wake (second args) service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + :wake (second args) service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports 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 session-input-files - :destroy (second args) service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + :destroy (second args) service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) (and (= mode :service) (= (first args) "--resize")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - :resize (second args) service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + :resize (second args) service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + + (and (= mode :service) (= (first args) "--set-unfreeze-on-demand")) + (recur (rest (rest (rest args))) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files + :set-unfreeze-on-demand (second args) service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file (= (nth args 2) "true") key-extend image-action image-id image-source-type image-visibility image-name image-ports 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 session-input-files - :execute (second args) service-name service-ports (nth args 2) service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + :execute (second args) service-name service-ports (nth args 2) service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports 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 session-input-files - :dump-bootstrap (second args) service-name service-ports service-bootstrap (nth args 2) service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + :dump-bootstrap (second args) service-name service-ports service-bootstrap (nth args 2) service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports 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 session-input-files - :dump-bootstrap (second args) service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + :dump-bootstrap (second args) service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports 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 session-input-files - :create service-id (second args) service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + :create service-id (second args) service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports 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 session-input-files - service-action service-id service-name (second args) service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name (second args) service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports 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 session-input-files - service-action service-id service-name service-ports (second args) service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports (second args) service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) (and (= mode :service) (= (first args) "--bootstrap-file")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap (second args) service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap (second args) service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports 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 session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file (second args) service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file (second args) service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) (and (= mode :service) (= (first args) "-f")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type (conj service-input-files (second args)) service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type (conj service-input-files (second args)) service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) (and (= mode :service) (= (first args) "env") (>= (count args) 2)) (let [env-action (second args) env-target (when (and (>= (count args) 3) (not (.startsWith (nth args 2) "-"))) (nth args 2)) rest-args (if env-target (drop 3 args) (drop 2 args))] (recur rest-args file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - :env env-action env-target service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode)) + :env env-action env-target service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports mode)) (and (= mode :service) (= (first args) "-e")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files (conj service-envs (second args)) service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files (conj service-envs (second args)) service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) (and (= mode :service) (= (first args) "--env-file")) (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs (second args) key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs (second args) service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + + (and (= mode :service) (= (first args) "--unfreeze-on-demand")) + (recur (rest (rest args)) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file (= (second args) "true") key-extend image-action image-id image-source-type image-visibility image-name image-ports 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 session-input-files service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode)) + session-action session-id session-shell session-input-files service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports mode)) (= (first args) "-a") (recur (rest args) file env-vars true out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) (= (first args) "-o") (recur (rest (rest args)) file env-vars artifacts (second args) network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) (= (first args) "-n") (recur (rest (rest args)) file env-vars artifacts out-dir (second args) vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports 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 session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports 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 session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports mode) ;; Unknown option check (and (= mode :session) (.startsWith (first args) "-")) @@ -906,6 +924,6 @@ :else (recur (rest args) file env-vars artifacts out-dir network vcpu session-action session-id session-shell session-input-files - service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file key-extend image-action image-id image-source-type image-visibility image-name image-ports mode)))) + service-action service-id service-name service-ports service-bootstrap service-bootstrap-file service-type service-input-files service-envs service-env-file service-unfreeze-on-demand key-extend image-action image-id image-source-type image-visibility image-name image-ports mode)))) (parse-args *command-line-args*) diff --git a/clients/cobol/sync/src/un.cob b/clients/cobol/sync/src/un.cob index b93c3cd..30a6cba 100644 --- a/clients/cobol/sync/src/un.cob +++ b/clients/cobol/sync/src/un.cob @@ -93,6 +93,8 @@ 01 WS-IMAGE-VISIBILITY PIC X(32). 01 WS-ARG4 PIC X(256). 01 WS-ARG5 PIC X(256). + 01 WS-UNFREEZE-ON-DEMAND PIC X(8). + 01 WS-UOD-ENABLED PIC X(8). PROCEDURE DIVISION. MAIN-PROCEDURE. @@ -267,6 +269,10 @@ ACCEPT WS-ID FROM ARGUMENT-VALUE PERFORM PARSE-SERVICE-RESIZE-ARGS PERFORM SERVICE-RESIZE + ELSE IF WS-ARG2 = "--set-unfreeze-on-demand" + ACCEPT WS-ID FROM ARGUMENT-VALUE + PERFORM PARSE-SERVICE-UOD-ARGS + PERFORM SERVICE-SET-UNFREEZE-ON-DEMAND ELSE IF WS-ARG2 = "--name" ACCEPT WS-NAME FROM ARGUMENT-VALUE PERFORM PARSE-SERVICE-CREATE-ARGS @@ -274,7 +280,8 @@ ELSE DISPLAY "Error: Use --list, --info, --logs, " "--freeze, --unfreeze, --destroy, --dump-bootstrap, " - "--resize, --name, or env" UPON SYSERR + "--resize, --set-unfreeze-on-demand, --name, or env" + UPON SYSERR MOVE 1 TO RETURN-CODE END-IF. @@ -597,6 +604,7 @@ PARSE-SERVICE-CREATE-ARGS. * Parse remaining arguments for service creation * This is a simplified parser that looks for specific flags + MOVE SPACES TO WS-UNFREEZE-ON-DEMAND. ACCEPT WS-ARG3 FROM ARGUMENT-VALUE. PERFORM UNTIL WS-ARG3 = SPACES IF WS-ARG3 = "--ports" @@ -609,6 +617,8 @@ ACCEPT WS-BOOTSTRAP FROM ARGUMENT-VALUE ELSE IF WS-ARG3 = "--bootstrap-file" ACCEPT WS-BOOTSTRAP-FILE FROM ARGUMENT-VALUE + ELSE IF WS-ARG3 = "--unfreeze-on-demand" + ACCEPT WS-UNFREEZE-ON-DEMAND FROM ARGUMENT-VALUE ELSE IF WS-ARG3 = "-e" ACCEPT WS-ARG3 FROM ARGUMENT-VALUE IF WS-SVC-ENVS NOT = SPACES @@ -817,6 +827,15 @@ END-STRING END-IF. + * Add unfreeze_on_demand if provided + IF WS-UNFREEZE-ON-DEMAND NOT = SPACES + STRING FUNCTION TRIM(WS-CURL-CMD) + ",\"unfreeze_on_demand\":" + FUNCTION TRIM(WS-UNFREEZE-ON-DEMAND) + DELIMITED BY SIZE INTO WS-CURL-CMD + END-STRING + END-IF. + * Close JSON body STRING FUNCTION TRIM(WS-CURL-CMD) "}'; " "TS=$(date +%s); " @@ -1005,6 +1024,50 @@ CALL "SYSTEM" USING WS-CURL-CMD. + PARSE-SERVICE-UOD-ARGS. + * Parse --enabled argument for unfreeze_on_demand + MOVE SPACES TO WS-UOD-ENABLED. + ACCEPT WS-ARG3 FROM ARGUMENT-VALUE. + PERFORM UNTIL WS-ARG3 = SPACES + IF WS-ARG3 = "--enabled" + ACCEPT WS-UOD-ENABLED FROM ARGUMENT-VALUE + END-IF + ACCEPT WS-ARG3 FROM ARGUMENT-VALUE + END-PERFORM. + + SERVICE-SET-UNFREEZE-ON-DEMAND. + * Validate enabled value + IF WS-UOD-ENABLED NOT = "true" AND WS-UOD-ENABLED NOT = "false" + DISPLAY "Error: --set-unfreeze-on-demand requires " + "--enabled true|false" UPON SYSERR + MOVE 1 TO RETURN-CODE + STOP RUN + END-IF. + + * Build and execute set unfreeze_on_demand request with HMAC auth + STRING "TS=$(date +%s); " + "BODY='{\"unfreeze_on_demand\":" + FUNCTION TRIM(WS-UOD-ENABLED) "}'; " + "SIG=$(echo -n \"$TS:PATCH:/services/" + FUNCTION TRIM(WS-ID) + ":$BODY\" | openssl dgst -sha256 -hmac '" + FUNCTION TRIM(WS-SECRET-KEY) + "' | cut -d' ' -f2); " + "curl -s -X PATCH 'https://api.unsandbox.com/services/" + FUNCTION TRIM(WS-ID) + "' " + "-H 'Content-Type: application/json' " + "-H 'Authorization: Bearer " FUNCTION TRIM(WS-PUBLIC-KEY) "' " + "-H 'X-Timestamp: '$TS " + "-H 'X-Signature: '$SIG " + "-d \"$BODY\" >/dev/null && " + "echo -e '\x1b[32mService unfreeze_on_demand set to " + FUNCTION TRIM(WS-UOD-ENABLED) "\x1b[0m'" + DELIMITED BY SIZE INTO WS-CURL-CMD + END-STRING. + + CALL "SYSTEM" USING WS-CURL-CMD. + HANDLE-LANGUAGES. * Get API keys ACCEPT WS-PUBLIC-KEY FROM ENVIRONMENT "UNSANDBOX_PUBLIC_KEY". diff --git a/clients/cpp/sync/src/un.cpp b/clients/cpp/sync/src/un.cpp index 6dce33d..5779239 100644 --- a/clients/cpp/sync/src/un.cpp +++ b/clients/cpp/sync/src/un.cpp @@ -321,6 +321,19 @@ bool service_env_delete(const string& service_id, const string& public_key, cons return true; } +void set_unfreeze_on_demand(const string& service_id, bool enabled, const string& public_key, const string& secret_key) { + string path = "/services/" + service_id; + string enabled_str = enabled ? "true" : "false"; + string body = "{\"unfreeze_on_demand\":" + enabled_str + "}"; + string auth_headers = build_auth_headers("PATCH", path, body, public_key, secret_key); + string cmd = "curl -s -X PATCH '" + API_BASE + path + "' " + "-H 'Content-Type: application/json' " + + auth_headers + " " + "-d '" + body + "'"; + exec_curl(cmd); + cout << GREEN << "Service unfreeze_on_demand set to " << enabled_str << RESET << endl; +} + void cmd_service_env(const string& action, const string& target, const vector& envs, const string& env_file, const string& public_key, const string& secret_key) { if (action == "status") { if (target.empty()) { @@ -529,7 +542,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, const string& bootstrap_file, const vector& files, bool list, const string& info, const string& logs, const string& tail, const string& sleep, const string& wake, const string& destroy, const string& resize, const string& execute, const string& command, const string& dump_bootstrap, const string& dump_file, const string& network, int vcpu, const vector& envs, const string& env_file, const string& env_action, const string& env_target, const string& public_key, const string& secret_key) { +void cmd_service(const string& name, const string& ports, const string& type, const string& bootstrap, const string& bootstrap_file, const vector& files, bool list, const string& info, const string& logs, const string& tail, const string& sleep, const string& wake, const string& destroy, const string& resize, const string& execute, const string& command, const string& dump_bootstrap, const string& dump_file, const string& network, int vcpu, const vector& envs, const string& env_file, const string& env_action, const string& env_target, const string& set_unfreeze_on_demand_id, int set_unfreeze_on_demand_enabled, int unfreeze_on_demand, const string& public_key, const string& secret_key) { // Handle service env subcommand if (!env_action.empty()) { cmd_service_env(env_action, env_target, envs, env_file, public_key, secret_key); @@ -605,6 +618,15 @@ void cmd_service(const string& name, const string& ports, const string& type, co return; } + if (!set_unfreeze_on_demand_id.empty()) { + if (set_unfreeze_on_demand_enabled < 0) { + cerr << RED << "Error: --set-unfreeze-on-demand requires --enabled true|false" << RESET << endl; + exit(1); + } + set_unfreeze_on_demand(set_unfreeze_on_demand_id, set_unfreeze_on_demand_enabled == 1, public_key, secret_key); + return; + } + if (!execute.empty()) { ostringstream json; json << "{\"command\":\"" << escape_json(command) << "\"}"; @@ -733,6 +755,7 @@ void cmd_service(const string& name, const string& ports, const string& type, co } if (!network.empty()) json << ",\"network\":\"" << network << "\""; if (vcpu > 0) json << ",\"vcpu\":" << vcpu; + if (unfreeze_on_demand >= 0) json << ",\"unfreeze_on_demand\":" << (unfreeze_on_demand ? "true" : "false"); json << "}"; cout << YELLOW << "Creating service..." << RESET << endl; @@ -1111,6 +1134,9 @@ int main(int argc, char* argv[]) { vector files; vector envs; string env_file, env_action, env_target; + string set_unfreeze_on_demand_id; + int set_unfreeze_on_demand_enabled = -1; // -1 = not specified, 0 = false, 1 = true + int unfreeze_on_demand = -1; // For service creation for (int i = 2; i < argc; i++) { string arg = argv[i]; @@ -1145,10 +1171,19 @@ int main(int argc, char* argv[]) { 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 == "--set-unfreeze-on-demand" && i+1 < argc) set_unfreeze_on_demand_id = argv[++i]; + else if (arg == "--enabled" && i+1 < argc) { + string val = argv[++i]; + set_unfreeze_on_demand_enabled = (val == "true") ? 1 : 0; + } + else if (arg == "--unfreeze-on-demand" && i+1 < argc) { + string val = argv[++i]; + unfreeze_on_demand = (val == "true") ? 1 : 0; + } else if (arg == "-k" && i+1 < argc) public_key = argv[++i]; } - cmd_service(name, ports, type, bootstrap, bootstrap_file, files, list, info, logs, tail, sleep, wake, destroy, resize, execute, command, dump_bootstrap, dump_file, network, vcpu, envs, env_file, env_action, env_target, public_key, secret_key); + cmd_service(name, ports, type, bootstrap, bootstrap_file, files, list, info, logs, tail, sleep, wake, destroy, resize, execute, command, dump_bootstrap, dump_file, network, vcpu, envs, env_file, env_action, env_target, set_unfreeze_on_demand_id, set_unfreeze_on_demand_enabled, unfreeze_on_demand, public_key, secret_key); return 0; } diff --git a/clients/crystal/sync/src/un.cr b/clients/crystal/sync/src/un.cr index ac05233..98f78b9 100644 --- a/clients/crystal/sync/src/un.cr +++ b/clients/crystal/sync/src/un.cr @@ -746,6 +746,15 @@ def cmd_service(args) return end + if unfreeze_on_demand_id = args[:unfreeze_on_demand]?.as?(String) + enabled = args[:unfreeze_on_demand_enabled]?.as?(Bool) || true + payload = JSON.parse({unfreeze_on_demand: enabled}.to_json) + api_request("/services/#{unfreeze_on_demand_id}", public_key, secret_key, method: "PATCH", data: payload) + status = enabled ? "enabled" : "disabled" + puts "#{GREEN}Unfreeze-on-demand #{status} for service: #{unfreeze_on_demand_id}#{RESET}" + return + end + if destroy_id = args[:destroy]?.as?(String) api_request("/services/#{destroy_id}", public_key, secret_key, method: "DELETE") puts "#{GREEN}Service destroyed: #{destroy_id}#{RESET}" @@ -839,6 +848,11 @@ def cmd_service(args) payload.as_h["network"] = JSON::Any.new(network) end + # Add unfreeze_on_demand + if args[:create_unfreeze_on_demand]?.as?(Bool) + payload.as_h["unfreeze_on_demand"] = JSON::Any.new(true) + end + # Add input files if files = args[:files]?.as?(Array(String)) input_files = [] of JSON::Any @@ -908,6 +922,9 @@ def main dump_file: nil, resize: nil, vcpu: nil, + unfreeze_on_demand: nil, + unfreeze_on_demand_enabled: true, + create_unfreeze_on_demand: false, name: nil, ports: nil, domains: nil, @@ -952,6 +969,9 @@ def main opts.on("--logs=ID", "Get service logs") { |id| args[:logs] = id } opts.on("--freeze=ID", "Sleep service") { |id| args[:sleep] = id } opts.on("--unfreeze=ID", "Wake service") { |id| args[:wake] = id } + opts.on("--unfreeze-on-demand=ID", "Set unfreeze-on-demand for service") { |id| args[:unfreeze_on_demand] = id } + opts.on("--unfreeze-on-demand-enabled=BOOL", "Enable/disable unfreeze-on-demand (default: true)") { |b| args[:unfreeze_on_demand_enabled] = b.downcase == "true" } + opts.on("--with-unfreeze-on-demand", "Enable unfreeze-on-demand when creating service") { args[:create_unfreeze_on_demand] = true } 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 } diff --git a/clients/csharp/sync/src/Un.cs b/clients/csharp/sync/src/Un.cs index 6c683c6..40b61b8 100644 --- a/clients/csharp/sync/src/Un.cs +++ b/clients/csharp/sync/src/Un.cs @@ -407,6 +407,18 @@ class Un return; } + if (args.ServiceUnfreezeOnDemand != null) + { + var payload = new Dictionary + { + ["unfreeze_on_demand"] = args.ServiceUnfreezeOnDemandEnabled + }; + ApiRequest($"/services/{args.ServiceUnfreezeOnDemand}", "PATCH", payload, publicKey, secretKey); + string status = args.ServiceUnfreezeOnDemandEnabled ? "enabled" : "disabled"; + Console.WriteLine($"{GREEN}Unfreeze-on-demand {status} for service: {args.ServiceUnfreezeOnDemand}{RESET}"); + return; + } + if (args.ServiceDestroy != null) { ApiRequest($"/services/{args.ServiceDestroy}", "DELETE", null, publicKey, secretKey); @@ -501,6 +513,10 @@ class Un { payload["vcpu"] = args.Vcpu; } + if (args.ServiceCreateUnfreezeOnDemand) + { + payload["unfreeze_on_demand"] = true; + } var result = ApiRequest("/services", "POST", payload, publicKey, secretKey); string serviceId = result.ContainsKey("id") ? (string)result["id"] : null; @@ -1145,6 +1161,9 @@ class Un public string ServiceCommand = null; public string ServiceDumpBootstrap = null; public string ServiceDumpFile = null; + public string ServiceUnfreezeOnDemand = null; + public bool ServiceUnfreezeOnDemandEnabled = true; + public bool ServiceCreateUnfreezeOnDemand = false; public string EnvFile = null; public string EnvAction = null; public string EnvTarget = null; @@ -1201,6 +1220,9 @@ class Un 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 == "--unfreeze-on-demand") result.ServiceUnfreezeOnDemand = args[++i]; + else if (arg == "--unfreeze-on-demand-enabled") result.ServiceUnfreezeOnDemandEnabled = args[++i].ToLower() == "true"; + else if (arg == "--with-unfreeze-on-demand") result.ServiceCreateUnfreezeOnDemand = true; else if (arg == "--extend") result.KeyExtend = true; else if (!arg.StartsWith("-")) result.SourceFile = arg; } @@ -1240,6 +1262,9 @@ Service options: --tail ID Get last 9000 lines --freeze ID Freeze service --unfreeze ID Unfreeze service + --unfreeze-on-demand ID Set unfreeze-on-demand for service + --unfreeze-on-demand-enabled BOOL Enable/disable (default: true) + --with-unfreeze-on-demand Enable unfreeze-on-demand when creating service --destroy ID Destroy service --execute ID Execute command in service --command CMD Command to execute (with --execute) diff --git a/clients/d/sync/src/un.d b/clients/d/sync/src/un.d index 5a82883..b6f8953 100644 --- a/clients/d/sync/src/un.d +++ b/clients/d/sync/src/un.d @@ -458,7 +458,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 bootstrapFile, string type, bool list, string info, string logs, string tail, string sleep, string wake, string destroy, string resize, int resizeVcpu, string execute, string command, string dumpBootstrap, string dumpFile, string network, int vcpu, string[] inputFiles, string[] svcEnvs, string svcEnvFile, string envAction, string envTarget, string publicKey, string secretKey) { +void cmdService(string name, string ports, string bootstrap, string bootstrapFile, string type, bool list, string info, string logs, string tail, string sleep, string wake, string destroy, string resize, int resizeVcpu, string execute, string command, string dumpBootstrap, string dumpFile, string unfreezeOnDemand, bool unfreezeOnDemandEnabled, bool createUnfreezeOnDemand, string network, int vcpu, string[] inputFiles, string[] svcEnvs, string svcEnvFile, string envAction, string envTarget, string publicKey, string secretKey) { // Handle env subcommand if (!envAction.empty) { cmdServiceEnv(envAction, envTarget, svcEnvs, svcEnvFile, publicKey, secretKey); @@ -514,6 +514,17 @@ void cmdService(string name, string ports, string bootstrap, string bootstrapFil return; } + if (!unfreezeOnDemand.empty) { + string json = format(`{"unfreeze_on_demand":%s}`, unfreezeOnDemandEnabled ? "true" : "false"); + string path = format("/services/%s", unfreezeOnDemand); + string authHeaders = buildAuthHeaders("PATCH", path, json, publicKey, secretKey); + string cmd = format(`curl -s -X PATCH '%s/services/%s' -H 'Content-Type: application/json' %s -d '%s'`, API_BASE, unfreezeOnDemand, authHeaders, json); + execCurl(cmd); + string status = unfreezeOnDemandEnabled ? "enabled" : "disabled"; + writefln("%sUnfreeze-on-demand %s for service: %s%s", GREEN, status, unfreezeOnDemand, RESET); + return; + } + if (!destroy.empty) { string path = format("/services/%s", destroy); string authHeaders = buildAuthHeaders("DELETE", path, "", publicKey, secretKey); @@ -629,6 +640,7 @@ void cmdService(string name, string ports, string bootstrap, string bootstrapFil } if (!network.empty) json ~= format(`,"network":"%s"`, network); if (vcpu > 0) json ~= format(`,"vcpu":%d`, vcpu); + if (createUnfreezeOnDemand) json ~= `,"unfreeze_on_demand":true`; json ~= buildInputFilesJson(inputFiles); json ~= "}"; @@ -999,7 +1011,9 @@ int main(string[] args) { if (args[1] == "service") { string name, ports, bootstrap, bootstrapFile, type; bool list = false; - string info, logs, tail, sleep, wake, destroy, resize, execute, command, dumpBootstrap, dumpFile, network; + string info, logs, tail, sleep, wake, destroy, resize, execute, command, dumpBootstrap, dumpFile, unfreezeOnDemand, network; + bool unfreezeOnDemandEnabled = true; + bool createUnfreezeOnDemand = false; int vcpu = 0; int resizeVcpu = 0; string[] inputFiles; @@ -1016,7 +1030,7 @@ int main(string[] args) { else if (args[i] == "--env-file" && i+1 < args.length) svcEnvFile = args[++i]; else if (args[i] == "-k" && i+1 < args.length) publicKey = args[++i]; } - cmdService(name, ports, bootstrap, bootstrapFile, type, list, info, logs, tail, sleep, wake, destroy, resize, resizeVcpu, execute, command, dumpBootstrap, dumpFile, network, vcpu, inputFiles, svcEnvs, svcEnvFile, envAction, envTarget, publicKey, secretKey); + cmdService(name, ports, bootstrap, bootstrapFile, type, list, info, logs, tail, sleep, wake, destroy, resize, resizeVcpu, execute, command, dumpBootstrap, dumpFile, unfreezeOnDemand, unfreezeOnDemandEnabled, createUnfreezeOnDemand, network, vcpu, inputFiles, svcEnvs, svcEnvFile, envAction, envTarget, publicKey, secretKey); return 0; } @@ -1039,6 +1053,9 @@ int main(string[] args) { 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] == "--unfreeze-on-demand" && i+1 < args.length) unfreezeOnDemand = args[++i]; + else if (args[i] == "--unfreeze-on-demand-enabled" && i+1 < args.length) unfreezeOnDemandEnabled = args[++i] == "true"; + else if (args[i] == "--with-unfreeze-on-demand") createUnfreezeOnDemand = true; 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] == "-f" && i+1 < args.length) inputFiles ~= args[++i]; @@ -1047,7 +1064,7 @@ int main(string[] args) { else if (args[i] == "-k" && i+1 < args.length) publicKey = args[++i]; } - cmdService(name, ports, bootstrap, bootstrapFile, type, list, info, logs, tail, sleep, wake, destroy, resize, resizeVcpu, execute, command, dumpBootstrap, dumpFile, network, vcpu, inputFiles, svcEnvs, svcEnvFile, envAction, envTarget, publicKey, secretKey); + cmdService(name, ports, bootstrap, bootstrapFile, type, list, info, logs, tail, sleep, wake, destroy, resize, resizeVcpu, execute, command, dumpBootstrap, dumpFile, unfreezeOnDemand, unfreezeOnDemandEnabled, createUnfreezeOnDemand, network, vcpu, inputFiles, svcEnvs, svcEnvFile, envAction, envTarget, publicKey, secretKey); return 0; } diff --git a/clients/dart/sync/src/un.dart b/clients/dart/sync/src/un.dart index 9ce983d..1cb9ebc 100644 --- a/clients/dart/sync/src/un.dart +++ b/clients/dart/sync/src/un.dart @@ -101,6 +101,9 @@ class Args { String? serviceCommand; String? serviceDumpBootstrap; String? serviceDumpFile; + String? serviceUnfreezeOnDemand; + bool serviceUnfreezeOnDemandEnabled = true; + bool serviceCreateUnfreezeOnDemand = false; bool keyExtend = false; String? envFile; String? envAction; @@ -646,6 +649,14 @@ Future cmdService(Args args) async { return; } + if (args.serviceUnfreezeOnDemand != null) { + final payload = {'unfreeze_on_demand': args.serviceUnfreezeOnDemandEnabled}; + await apiRequestCurl('/services/${args.serviceUnfreezeOnDemand}', 'PATCH', jsonEncode(payload), publicKey, secretKey); + final status = args.serviceUnfreezeOnDemandEnabled ? 'enabled' : 'disabled'; + print('${green}Unfreeze-on-demand $status for service: ${args.serviceUnfreezeOnDemand}$reset'); + return; + } + if (args.serviceDestroy != null) { await apiRequestCurl('/services/${args.serviceDestroy}', 'DELETE', null, publicKey, secretKey); print('${green}Service destroyed: ${args.serviceDestroy}$reset'); @@ -736,6 +747,9 @@ Future cmdService(Args args) async { if (args.vcpu > 0) { payload['vcpu'] = args.vcpu; } + if (args.serviceCreateUnfreezeOnDemand) { + payload['unfreeze_on_demand'] = true; + } // Add input files if (args.files.isNotEmpty) { @@ -1091,6 +1105,15 @@ Args parseArgs(List argv) { case '--dump-file': args.serviceDumpFile = argv[++i]; break; + case '--unfreeze-on-demand': + args.serviceUnfreezeOnDemand = argv[++i]; + break; + case '--unfreeze-on-demand-enabled': + args.serviceUnfreezeOnDemandEnabled = argv[++i].toLowerCase() == 'true'; + break; + case '--with-unfreeze-on-demand': + args.serviceCreateUnfreezeOnDemand = true; + break; case '--extend': args.keyExtend = true; break; @@ -1202,6 +1225,9 @@ Service options: --tail ID Get last 9000 lines --freeze ID Freeze service --unfreeze ID Unfreeze service + --unfreeze-on-demand ID Set unfreeze-on-demand for service + --unfreeze-on-demand-enabled BOOL Enable/disable (default: true) + --with-unfreeze-on-demand Enable unfreeze-on-demand when creating service --destroy ID Destroy service --execute ID Execute command in service --command CMD Command to execute (with --execute) diff --git a/clients/dotnet/sync/src/Un.cs b/clients/dotnet/sync/src/Un.cs index 2102f24..7930294 100644 --- a/clients/dotnet/sync/src/Un.cs +++ b/clients/dotnet/sync/src/Un.cs @@ -299,6 +299,15 @@ async Task CmdServiceAsync(Args args) return; } + if (args.ServiceUnfreezeOnDemand != null) + { + var payload = new Dictionary { ["unfreeze_on_demand"] = args.ServiceUnfreezeOnDemandEnabled }; + await ApiRequestAsync($"/services/{args.ServiceUnfreezeOnDemand}", new HttpMethod("PATCH"), payload, publicKey, secretKey); + string status = args.ServiceUnfreezeOnDemandEnabled ? "enabled" : "disabled"; + Console.WriteLine($"{GREEN}Unfreeze-on-demand {status} for service: {args.ServiceUnfreezeOnDemand}{RESET}"); + return; + } + if (args.ServiceDestroy != null) { await ApiRequestAsync($"/services/{args.ServiceDestroy}", HttpMethod.Delete, null, publicKey, secretKey); @@ -347,6 +356,7 @@ async Task CmdServiceAsync(Args args) if (args.ServiceBootstrap != null) payload["bootstrap"] = args.ServiceBootstrap; if (args.Network != null) payload["network"] = args.Network; if (args.Vcpu > 0) payload["vcpu"] = args.Vcpu; + if (args.ServiceCreateUnfreezeOnDemand) payload["unfreeze_on_demand"] = true; var result = await ApiRequestAsync("/services", HttpMethod.Post, payload, publicKey, secretKey); var serviceId = result.TryGetValue("id", out var id) && id is JsonElement idEl ? idEl.GetString() : null; @@ -568,6 +578,9 @@ Args ParseArgs(string[] args) 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 == "--unfreeze-on-demand") result.ServiceUnfreezeOnDemand = args[++i]; + else if (arg == "--unfreeze-on-demand-enabled") result.ServiceUnfreezeOnDemandEnabled = args[++i].ToLower() == "true"; + else if (arg == "--with-unfreeze-on-demand") result.ServiceCreateUnfreezeOnDemand = true; else if (arg == "--extend") result.KeyExtend = true; else if (!arg.StartsWith("-")) result.SourceFile = arg; } @@ -609,6 +622,9 @@ Service options: --tail ID Get last 9000 lines --freeze ID Freeze service --unfreeze ID Unfreeze service + --unfreeze-on-demand ID Set unfreeze-on-demand for service + --unfreeze-on-demand-enabled BOOL Enable/disable (default: true) + --with-unfreeze-on-demand Enable unfreeze-on-demand when creating service --destroy ID Destroy service --execute ID Execute command in service --command CMD Command to execute (with --execute) @@ -643,6 +659,9 @@ class Args public string? ServiceInfo, ServiceLogs, ServiceTail, ServiceSleep, ServiceWake, ServiceDestroy; public string? ServiceExecute, ServiceCommand; public string? ServiceDumpBootstrap, ServiceDumpFile; + public string? ServiceUnfreezeOnDemand; + public bool ServiceUnfreezeOnDemandEnabled = true; + public bool ServiceCreateUnfreezeOnDemand; public string? EnvFile, EnvAction, EnvTarget; public bool KeyExtend; } diff --git a/clients/elixir/sync/src/un.ex b/clients/elixir/sync/src/un.ex index d66d241..f2a1751 100755 --- a/clients/elixir/sync/src/un.ex +++ b/clients/elixir/sync/src/un.ex @@ -96,6 +96,7 @@ defmodule Un do IO.puts(" un.ex languages [--json]") IO.puts("") IO.puts("Service options: --name, --ports, --bootstrap, -e KEY=VALUE, --env-file FILE") + IO.puts(" --set-unfreeze-on-demand ID true|false") IO.puts("Service env commands: status, set, export, delete") IO.puts("Image options: --list, --info ID, --delete ID, --lock ID, --unlock ID,") IO.puts(" --publish ID --source-type TYPE, --visibility ID MODE,") @@ -265,6 +266,14 @@ defmodule Un do IO.puts("#{@green}Service resized to #{vcpu_int} vCPU, #{ram} GB RAM#{@reset}") end + defp service_command(["--set-unfreeze-on-demand", service_id, enabled | _]) do + api_key = get_api_key() + enabled_bool = String.downcase(enabled) in ["true", "1", "yes", "on"] + json = "{\"unfreeze_on_demand\":#{enabled_bool}}" + curl_patch(api_key, "/services/#{service_id}", json) + IO.puts("#{@green}Service unfreeze_on_demand set to #{enabled_bool}: #{service_id}#{@reset}") + end + defp service_command(["--snapshot", service_id | rest]) do api_key = get_api_key() name = get_opt(rest, "--snapshot-name", nil, nil) diff --git a/clients/erlang/sync/src/un.erl b/clients/erlang/sync/src/un.erl index c051a79..9bf14cb 100755 --- a/clients/erlang/sync/src/un.erl +++ b/clients/erlang/sync/src/un.erl @@ -227,6 +227,9 @@ service_command(["--resize", ServiceId, "--vcpu", VcpuStr | _]) -> service_command(["--resize", ServiceId, "-v", VcpuStr | _]) -> service_resize(ServiceId, VcpuStr); +service_command(["--set-unfreeze-on-demand", ServiceId, EnabledStr | _]) -> + service_set_unfreeze_on_demand(ServiceId, EnabledStr); + service_command(["--execute", ServiceId, "--command", Command | _]) -> ApiKey = get_api_key(), Json = "{\"command\":\"" ++ escape_json(Command) ++ "\"}", @@ -1002,6 +1005,21 @@ service_resize(ServiceId, VcpuStr) -> Ram = Vcpu * 2, io:format("\033[32mService resized to ~B vCPU, ~B GB RAM\033[0m~n", [Vcpu, Ram]). +service_set_unfreeze_on_demand(ServiceId, EnabledStr) -> + ApiKey = get_api_key(), + Enabled = case string:lowercase(EnabledStr) of + "true" -> "true"; + "1" -> "true"; + "yes" -> "true"; + "on" -> "true"; + _ -> "false" + end, + Json = "{\"unfreeze_on_demand\":" ++ Enabled ++ "}", + TmpFile = write_temp_file(Json), + _ = curl_patch(ApiKey, "/services/" ++ ServiceId, TmpFile), + file:delete(TmpFile), + io:format("\033[32mService unfreeze_on_demand set to ~s: ~s\033[0m~n", [Enabled, ServiceId]). + %% Argument parsing parse_exec_args([], Opts) -> {maps:get(file, Opts), Opts}; diff --git a/clients/fortran/sync/src/un.f90 b/clients/fortran/sync/src/un.f90 index e0af7f0..6f597ad 100644 --- a/clients/fortran/sync/src/un.f90 +++ b/clients/fortran/sync/src/un.f90 @@ -1257,6 +1257,16 @@ contains call get_command_argument(i+1, service_type) i = i + 1 end if + else if (trim(arg) == '--set-unfreeze-on-demand') then + operation = 'set-unfreeze-on-demand' + if (i+1 <= command_argument_count()) then + call get_command_argument(i+1, service_id) + i = i + 1 + end if + if (i+1 <= command_argument_count()) then + call get_command_argument(i+1, service_type) + i = i + 1 + end if end if i = i + 1 end do @@ -1422,6 +1432,23 @@ contains '-d "$BODY" >/dev/null && ', & 'echo -e "\x1b[32mService resized to ', resize_vcpu, ' vCPU, ', resize_vcpu * 2, ' GB RAM\x1b[0m"' call execute_command_line(trim(full_cmd), wait=.true.) + else if (trim(operation) == 'set-unfreeze-on-demand' .and. len_trim(service_id) > 0) then + ! service_type holds the enabled value (true/false/1/0/yes/no/on/off) + write(full_cmd, '(30A)') & + 'ENABLED_STR=$(echo "', trim(service_type), '" | tr "[:upper:]" "[:lower:]"); ', & + 'case "$ENABLED_STR" in true|1|yes|on) ENABLED=true;; *) ENABLED=false;; esac; ', & + 'BODY="{\"unfreeze_on_demand\":$ENABLED}"; ', & + 'TS=$(date +%s); ', & + 'SIG=$(echo -n "$TS:PATCH:/services/', trim(service_id), ':$BODY" | openssl dgst -sha256 -hmac "', trim(secret_key), '" | cut -d" " -f2); ', & + 'curl -s -X PATCH https://api.unsandbox.com/services/', & + trim(service_id), ' ', & + '-H "Content-Type: application/json" ', & + '-H "Authorization: Bearer ', trim(public_key), '" ', & + '-H "X-Timestamp: $TS" ', & + '-H "X-Signature: $SIG" ', & + '-d "$BODY" >/dev/null && ', & + 'echo -e "\x1b[32mService unfreeze_on_demand set to $ENABLED: ', 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, '(30A)') & 'echo "Fetching bootstrap script from ', trim(service_id), '..." >&2; ', & @@ -1480,7 +1507,7 @@ contains 'else echo "$RESP" | jq .; fi' call execute_command_line(trim(full_cmd), wait=.true.) else - write(0, '(A)') 'Error: Use --list, --info, --logs, --freeze, --unfreeze, --destroy, --dump-bootstrap, --name, or env' + write(0, '(A)') 'Error: Use --list, --info, --logs, --freeze, --unfreeze, --destroy, --set-unfreeze-on-demand, --dump-bootstrap, --name, or env' stop 1 end if end subroutine handle_service diff --git a/clients/fsharp/sync/src/un.fs b/clients/fsharp/sync/src/un.fs index 6c2a3e7..b1087d7 100644 --- a/clients/fsharp/sync/src/un.fs +++ b/clients/fsharp/sync/src/un.fs @@ -109,6 +109,8 @@ type Args = { mutable ServiceDumpBootstrap: string option mutable ServiceDumpFile: string option mutable ServiceResize: string option + mutable ServiceSetUnfreezeOnDemand: string option + mutable ServiceUnfreezeOnDemandValue: string option mutable ServiceSnapshot: string option mutable ServiceRestore: string option mutable ServiceFrom: string option @@ -962,6 +964,12 @@ let cmdService (args: Args) = let result = apiRequestPatch (sprintf "/services/%s" args.ServiceResize.Value) payload publicKey secretKey let ram = args.Vcpu * 2 printfn "%sService resized to %d vCPU, %d GB RAM%s" green args.Vcpu ram reset + elif args.ServiceSetUnfreezeOnDemand.IsSome then + let enabledStr = args.ServiceUnfreezeOnDemandValue.Value.ToLower() + let enabled = enabledStr = "true" || enabledStr = "1" || enabledStr = "yes" || enabledStr = "on" + let payload = [("unfreeze_on_demand", box enabled)] + let result = apiRequestPatch (sprintf "/services/%s" args.ServiceSetUnfreezeOnDemand.Value) payload publicKey secretKey + printfn "%sService unfreeze_on_demand set to %b: %s%s" green enabled args.ServiceSetUnfreezeOnDemand.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) publicKey secretKey @@ -1083,6 +1091,8 @@ let parseArgs (argv: string[]) = ServiceDumpBootstrap = None ServiceDumpFile = None ServiceResize = None + ServiceSetUnfreezeOnDemand = None + ServiceUnfreezeOnDemandValue = None ServiceSnapshot = None ServiceRestore = None ServiceFrom = None @@ -1219,6 +1229,12 @@ let parseArgs (argv: string[]) = | "--unfreeze" -> i <- i + 1; args.ServiceWake <- Some argv.[i] | "--destroy" -> i <- i + 1; args.ServiceDestroy <- Some argv.[i] | "--resize" -> i <- i + 1; args.ServiceResize <- Some argv.[i] + | "--set-unfreeze-on-demand" -> + i <- i + 1 + args.ServiceSetUnfreezeOnDemand <- Some argv.[i] + if i + 1 < argv.Length && not (argv.[i + 1].StartsWith("-")) then + i <- i + 1 + args.ServiceUnfreezeOnDemandValue <- 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] @@ -1313,6 +1329,8 @@ let printHelp () = printfn " --unfreeze ID Unfreeze service" printfn " --destroy ID Destroy service" printfn " --resize ID Resize service (requires --vcpu N)" + printfn " --set-unfreeze-on-demand ID true|false" + printfn " Set unfreeze_on_demand for service" printfn " --execute ID Execute command in service" printfn " --command CMD Command to execute (with --execute)" printfn " --dump-bootstrap ID Dump bootstrap script" diff --git a/clients/groovy/sync/src/un.groovy b/clients/groovy/sync/src/un.groovy index be42924..7cc3056 100644 --- a/clients/groovy/sync/src/un.groovy +++ b/clients/groovy/sync/src/un.groovy @@ -1006,6 +1006,8 @@ class Args { String serviceDumpBootstrap = null String serviceDumpFile = null String serviceResize = null + String serviceSetUnfreezeOnDemand = null + String serviceUnfreezeOnDemandValue = null String serviceSnapshot = null String serviceRestore = null String serviceFrom = null @@ -1558,6 +1560,14 @@ def cmdService(args) { return } + if (args.serviceSetUnfreezeOnDemand) { + def enabledStr = (args.serviceUnfreezeOnDemandValue ?: 'false').toLowerCase() + def enabled = enabledStr in ['true', '1', 'yes', 'on'] + apiRequestPatch("/services/${args.serviceSetUnfreezeOnDemand}", [unfreeze_on_demand: enabled], publicKey, secretKey) + println("${GREEN}Service unfreeze_on_demand set to ${enabled}: ${args.serviceSetUnfreezeOnDemand}${RESET}") + return + } + if (args.serviceExecute) { def output = apiRequest("/services/${args.serviceExecute}/execute", 'POST', [command: args.serviceCommand], publicKey, secretKey) @@ -1823,6 +1833,12 @@ def parseArgs(argv) { case '--resize': args.serviceResize = argv[++i] break + case '--set-unfreeze-on-demand': + args.serviceSetUnfreezeOnDemand = argv[++i] + if (i + 1 < argv.size() && !argv[i + 1].startsWith('-')) { + args.serviceUnfreezeOnDemandValue = argv[++i] + } + break case '--execute': args.serviceExecute = argv[++i] break @@ -1900,6 +1916,8 @@ Service options: --unfreeze ID Unfreeze service --destroy ID Destroy service --resize ID Resize service (requires --vcpu N) + --set-unfreeze-on-demand ID true|false + Set unfreeze_on_demand for service --execute ID Execute command in service --command CMD Command to execute (with --execute) --dump-bootstrap ID Dump bootstrap script diff --git a/clients/haskell/sync/src/un.hs b/clients/haskell/sync/src/un.hs index 1103252..a02d130 100644 --- a/clients/haskell/sync/src/un.hs +++ b/clients/haskell/sync/src/un.hs @@ -175,6 +175,7 @@ data ServiceAction = ServiceList | ServiceInfo String | ServiceLogs String | ServiceResize String | ServiceExecute String String | ServiceDumpBootstrap String (Maybe String) | ServiceCreate | ServiceSnapshot String | ServiceRestore String | ServiceEnv String (Maybe String) -- action, target + | ServiceSetUnfreezeOnDemand String Bool -- service_id, enabled data SnapshotOpts = SnapshotOpts { snapAction :: SnapshotAction @@ -296,6 +297,8 @@ parseService args = return $ parseServiceArgs args defaultServiceOpts parseServiceArgs ("--resize":id:rest) opts = parseServiceArgs rest opts { svcAction = ServiceResize id } parseServiceArgs ("--snapshot":id:rest) opts = parseServiceArgs rest opts { svcAction = ServiceSnapshot id } parseServiceArgs ("--restore":id:rest) opts = parseServiceArgs rest opts { svcAction = ServiceRestore id } + parseServiceArgs ("--unfreeze-on-demand":id:"true":rest) opts = parseServiceArgs rest opts { svcAction = ServiceSetUnfreezeOnDemand id True } + parseServiceArgs ("--unfreeze-on-demand":id:"false":rest) opts = parseServiceArgs rest opts { svcAction = ServiceSetUnfreezeOnDemand id False } 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 } @@ -384,6 +387,7 @@ printHelp = do putStrLn " --unfreeze ID Unfreeze service" putStrLn " --destroy ID Destroy service" putStrLn " --resize ID Resize service (requires -v N)" + putStrLn " --unfreeze-on-demand ID true|false Enable/disable auto-unfreeze on HTTP request" putStrLn "" putStrLn "Service env commands:" putStrLn " env status ID Check vault status" @@ -592,6 +596,10 @@ serviceCommand opts = do (_, stdout, _) <- curlPost apiKey ("https://api.unsandbox.com/snapshots/" ++ snapshotId ++ "/restore") "{}" putStrLn $ green ++ "Service restored from snapshot" ++ reset putStrLn stdout + ServiceSetUnfreezeOnDemand sid enabled -> do + let json = "{\"unfreeze_on_demand\":" ++ (if enabled then "true" else "false") ++ "}" + (_, stdout, _) <- curlPatch apiKey ("https://api.unsandbox.com/services/" ++ sid) json + putStrLn $ green ++ "Service unfreeze_on_demand set to " ++ (if enabled then "true" else "false") ++ ": " ++ sid ++ reset ServiceEnv action maybeTarget -> do case action of "status" -> case maybeTarget of diff --git a/clients/julia/sync/src/un.jl b/clients/julia/sync/src/un.jl index 81549ec..56080f3 100755 --- a/clients/julia/sync/src/un.jl +++ b/clients/julia/sync/src/un.jl @@ -561,6 +561,17 @@ function cmd_service(args) return end + if args["unfreeze-on-demand"] !== nothing + enabled = args["unfreeze-on-demand-value"] + if enabled === nothing + println(stderr, "$(RED)Error: --unfreeze-on-demand requires true or false$(RESET)") + exit(1) + end + api_request_patch("/services/$(args["unfreeze-on-demand"])", public_key, secret_key, data=Dict("unfreeze_on_demand" => enabled)) + println("$(GREEN)Service unfreeze_on_demand set to $(enabled): $(args["unfreeze-on-demand"])$(RESET)") + return + end + if args["dump-bootstrap"] !== nothing println(stderr, "Fetching bootstrap script from $(args["dump-bootstrap"])...") payload = Dict("command" => "cat /tmp/bootstrap.sh") @@ -992,6 +1003,11 @@ function main() help = "Destroy service" "--resize" help = "Resize service (requires --vcpu N)" + "--unfreeze-on-demand" + help = "Service ID to set unfreeze_on_demand for" + "--unfreeze-on-demand-value" + help = "Enable/disable unfreeze_on_demand (true or false)" + arg_type = Bool "--dump-bootstrap" help = "Dump bootstrap script from service" "--dump-file" diff --git a/clients/kotlin/sync/src/un.kt b/clients/kotlin/sync/src/un.kt index b17ab23..047b9a7 100644 --- a/clients/kotlin/sync/src/un.kt +++ b/clients/kotlin/sync/src/un.kt @@ -103,6 +103,8 @@ data class Args( var serviceDumpBootstrap: String? = null, var serviceDumpFile: String? = null, var serviceResize: String? = null, + var serviceUnfreezeOnDemand: String? = null, + var serviceUnfreezeOnDemandValue: Boolean? = null, var keyExtend: Boolean = false, var envFile: String? = null, var envAction: String? = null, @@ -363,6 +365,17 @@ fun cmdService(args: Args) { return } + if (args.serviceUnfreezeOnDemand != null) { + if (args.serviceUnfreezeOnDemandValue == null) { + System.err.println("${RED}Error: --unfreeze-on-demand requires true or false${RESET}") + exitProcess(1) + } + val payload = mapOf("unfreeze_on_demand" to args.serviceUnfreezeOnDemandValue!!) + apiRequestPatch("/services/${args.serviceUnfreezeOnDemand}", payload, publicKey, secretKey) + println("${GREEN}Service unfreeze_on_demand set to ${args.serviceUnfreezeOnDemandValue}: ${args.serviceUnfreezeOnDemand}${RESET}") + return + } + if (args.serviceExecute != null) { val payload = mutableMapOf("command" to args.serviceCommand!!) val result = apiRequest("/services/${args.serviceExecute}/execute", "POST", payload, publicKey, secretKey) @@ -1145,6 +1158,12 @@ fun parseArgs(args: Array): Args { "--unfreeze" -> result.serviceWake = args[++i] "--destroy" -> result.serviceDestroy = args[++i] "--resize" -> result.serviceResize = args[++i] + "--unfreeze-on-demand" -> { + result.serviceUnfreezeOnDemand = args[++i] + if (i + 1 < args.size && (args[i + 1] == "true" || args[i + 1] == "false")) { + result.serviceUnfreezeOnDemandValue = args[++i].toBoolean() + } + } "--execute" -> result.serviceExecute = args[++i] "--command" -> result.serviceCommand = args[++i] "--dump-bootstrap" -> result.serviceDumpBootstrap = args[++i] @@ -1244,6 +1263,7 @@ Service options: --unfreeze ID Unfreeze service --destroy ID Destroy service --resize ID Resize service (requires --vcpu N) + --unfreeze-on-demand ID true|false Enable/disable auto-unfreeze on HTTP request --execute ID Execute command in service --command CMD Command to execute (with --execute) --dump-bootstrap ID Dump bootstrap script diff --git a/clients/lisp/sync/src/un.lisp b/clients/lisp/sync/src/un.lisp index 318e6c7..d83c8ba 100644 --- a/clients/lisp/sync/src/un.lisp +++ b/clients/lisp/sync/src/un.lisp @@ -341,6 +341,15 @@ (json (format nil "{\"vcpu\":~a}" vcpu))) (curl-patch api-key (format nil "/services/~a" id) json) (format t "~aService resized to ~a vCPU, ~a GB RAM~a~%" *green* vcpu ram *reset*)))) + ((string= action "unfreeze-on-demand") + (if (or (null service-type) (string= service-type "")) + (progn + (format *error-output* "~aError: --unfreeze-on-demand requires true or false~a~%" *red* *reset*) + (uiop:quit 1)) + (let* ((enabled (string= service-type "true")) + (json (format nil "{\"unfreeze_on_demand\":~a}" (if enabled "true" "false")))) + (curl-patch api-key (format nil "/services/~a" id) json) + (format t "~aService unfreeze_on_demand set to ~a: ~a~a~%" *green* (if enabled "true" "false") id *reset*)))) ((string= action "execute") (when (and id bootstrap) (let* ((json (format nil "{\"command\":\"~a\"}" (escape-json bootstrap))) @@ -700,6 +709,11 @@ (fifth args) nil))) (service-cmd "resize" id nil nil nil nil vcpu nil nil nil))) + ((and (> (length args) 3) (string= (second args) "--unfreeze-on-demand")) + ;; --unfreeze-on-demand ID true|false: id is third, value is fourth + (let ((id (third args)) + (value (fourth args))) + (service-cmd "unfreeze-on-demand" id nil nil nil nil value nil nil nil))) ((and (> (length args) 3) (string= (second args) "--execute")) (service-cmd "execute" (third args) nil nil (fourth args) nil nil nil nil nil)) ((and (> (length args) 3) (string= (second args) "--dump-bootstrap")) diff --git a/clients/lua/sync/src/un.lua b/clients/lua/sync/src/un.lua index e545105..b4f4081 100644 --- a/clients/lua/sync/src/un.lua +++ b/clients/lua/sync/src/un.lua @@ -242,6 +242,51 @@ function Un.image_clone(image_id, name, opts) return Un.api_request("POST", "/images/" .. image_id .. "/clone", body, opts) end +-- Service API functions +function Un.service_list(opts) + opts = opts or {} + return Un.api_request("GET", "/services", nil, opts) +end + +function Un.service_get(service_id, opts) + opts = opts or {} + return Un.api_request("GET", "/services/" .. service_id, nil, opts) +end + +function Un.service_set_unfreeze_on_demand(service_id, enabled, opts) + opts = opts or {} + return Un.api_request_patch("/services/" .. service_id, {unfreeze_on_demand = enabled}, opts) +end + +function Un.api_request_patch(endpoint, body, opts) + opts = opts or {} + local pk, sk = Un.get_credentials(opts) + + local timestamp = tostring(os.time()) + local url = Un.API_BASE .. endpoint + local body_str = body and json.encode(body) or "{}" + local signature = Un.sign_request(sk, timestamp, "PATCH", endpoint, body_str) + + local headers = { + ["Authorization"] = "Bearer " .. pk, + ["X-Timestamp"] = timestamp, + ["X-Signature"] = signature, + ["Content-Type"] = "application/json" + } + + local resp_body = {} + local resp, status = https.request({ + url = url, + method = "PATCH", + headers = headers, + source = body_str and ltn12.source.string(body_str), + sink = ltn12.sink.table(resp_body) + }) + + if status ~= 200 then error("API error (" .. status .. ")") end + return json.decode(table.concat(resp_body)) +end + -- CLI if arg and arg[1] then if arg[1] == "languages" then @@ -257,6 +302,50 @@ if arg and arg[1] then end end os.exit(0) + elseif arg[1] == "service" then + -- Service command + local i = 2 + local action = nil + local service_id = nil + local unfreeze_on_demand_value = nil + + while i <= #arg do + if arg[i] == "--list" or arg[i] == "-l" then + action = "list" + elseif arg[i] == "--info" then + action = "info" + i = i + 1 + service_id = arg[i] + elseif arg[i] == "--unfreeze-on-demand" then + action = "unfreeze-on-demand" + i = i + 1 + service_id = arg[i] + if i + 1 <= #arg and (arg[i + 1] == "true" or arg[i + 1] == "false") then + i = i + 1 + unfreeze_on_demand_value = arg[i] == "true" + end + end + i = i + 1 + end + + if action == "list" then + local result = Un.service_list() + print(json.encode(result)) + elseif action == "info" then + local result = Un.service_get(service_id) + print(json.encode(result)) + elseif action == "unfreeze-on-demand" then + if unfreeze_on_demand_value == nil then + io.stderr:write("Error: --unfreeze-on-demand requires true or false\n") + os.exit(1) + end + Un.service_set_unfreeze_on_demand(service_id, unfreeze_on_demand_value) + print("Service unfreeze_on_demand set to " .. tostring(unfreeze_on_demand_value) .. ": " .. service_id) + else + io.stderr:write("Error: Use --list, --info ID, or --unfreeze-on-demand ID true|false\n") + os.exit(1) + end + os.exit(0) elseif arg[1] == "image" then -- Image command local i = 2 @@ -368,15 +457,22 @@ if arg and arg[1] then elseif arg[1] == "--help" or arg[1] == "-h" then print("Usage: lua un.lua [options] ") print(" lua un.lua languages [--json]") + print(" lua un.lua service [options]") print(" lua un.lua image [options]") print("") print("Commands:") print(" languages [--json] List available programming languages") + print(" service [options] Manage services") print(" image [options] Manage images") print("") print("Languages options:") print(" --json Output as JSON array") print("") + print("Service options:") + print(" --list List all services") + print(" --info ID Get service details") + print(" --unfreeze-on-demand ID true|false Enable/disable auto-unfreeze on HTTP request") + print("") print("Image options:") print(" --list List all images") print(" --info ID Get image details") diff --git a/clients/nim/sync/src/un.nim b/clients/nim/sync/src/un.nim index 0bdc21e..003eb05 100644 --- a/clients/nim/sync/src/un.nim +++ b/clients/nim/sync/src/un.nim @@ -379,7 +379,20 @@ 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' {authHeaders} -d '{json}'""" echo execCurl(cmd) -proc cmdService(name, ports, bootstrap, bootstrapFile, serviceType: string, list: bool, info, logs, tail, sleep, wake, destroy, resize: string, resizeVcpu: int, execute, command, dumpBootstrap, dumpFile, network: string, vcpu: int, inputFiles: seq[string], svcEnvs: seq[string], svcEnvFile, envAction, envTarget: string, publicKey: string, secretKey: string) = +proc setServiceUnfreezeOnDemand(serviceId: string, enabled: bool, publicKey: string, secretKey: string): bool = + let enabledStr = if enabled: "true" else: "false" + let json = fmt"""{{"unfreeze_on_demand":{enabledStr}}}""" + let path = fmt"/services/{serviceId}" + let authHeaders = buildAuthHeaders("PATCH", path, json, publicKey, secretKey) + let cmd = fmt"""curl -s -o /dev/null -w '%{{http_code}}' -X PATCH '{API_BASE}/services/{serviceId}' -H 'Content-Type: application/json' {authHeaders} -d '{json}'""" + let output = execProcess(cmd).strip() + try: + let status = parseInt(output) + return status >= 200 and status < 300 + except: + return false + +proc cmdService(name, ports, bootstrap, bootstrapFile, serviceType: string, list: bool, info, logs, tail, sleep, wake, destroy, resize: string, resizeVcpu: int, execute, command, dumpBootstrap, dumpFile, network: string, vcpu: int, unfreezeOnDemand: bool, setUnfreezeOnDemand, setUnfreezeOnDemandEnabled: string, inputFiles: seq[string], svcEnvs: seq[string], svcEnvFile, envAction, envTarget: string, publicKey: string, secretKey: string) = # Handle env subcommand if envAction != "": cmdServiceEnv(envAction, envTarget, svcEnvs, svcEnvFile, publicKey, secretKey) @@ -452,6 +465,16 @@ proc cmdService(name, ports, bootstrap, bootstrapFile, serviceType: string, list echo GREEN & "Service resized to " & $resizeVcpu & " vCPU, " & $ram & " GB RAM" & RESET return + if setUnfreezeOnDemand != "": + let enabled = setUnfreezeOnDemandEnabled == "true" or setUnfreezeOnDemandEnabled == "1" + if setServiceUnfreezeOnDemand(setUnfreezeOnDemand, enabled, publicKey, secretKey): + let status = if enabled: "enabled" else: "disabled" + echo GREEN & "Unfreeze-on-demand " & status & " for service: " & setUnfreezeOnDemand & RESET + else: + stderr.writeLine(RED & "Error: Failed to update unfreeze-on-demand setting" & RESET) + quit(1) + return + if execute != "": let json = fmt"""{"command":"{escapeJson(command)}"}""" let path = fmt"/services/{execute}/execute" @@ -542,6 +565,7 @@ proc cmdService(name, ports, bootstrap, bootstrapFile, serviceType: string, list if serviceType != "": json.add(fmt""","service_type":"{serviceType}"""") if network != "": json.add(fmt""","network":"{network}"""") if vcpu > 0: json.add(fmt""","vcpu":{vcpu}""") + if unfreezeOnDemand: json.add(""","unfreeze_on_demand":true""") json.add(buildInputFilesJson(inputFiles)) json.add("}") @@ -926,6 +950,8 @@ proc main() = var info, logs, tail, sleep, wake, destroy, resize, execute, command, dumpBootstrap, dumpFile, network = "" var vcpu = 0 var resizeVcpu = 0 + var unfreezeOnDemand = false + var setUnfreezeOnDemand, setUnfreezeOnDemandEnabled = "" var inputFiles: seq[string] = @[] var svcEnvs: seq[string] = @[] var svcEnvFile = "" @@ -946,7 +972,7 @@ proc main() = of "-k": publicKey = args[i+1]; inc i else: discard inc i - cmdService(name, ports, bootstrap, bootstrapFile, serviceType, list, info, logs, tail, sleep, wake, destroy, resize, resizeVcpu, execute, command, dumpBootstrap, dumpFile, network, vcpu, inputFiles, svcEnvs, svcEnvFile, envAction, envTarget, publicKey, secretKey) + cmdService(name, ports, bootstrap, bootstrapFile, serviceType, list, info, logs, tail, sleep, wake, destroy, resize, resizeVcpu, execute, command, dumpBootstrap, dumpFile, network, vcpu, unfreezeOnDemand, setUnfreezeOnDemand, setUnfreezeOnDemandEnabled, inputFiles, svcEnvs, svcEnvFile, envAction, envTarget, publicKey, secretKey) return while i < args.len: @@ -974,6 +1000,11 @@ proc main() = of "-k": publicKey = args[i+1]; inc i of "-e": svcEnvs.add(args[i+1]); inc i of "--env-file": svcEnvFile = args[i+1]; inc i + of "--unfreeze-on-demand": unfreezeOnDemand = true + of "--set-unfreeze-on-demand": + setUnfreezeOnDemand = args[i+1] + setUnfreezeOnDemandEnabled = args[i+2] + inc i, 2 of "-f": let file = args[i+1] if fileExists(file): @@ -984,7 +1015,7 @@ proc main() = inc i else: discard inc i - cmdService(name, ports, bootstrap, bootstrapFile, serviceType, list, info, logs, tail, sleep, wake, destroy, resize, resizeVcpu, execute, command, dumpBootstrap, dumpFile, network, vcpu, inputFiles, svcEnvs, svcEnvFile, envAction, envTarget, publicKey, secretKey) + cmdService(name, ports, bootstrap, bootstrapFile, serviceType, list, info, logs, tail, sleep, wake, destroy, resize, resizeVcpu, execute, command, dumpBootstrap, dumpFile, network, vcpu, unfreezeOnDemand, setUnfreezeOnDemand, setUnfreezeOnDemandEnabled, inputFiles, svcEnvs, svcEnvFile, envAction, envTarget, publicKey, secretKey) return # Execute mode diff --git a/clients/objective-c/sync/src/un.m b/clients/objective-c/sync/src/un.m index 7874a58..ec6592f 100644 --- a/clients/objective-c/sync/src/un.m +++ b/clients/objective-c/sync/src/un.m @@ -1258,6 +1258,15 @@ void cmdSession(NSArray* args) { [YELLOW UTF8String], [RESET UTF8String]); } +/** + * Set unfreeze_on_demand flag for a service. + */ +void setServiceUnfreezeOnDemand(NSString* serviceId, BOOL enabled, NSString* publicKey, NSString* secretKey) { + NSString* endpoint = [NSString stringWithFormat:@"/services/%@", serviceId]; + NSDictionary* payload = @{@"unfreeze_on_demand": @(enabled)}; + apiRequestCLI(endpoint, @"PATCH", payload, publicKey, secretKey); +} + void cmdService(NSArray* args) { NSString* publicKey, *secretKey; UNGetApiKeysCLI(&publicKey, &secretKey); @@ -1274,6 +1283,9 @@ void cmdService(NSArray* args) { NSString* bootstrapFile = nil; NSString* network = nil; int vcpu = 0; + BOOL unfreezeOnDemand = NO; + NSString* setUnfreezeOnDemandId = nil; + BOOL setUnfreezeOnDemandEnabled = NO; NSMutableArray* inputFiles = [NSMutableArray array]; NSMutableArray* envVars = [NSMutableArray array]; NSString* envFile = nil; @@ -1351,9 +1363,23 @@ void cmdService(NSArray* args) { network = args[++i]; } else if ([arg isEqualToString:@"-v"] && i + 1 < [args count]) { vcpu = [args[++i] intValue]; + } else if ([arg isEqualToString:@"--unfreeze-on-demand"]) { + unfreezeOnDemand = YES; + } else if ([arg isEqualToString:@"--set-unfreeze-on-demand"] && i + 2 < [args count]) { + setUnfreezeOnDemandId = args[++i]; + NSString* enabledStr = args[++i]; + setUnfreezeOnDemandEnabled = [enabledStr isEqualToString:@"true"] || [enabledStr isEqualToString:@"1"]; } } + if (setUnfreezeOnDemandId) { + setServiceUnfreezeOnDemand(setUnfreezeOnDemandId, setUnfreezeOnDemandEnabled, publicKey, secretKey); + NSString* status = setUnfreezeOnDemandEnabled ? @"enabled" : @"disabled"; + printf("%sUnfreeze-on-demand %s for service: %s%s\n", + [GREEN UTF8String], [status UTF8String], [setUnfreezeOnDemandId UTF8String], [RESET UTF8String]); + return; + } + if (listMode) { NSDictionary* result = apiRequestCLI(@"/services", @"GET", nil, publicKey, secretKey); NSArray* services = result[@"services"]; @@ -1462,6 +1488,7 @@ void cmdService(NSArray* args) { if (network) payload[@"network"] = network; if (vcpu > 0) payload[@"vcpu"] = @(vcpu); + if (unfreezeOnDemand) payload[@"unfreeze_on_demand"] = @YES; NSDictionary* result = apiRequestCLI(@"/services", @"POST", payload, publicKey, secretKey); printf("%sService created: %s%s\n", [GREEN UTF8String], [result[@"id"] UTF8String], [RESET UTF8String]); diff --git a/clients/ocaml/sync/src/un.ml b/clients/ocaml/sync/src/un.ml index 006fb1f..e6de159 100755 --- a/clients/ocaml/sync/src/un.ml +++ b/clients/ocaml/sync/src/un.ml @@ -1180,8 +1180,25 @@ let session_command action shell network vcpu input_files = Printf.printf "%s\n" response | _ -> () +(* Set unfreeze_on_demand for a service *) +let set_service_unfreeze_on_demand service_id enabled = + let (public_key, secret_key) = get_api_keys () in + let enabled_str = if enabled then "true" else "false" in + let json = Printf.sprintf "{\"unfreeze_on_demand\":%s}" enabled_str in + let endpoint = Printf.sprintf "/services/%s" service_id in + let auth_headers = build_auth_headers public_key secret_key "PATCH" endpoint json in + let tmp_file = Printf.sprintf "/tmp/un_ocaml_%d.json" (Random.int 999999) in + let oc = open_out tmp_file in + output_string oc json; + close_out oc; + let cmd = Printf.sprintf "curl -s -X PATCH %s%s -H 'Content-Type: application/json'%s -d @%s" + api_base endpoint auth_headers tmp_file in + let _ = Sys.command cmd in + Sys.remove tmp_file; + true + (* Service command *) -let service_command action name ports bootstrap bootstrap_file service_type network vcpu input_files envs env_file = +let service_command action name ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand input_files envs env_file = let api_key = get_api_key () in match action with | "env" -> @@ -1262,6 +1279,16 @@ let service_command action name ports bootstrap bootstrap_file service_type netw | (None, _) -> Printf.fprintf stderr "Error: --resize requires service ID\n"; exit 1) + | "set_unfreeze_on_demand" -> + (match (name, ports) with + | (Some sid, Some enabled_str) -> + let enabled = enabled_str = "true" || enabled_str = "1" in + let _ = set_service_unfreeze_on_demand sid enabled in + let status = if enabled then "enabled" else "disabled" in + Printf.printf "%sUnfreeze-on-demand %s for service: %s%s\n" green status sid reset + | _ -> + Printf.fprintf stderr "Error: --set-unfreeze-on-demand requires service ID and enabled (true/false)\n"; + exit 1) | "execute" -> (match name with | Some sid -> @@ -1316,8 +1343,9 @@ let service_command action name ports bootstrap bootstrap_file service_type netw let service_type_json = match service_type with Some t -> Printf.sprintf ",\"service_type\":\"%s\"" t | None -> "" in let network_json = match network with Some net -> Printf.sprintf ",\"network\":\"%s\"" net | None -> "" in let vcpu_json = match vcpu with Some v -> Printf.sprintf ",\"vcpu\":%d" v | None -> "" in + let unfreeze_on_demand_json = if unfreeze_on_demand then ",\"unfreeze_on_demand\":true" else "" in let input_files_json = build_input_files_json input_files in - let json = Printf.sprintf "{\"name\":\"%s\"%s%s%s%s%s%s%s}" n ports_json bootstrap_json bootstrap_content_json service_type_json network_json vcpu_json input_files_json in + let json = Printf.sprintf "{\"name\":\"%s\"%s%s%s%s%s%s%s%s}" n ports_json bootstrap_json bootstrap_content_json service_type_json network_json vcpu_json unfreeze_on_demand_json input_files_json in let response = curl_post api_key "/services" json in Printf.printf "%sService created%s\n" green reset; Printf.printf "%s\n" response; @@ -1517,36 +1545,38 @@ let () = | _ :: rest -> parse_envs acc rest in let envs = parse_envs [] rest in - let rec parse_service action name ports bootstrap bootstrap_file service_type network vcpu env_file = function - | [] -> service_command action name ports bootstrap bootstrap_file service_type network vcpu input_files envs env_file + let rec parse_service action name ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file = function + | [] -> service_command action name ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand input_files envs env_file | "env" :: env_action :: target :: rest when not (String.length target > 0 && target.[0] = '-') -> - parse_service "env_cmd" (Some env_action) (Some target) bootstrap bootstrap_file service_type network vcpu env_file rest + parse_service "env_cmd" (Some env_action) (Some target) bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest | "env" :: env_action :: rest -> - parse_service "env_cmd" (Some env_action) None bootstrap bootstrap_file service_type network vcpu env_file rest - | "--list" :: rest -> parse_service "list" name ports bootstrap bootstrap_file service_type network vcpu env_file rest - | "--info" :: id :: rest -> parse_service "info" (Some id) ports bootstrap bootstrap_file service_type network vcpu env_file rest - | "--logs" :: id :: rest -> parse_service "logs" (Some id) ports bootstrap bootstrap_file service_type network vcpu env_file rest - | "--freeze" :: id :: rest -> parse_service "sleep" (Some id) ports bootstrap bootstrap_file service_type network vcpu env_file rest - | "--unfreeze" :: id :: rest -> parse_service "wake" (Some id) ports bootstrap bootstrap_file service_type network vcpu env_file rest - | "--destroy" :: id :: rest -> parse_service "destroy" (Some id) ports bootstrap bootstrap_file service_type network vcpu env_file rest - | "--resize" :: id :: rest -> parse_service "resize" (Some id) ports bootstrap bootstrap_file service_type network vcpu env_file rest - | "--vcpu" :: v :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network (Some (int_of_string v)) env_file rest - | "--execute" :: id :: "--command" :: cmd :: rest -> parse_service "execute" (Some id) ports (Some cmd) bootstrap_file service_type network vcpu env_file rest - | "--dump-bootstrap" :: id :: file :: rest -> parse_service "dump_bootstrap" (Some id) ports bootstrap (Some file) service_type network vcpu env_file rest - | "--dump-bootstrap" :: id :: rest -> parse_service "dump_bootstrap" (Some id) ports bootstrap bootstrap_file service_type network vcpu env_file rest - | "--name" :: n :: rest -> parse_service "create" (Some n) ports bootstrap bootstrap_file service_type network vcpu env_file rest - | "--ports" :: p :: rest -> parse_service action name (Some p) bootstrap bootstrap_file service_type network vcpu env_file rest - | "--bootstrap" :: b :: rest -> parse_service action name ports (Some b) bootstrap_file service_type network vcpu env_file rest - | "--bootstrap-file" :: f :: rest -> parse_service action name ports bootstrap (Some f) service_type network vcpu env_file rest - | "--type" :: t :: rest -> parse_service action name ports bootstrap bootstrap_file (Some t) network vcpu env_file rest - | "-n" :: net :: rest -> parse_service action name ports bootstrap bootstrap_file service_type (Some net) vcpu env_file rest - | "-v" :: v :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network (Some (int_of_string v)) env_file rest - | "--env-file" :: f :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network vcpu (Some f) rest - | "-e" :: _ :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network vcpu env_file rest (* skip -e, already parsed *) - | "-f" :: _ :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network vcpu env_file rest (* skip -f, already parsed *) - | _ :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network vcpu env_file rest + parse_service "env_cmd" (Some env_action) None bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest + | "--list" :: rest -> parse_service "list" name ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest + | "--info" :: id :: rest -> parse_service "info" (Some id) ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest + | "--logs" :: id :: rest -> parse_service "logs" (Some id) ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest + | "--freeze" :: id :: rest -> parse_service "sleep" (Some id) ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest + | "--unfreeze" :: id :: rest -> parse_service "wake" (Some id) ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest + | "--destroy" :: id :: rest -> parse_service "destroy" (Some id) ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest + | "--resize" :: id :: rest -> parse_service "resize" (Some id) ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest + | "--vcpu" :: v :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network (Some (int_of_string v)) unfreeze_on_demand env_file rest + | "--execute" :: id :: "--command" :: cmd :: rest -> parse_service "execute" (Some id) ports (Some cmd) bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest + | "--dump-bootstrap" :: id :: file :: rest -> parse_service "dump_bootstrap" (Some id) ports bootstrap (Some file) service_type network vcpu unfreeze_on_demand env_file rest + | "--dump-bootstrap" :: id :: rest -> parse_service "dump_bootstrap" (Some id) ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest + | "--name" :: n :: rest -> parse_service "create" (Some n) ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest + | "--ports" :: p :: rest -> parse_service action name (Some p) bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest + | "--bootstrap" :: b :: rest -> parse_service action name ports (Some b) bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest + | "--bootstrap-file" :: f :: rest -> parse_service action name ports bootstrap (Some f) service_type network vcpu unfreeze_on_demand env_file rest + | "--type" :: t :: rest -> parse_service action name ports bootstrap bootstrap_file (Some t) network vcpu unfreeze_on_demand env_file rest + | "-n" :: net :: rest -> parse_service action name ports bootstrap bootstrap_file service_type (Some net) vcpu unfreeze_on_demand env_file rest + | "-v" :: v :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network (Some (int_of_string v)) unfreeze_on_demand env_file rest + | "--env-file" :: f :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand (Some f) rest + | "--unfreeze-on-demand" :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network vcpu true env_file rest + | "--set-unfreeze-on-demand" :: id :: enabled :: rest -> parse_service "set_unfreeze_on_demand" (Some id) (Some enabled) bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest + | "-e" :: _ :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest (* skip -e, already parsed *) + | "-f" :: _ :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest (* skip -f, already parsed *) + | _ :: rest -> parse_service action name ports bootstrap bootstrap_file service_type network vcpu unfreeze_on_demand env_file rest in - parse_service "create" None None None None None None None None rest + parse_service "create" None None None None None None None false None rest | args -> let rec parse_execute file env_vars artifacts out_dir network vcpu = function | [] -> execute_command file env_vars artifacts out_dir network vcpu diff --git a/clients/perl/sync/src/un.pl b/clients/perl/sync/src/un.pl index 74deab2..8309314 100644 --- a/clients/perl/sync/src/un.pl +++ b/clients/perl/sync/src/un.pl @@ -733,6 +733,17 @@ sub cmd_service { return; } + if ($options->{set_unfreeze_on_demand}) { + my $enabled = ($options->{set_unfreeze_on_demand_enabled} && + ($options->{set_unfreeze_on_demand_enabled} eq 'true' || $options->{set_unfreeze_on_demand_enabled} eq '1')) + ? JSON::PP::true : JSON::PP::false; + my $payload = { unfreeze_on_demand => $enabled }; + api_request("/services/$options->{set_unfreeze_on_demand}", 'PATCH', $payload, $public_key, $secret_key); + my $status = $enabled ? 'enabled' : 'disabled'; + print "${GREEN}Unfreeze-on-demand $status for service: $options->{set_unfreeze_on_demand}${RESET}\n"; + return; + } + if ($options->{execute}) { my $payload = { command => $options->{command} }; my $result = api_request("/services/$options->{execute}/execute", 'POST', $payload, $public_key, $secret_key); @@ -816,6 +827,7 @@ sub cmd_service { } $payload->{network} = $options->{network} if $options->{network}; $payload->{vcpu} = $options->{vcpu} if $options->{vcpu}; + $payload->{unfreeze_on_demand} = JSON::PP::true if $options->{unfreeze_on_demand}; my $result = api_request('/services', 'POST', $payload, $public_key, $secret_key); my $service_id = $result->{id}; @@ -1170,6 +1182,11 @@ sub main { $options{spawn} = $ARGV[++$i]; } elsif ($arg eq '--clone') { $options{clone} = $ARGV[++$i]; + } elsif ($arg eq '--unfreeze-on-demand') { + $options{unfreeze_on_demand} = 1; + } elsif ($arg eq '--set-unfreeze-on-demand') { + $options{set_unfreeze_on_demand} = $ARGV[++$i]; + $options{set_unfreeze_on_demand_enabled} = $ARGV[++$i] if defined $ARGV[$i + 1]; } elsif ($arg =~ /^-/) { print STDERR "${RED}Unknown option: $arg${RESET}\n"; exit 1; diff --git a/clients/php/async/src/UnsandboxAsync.php b/clients/php/async/src/UnsandboxAsync.php index 7e44ec5..d63e421 100644 --- a/clients/php/async/src/UnsandboxAsync.php +++ b/clients/php/async/src/UnsandboxAsync.php @@ -759,6 +759,9 @@ class UnsandboxAsync { if (isset($opts['input_files'])) { $data['input_files'] = $opts['input_files']; } + if (isset($opts['unfreeze_on_demand']) && $opts['unfreeze_on_demand']) { + $data['unfreeze_on_demand'] = true; + } return $this->makeRequest('POST', '/services', $publicKey, $secretKey, $data); } @@ -860,6 +863,20 @@ class UnsandboxAsync { return $this->makeRequest('POST', "/services/{$serviceId}/unlock", $publicKey, $secretKey, []); } + /** + * Set unfreeze_on_demand flag for a service. + * + * @param string $serviceId Service ID + * @param bool $enabled Whether to enable unfreeze on demand + * @param string|null $publicKey Optional API key + * @param string|null $secretKey Optional API secret + * @return PromiseInterface Resolves to response array with update confirmation + */ + public function setUnfreezeOnDemand(string $serviceId, bool $enabled, ?string $publicKey = null, ?string $secretKey = null): PromiseInterface { + [$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey); + return $this->makeRequest('PATCH', "/services/{$serviceId}", $publicKey, $secretKey, ['unfreeze_on_demand' => $enabled]); + } + /** * Get bootstrap logs for a service. * @@ -1860,6 +1877,14 @@ class UnsandboxAsync { return; } + if ($serviceOpts['set_unfreeze_on_demand']) { + $enabled = in_array($serviceOpts['set_unfreeze_on_demand_enabled'], ['true', '1'], true); + $result = $this->setUnfreezeOnDemand($serviceOpts['set_unfreeze_on_demand'], $enabled)->wait(); + $status = $enabled ? 'enabled' : 'disabled'; + echo "Unfreeze-on-demand {$status} for service: " . $serviceOpts['set_unfreeze_on_demand'] . "\n"; + return; + } + // Create new service if (!empty($serviceOpts['name'])) { $createOpts = []; @@ -1875,6 +1900,9 @@ class UnsandboxAsync { if (!empty($serviceOpts['domains'])) { $createOpts['custom_domains'] = explode(',', $serviceOpts['domains']); } + if ($serviceOpts['unfreeze_on_demand']) { + $createOpts['unfreeze_on_demand'] = true; + } // Handle bootstrap from file $bootstrap = $serviceOpts['bootstrap'] ?? ''; @@ -1940,6 +1968,9 @@ class UnsandboxAsync { 'execute_cmd' => null, 'snapshot' => null, 'snapshot_name' => null, + 'unfreeze_on_demand' => false, + 'set_unfreeze_on_demand' => null, + 'set_unfreeze_on_demand_enabled' => null, ]; $i = 0; @@ -2011,6 +2042,13 @@ class UnsandboxAsync { } elseif ($arg === '--snapshot-name') { $i++; $opts['snapshot_name'] = $args[$i] ?? null; + } elseif ($arg === '--unfreeze-on-demand') { + $opts['unfreeze_on_demand'] = true; + } elseif ($arg === '--set-unfreeze-on-demand') { + $i++; + $opts['set_unfreeze_on_demand'] = $args[$i] ?? null; + $i++; + $opts['set_unfreeze_on_demand_enabled'] = $args[$i] ?? null; } $i++; diff --git a/clients/php/sync/src/un.php b/clients/php/sync/src/un.php index 9238378..6be16d1 100644 --- a/clients/php/sync/src/un.php +++ b/clients/php/sync/src/un.php @@ -1065,6 +1065,9 @@ class Unsandbox { if (isset($opts['input_files'])) { $data['input_files'] = $opts['input_files']; } + if (isset($opts['unfreeze_on_demand']) && $opts['unfreeze_on_demand']) { + $data['unfreeze_on_demand'] = true; + } return $this->makeRequest('POST', '/services', $publicKey, $secretKey, $data); } @@ -1180,6 +1183,22 @@ class Unsandbox { return $this->makeRequest('POST', "/services/{$serviceId}/unlock", $publicKey, $secretKey, []); } + /** + * Set unfreeze_on_demand flag for a service. + * + * @param string $serviceId Service ID + * @param bool $enabled Whether to enable unfreeze on demand + * @param string|null $publicKey Optional API key + * @param string|null $secretKey Optional API secret + * @return array Response array with update confirmation + * @throws CredentialsException Missing credentials + * @throws ApiException API request failed + */ + public function setUnfreezeOnDemand(string $serviceId, bool $enabled, ?string $publicKey = null, ?string $secretKey = null): array { + [$publicKey, $secretKey] = $this->resolveCredentials($publicKey, $secretKey); + return $this->makeRequest('PATCH', "/services/{$serviceId}", $publicKey, $secretKey, ['unfreeze_on_demand' => $enabled]); + } + /** * Get bootstrap logs for a service. * @@ -2227,6 +2246,14 @@ class Unsandbox { return; } + if ($serviceOpts['set_unfreeze_on_demand']) { + $enabled = in_array($serviceOpts['set_unfreeze_on_demand_enabled'], ['true', '1'], true); + $result = $this->setUnfreezeOnDemand($serviceOpts['set_unfreeze_on_demand'], $enabled); + $status = $enabled ? 'enabled' : 'disabled'; + echo "Unfreeze-on-demand {$status} for service: " . $serviceOpts['set_unfreeze_on_demand'] . "\n"; + return; + } + // Create new service if (!empty($serviceOpts['name'])) { $createOpts = []; @@ -2242,6 +2269,9 @@ class Unsandbox { if (!empty($serviceOpts['domains'])) { $createOpts['custom_domains'] = explode(',', $serviceOpts['domains']); } + if ($serviceOpts['unfreeze_on_demand']) { + $createOpts['unfreeze_on_demand'] = true; + } // Handle bootstrap from file $bootstrap = $serviceOpts['bootstrap'] ?? ''; @@ -2307,6 +2337,9 @@ class Unsandbox { 'execute_cmd' => null, 'snapshot' => null, 'snapshot_name' => null, + 'unfreeze_on_demand' => false, + 'set_unfreeze_on_demand' => null, + 'set_unfreeze_on_demand_enabled' => null, ]; $i = 0; @@ -2378,6 +2411,13 @@ class Unsandbox { } elseif ($arg === '--snapshot-name') { $i++; $opts['snapshot_name'] = $args[$i] ?? null; + } elseif ($arg === '--unfreeze-on-demand') { + $opts['unfreeze_on_demand'] = true; + } elseif ($arg === '--set-unfreeze-on-demand') { + $i++; + $opts['set_unfreeze_on_demand'] = $args[$i] ?? null; + $i++; + $opts['set_unfreeze_on_demand_enabled'] = $args[$i] ?? null; } $i++; diff --git a/clients/powershell/sync/src/un.ps1 b/clients/powershell/sync/src/un.ps1 index 7be2a4f..24f2714 100644 --- a/clients/powershell/sync/src/un.ps1 +++ b/clients/powershell/sync/src/un.ps1 @@ -765,6 +765,26 @@ function Invoke-Service { return } + if ($Args -contains "--set-unfreeze-on-demand") { + $idx = [array]::IndexOf($Args, "--set-unfreeze-on-demand") + $serviceId = $Args[$idx + 1] + $enabled = $Args[$idx + 2] + + if ($enabled -eq "true" -or $enabled -eq "1") { + $payload = @{ unfreeze_on_demand = $true } | ConvertTo-Json + Invoke-Api -Endpoint "/services/$serviceId" -Method "PATCH" -Body $payload + Write-Host "`e[32mUnfreeze-on-demand enabled for service: $serviceId`e[0m" + } elseif ($enabled -eq "false" -or $enabled -eq "0") { + $payload = @{ unfreeze_on_demand = $false } | ConvertTo-Json + Invoke-Api -Endpoint "/services/$serviceId" -Method "PATCH" -Body $payload + Write-Host "`e[32mUnfreeze-on-demand disabled for service: $serviceId`e[0m" + } else { + Write-Error "Error: --set-unfreeze-on-demand requires true/false or 1/0" + exit 1 + } + return + } + if ($Args -contains "--dump-bootstrap") { $idx = [array]::IndexOf($Args, "--dump-bootstrap") $serviceId = $Args[$idx + 1] @@ -829,6 +849,10 @@ function Invoke-Service { $payload["service_type"] = $Args[$tIdx + 1] } + if ($Args -contains "--unfreeze-on-demand") { + $payload["unfreeze_on_demand"] = $true + } + # Parse input files $inputFiles = @() for ($i = 0; $i -lt $Args.Count; $i++) { @@ -917,6 +941,7 @@ Service options: --ports PORTS Comma-separated ports --type TYPE Service type (minecraft, mumble, teamspeak, source, tcp, udp) --bootstrap CMD Bootstrap command + --unfreeze-on-demand Enable unfreeze-on-demand for new service -f FILE Input file (can be repeated) -e KEY=VALUE Environment variable for vault (can be repeated) --env-file FILE Load vault variables from file @@ -927,6 +952,7 @@ Service options: --unfreeze ID Unfreeze service --destroy ID Destroy service --resize ID Resize service (requires --vcpu or -v) + --set-unfreeze-on-demand ID true|false Enable/disable unfreeze-on-demand --dump-bootstrap ID Dump bootstrap script from service --dump-file FILE Save bootstrap to file (with --dump-bootstrap) diff --git a/clients/prolog/sync/src/un.pro b/clients/prolog/sync/src/un.pro index d0fa74a..f9db27b 100644 --- a/clients/prolog/sync/src/un.pro +++ b/clients/prolog/sync/src/un.pro @@ -246,6 +246,19 @@ service_resize(ServiceId, Vcpu) :- [Vcpu, ServiceId, SecretKey, ServiceId, PublicKey, Vcpu, Ram]), shell(Cmd, 0). +% Service set unfreeze on demand +service_set_unfreeze_on_demand(ServiceId, Enabled) :- + get_public_key(PublicKey), + get_secret_key(SecretKey), + ( Enabled = true + -> EnabledStr = 'true', Msg = 'enabled' + ; EnabledStr = 'false', Msg = 'disabled' + ), + format(atom(Cmd), + 'BODY=\'\'{\"unfreeze_on_demand\":~w}\'\'; TIMESTAMP=$(date +%s); MESSAGE="$TIMESTAMP:PATCH:/services/~w:$BODY"; SIGNATURE=$(echo -n "$MESSAGE" | openssl dgst -sha256 -hmac "~w" -hex | sed \'\'s/.*= //\'\'); curl -s -X PATCH https://api.unsandbox.com/services/~w -H "Content-Type: application/json" -H "Authorization: Bearer ~w" -H "X-Timestamp: $TIMESTAMP" -H "X-Signature: $SIGNATURE" -d "$BODY" >/dev/null && echo -e "\\x1b[32mUnfreeze-on-demand ~w for service: ~w\\x1b[0m"', + [EnabledStr, ServiceId, SecretKey, ServiceId, PublicKey, Msg, ServiceId]), + shell(Cmd, 0). + % Service env status service_env_status(ServiceId) :- get_public_key(PublicKey), @@ -650,6 +663,14 @@ parse_service_args(['--resize', ServiceId, '-v', VcpuAtom|_], _, _, _, _, _, _, parse_service_args(['--resize', _|_], _, _, _, _, _, _, _, _, _, _) :- write(user_error, '\x1b[31mError: --resize requires --vcpu or -v\x1b[0m\n'), halt(1). +parse_service_args(['--set-unfreeze-on-demand', ServiceId, EnabledAtom|_], _, _, _, _, _, _, _, _, _, _) :- + ( (EnabledAtom = 'true' ; EnabledAtom = '1') + -> service_set_unfreeze_on_demand(ServiceId, true) + ; (EnabledAtom = 'false' ; EnabledAtom = '0') + -> service_set_unfreeze_on_demand(ServiceId, false) + ; write(user_error, '\x1b[31mError: --set-unfreeze-on-demand requires true/false or 1/0\x1b[0m\n'), + halt(1) + ). parse_service_args(['--dump-bootstrap', ServiceId|Rest], _, _, _, _, _, _, _, _, _, _) :- ( Rest = ['--dump-file', DumpFile|_] -> service_dump_bootstrap(ServiceId, DumpFile) diff --git a/clients/r/sync/src/un.r b/clients/r/sync/src/un.r index 9e15e3f..e17ec0e 100644 --- a/clients/r/sync/src/un.r +++ b/clients/r/sync/src/un.r @@ -1460,6 +1460,19 @@ cmd_service <- function(args) { return() } + if (!is.null(args$set_unfreeze_on_demand)) { + if (is.null(args$set_unfreeze_on_demand_val)) { + cat(sprintf("%sError: --set-unfreeze-on-demand requires true/false or 1/0%s\n", RED, RESET), file = stderr()) + quit(status = 1) + } + enabled <- args$set_unfreeze_on_demand_val %in% c("true", "1") + payload <- list(unfreeze_on_demand = enabled) + result <- api_request(paste0("/services/", args$set_unfreeze_on_demand), public_key, secret_key, method = "PATCH", data = payload) + status_msg <- if (enabled) "enabled" else "disabled" + cat(sprintf("%sUnfreeze-on-demand %s for service: %s%s\n", GREEN, status_msg, args$set_unfreeze_on_demand, RESET)) + return() + } + if (!is.null(args$snapshot_svc)) { payload <- list() if (!is.null(args$snapshot_name)) { @@ -1550,6 +1563,10 @@ cmd_service <- function(args) { payload$vcpu <- args$vcpu } + if (!is.null(args$unfreeze_on_demand) && args$unfreeze_on_demand) { + payload$unfreeze_on_demand <- TRUE + } + # Add input files if (!is.null(args$files)) { input_files <- list() @@ -1628,6 +1645,9 @@ parse_args <- function() { shell = NULL, dump_bootstrap = NULL, dump_file = NULL, + set_unfreeze_on_demand = NULL, + set_unfreeze_on_demand_val = NULL, + unfreeze_on_demand = FALSE, name = NULL, ports = NULL, domains = NULL, @@ -1764,6 +1784,17 @@ parse_args <- function() { i <- i + 1 result$dump_file <- args[i] i <- i + 1 + } else if (arg == "--set-unfreeze-on-demand") { + i <- i + 1 + result$set_unfreeze_on_demand <- args[i] + i <- i + 1 + if (i <= length(args)) { + result$set_unfreeze_on_demand_val <- args[i] + i <- i + 1 + } + } else if (arg == "--unfreeze-on-demand") { + result$unfreeze_on_demand <- TRUE + i <- i + 1 } else if (arg == "--name") { i <- i + 1 result$name <- args[i] diff --git a/clients/raku/sync/src/un.raku b/clients/raku/sync/src/un.raku index 83bf909..6f397e8 100644 --- a/clients/raku/sync/src/un.raku +++ b/clients/raku/sync/src/un.raku @@ -924,6 +924,8 @@ sub cmd-service(@args) { my $wake-id = ''; my $destroy-id = ''; my $resize-id = ''; + my $set-unfreeze-on-demand-id = ''; + my $set-unfreeze-on-demand-val = False; my $name = ''; my $ports = ''; my $type = ''; @@ -931,6 +933,7 @@ sub cmd-service(@args) { my $bootstrap-file = ''; my $network = ''; my $vcpu = 0; + my $unfreeze-on-demand = False; my @input-files; # Parse arguments @@ -964,6 +967,16 @@ sub cmd-service(@args) { $i++; $resize-id = @args[$i]; } + when '--set-unfreeze-on-demand' { + $i++; + $set-unfreeze-on-demand-id = @args[$i]; + $i++; + my $val = @args[$i]; + $set-unfreeze-on-demand-val = ($val eq 'true' || $val eq '1'); + } + when '--unfreeze-on-demand' { + $unfreeze-on-demand = True; + } when '--name' { $i++; $name = @args[$i]; @@ -1059,6 +1072,14 @@ sub cmd-service(@args) { return; } + if $set-unfreeze-on-demand-id { + my %payload = unfreeze_on_demand => $set-unfreeze-on-demand-val; + api-request("/services/$set-unfreeze-on-demand-id", 'PATCH', %payload, :$public-key, :$secret-key); + my $status = $set-unfreeze-on-demand-val ?? 'enabled' !! 'disabled'; + say "{$GREEN}Unfreeze-on-demand $status for service: $set-unfreeze-on-demand-id{$RESET}"; + return; + } + # Create new service if $name { my %payload = name => $name; @@ -1086,6 +1107,7 @@ sub cmd-service(@args) { %payload = $network if $network; %payload = $vcpu if $vcpu > 0; + %payload = True if $unfreeze-on-demand; # Add input files if @input-files { diff --git a/clients/scheme/sync/src/un.scm b/clients/scheme/sync/src/un.scm index c84c348..f5bd877 100644 --- a/clients/scheme/sync/src/un.scm +++ b/clients/scheme/sync/src/un.scm @@ -541,7 +541,8 @@ (display response) (newline)))))) -(define (service-cmd action id name ports bootstrap bootstrap-file type input-files env-vars env-file vcpu) +(define (service-cmd action id name ports bootstrap bootstrap-file type input-files env-vars env-file vcpu . rest) + (define unfreeze-on-demand (and (pair? rest) (car rest))) (let ((api-key (get-api-key))) (cond ((equal? action "list") @@ -571,6 +572,17 @@ (begin (format (current-error-port) "~aError: --resize requires --vcpu N (1-8)~a\n" red reset) (exit 1)))) + ((equal? action "set-unfreeze-on-demand") + (if bootstrap ;; reusing bootstrap as the enabled value + (let* ((enabled (or (equal? bootstrap "true") (equal? bootstrap "1"))) + (enabled-str (if enabled "true" "false")) + (msg (if enabled "enabled" "disabled")) + (json (format #f "{\"unfreeze_on_demand\":~a}" enabled-str))) + (curl-patch api-key (format #f "/services/~a" id) json) + (format #t "~aUnfreeze-on-demand ~a for service: ~a~a\n" green msg id reset)) + (begin + (format (current-error-port) "~aError: --set-unfreeze-on-demand requires true/false or 1/0~a\n" red reset) + (exit 1)))) ((equal? action "env-status") (service-env-status api-key id)) ((equal? action "env-set") @@ -615,8 +627,9 @@ (format #f ",\"bootstrap_content\":\"~a\"" (escape-json (read-file bootstrap-file))) "")) (type-json (if type (format #f ",\"service_type\":\"~a\"" type) "")) + (unfreeze-on-demand-json (if unfreeze-on-demand ",\"unfreeze_on_demand\":true" "")) (input-files-json (build-input-files-json input-files)) - (json (format #f "{\"name\":\"~a\"~a~a~a~a~a}" name ports-json bootstrap-json bootstrap-content-json type-json input-files-json)) + (json (format #f "{\"name\":\"~a\"~a~a~a~a~a~a}" name ports-json bootstrap-json bootstrap-content-json type-json unfreeze-on-demand-json input-files-json)) (response (curl-post api-key "/services" json)) (service-id (json-extract-string response "id"))) (format #t "~aService created~a\n" green reset) @@ -852,6 +865,11 @@ (loop (cddr args))) (else (loop (cdr args)))))) (service-cmd "resize" resize-id #f #f #f #f #f '() '() #f vcpu-val))) + ((and (> (length args) 3) (equal? (cadr args) "--set-unfreeze-on-demand")) + ;; Parse --set-unfreeze-on-demand ID true/false + (let* ((service-id (caddr args)) + (enabled-val (list-ref args 3))) + (service-cmd "set-unfreeze-on-demand" service-id #f #f enabled-val #f #f '() '() #f #f))) ((and (> (length args) 3) (equal? (cadr args) "--execute")) (service-cmd "execute" (caddr args) #f #f (list-ref args 3) #f #f '() '() #f #f)) ((and (> (length args) 3) (equal? (cadr args) "--dump-bootstrap")) @@ -897,33 +915,37 @@ (type #f) (env-vars '()) (env-file #f) + (unfreeze-on-demand-flag #f) (input-files (parse-input-files rest-args))) ;; Parse remaining args (let loop ((args rest-args)) - (when (and (pair? args) (pair? (cdr args))) + (when (pair? args) (cond - ((equal? (car args) "--ports") + ((and (pair? (cdr args)) (equal? (car args) "--ports")) (set! ports (cadr args)) (loop (cddr args))) - ((equal? (car args) "--bootstrap") + ((and (pair? (cdr args)) (equal? (car args) "--bootstrap")) (set! bootstrap (cadr args)) (loop (cddr args))) - ((equal? (car args) "--bootstrap-file") + ((and (pair? (cdr args)) (equal? (car args) "--bootstrap-file")) (set! bootstrap-file (cadr args)) (loop (cddr args))) - ((equal? (car args) "--type") + ((and (pair? (cdr args)) (equal? (car args) "--type")) (set! type (cadr args)) (loop (cddr args))) - ((equal? (car args) "-e") + ((and (pair? (cdr args)) (equal? (car args) "-e")) (set! env-vars (cons (cadr args) env-vars)) (loop (cddr args))) - ((equal? (car args) "--env-file") + ((and (pair? (cdr args)) (equal? (car args) "--env-file")) (set! env-file (cadr args)) (loop (cddr args))) + ((equal? (car args) "--unfreeze-on-demand") + (set! unfreeze-on-demand-flag #t) + (loop (cdr args))) ((equal? (car args) "-f") (loop (cddr args))) ; skip -f, already parsed (else (loop (cdr args)))))) - (service-cmd "create" #f name ports bootstrap bootstrap-file type input-files env-vars env-file #f))) + (service-cmd "create" #f name ports bootstrap bootstrap-file type input-files env-vars env-file #f unfreeze-on-demand-flag))) (else (display "Error: Invalid service command\n" (current-error-port)) (exit 1)))) diff --git a/clients/swift/sync/src/un.swift b/clients/swift/sync/src/un.swift index 0fb9cb6..7d4af39 100644 --- a/clients/swift/sync/src/un.swift +++ b/clients/swift/sync/src/un.swift @@ -637,6 +637,7 @@ func createService( customDomains: [String]? = nil, vcpu: Int = 1, serviceType: String? = nil, + unfreezeOnDemand: Bool = false, publicKey: String? = nil, secretKey: String? = nil ) throws -> [String: Any] { @@ -664,6 +665,9 @@ func createService( if let serviceType = serviceType { data["service_type"] = serviceType } + if unfreezeOnDemand { + data["unfreeze_on_demand"] = unfreezeOnDemand + } return try makeRequest(method: "POST", path: "/services", publicKey: pk, secretKey: sk, data: data) } @@ -714,6 +718,12 @@ func unlockService(_ serviceId: String, publicKey: String? = nil, secretKey: Str return try makeRequest(method: "POST", path: "/services/\(serviceId)/unlock", publicKey: pk, secretKey: sk, data: [:]) } +/// Enable or disable automatic unfreezing on incoming requests +func setUnfreezeOnDemand(_ serviceId: String, enabled: Bool, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] { + let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey) + return try makeRequest(method: "PATCH", path: "/services/\(serviceId)", publicKey: pk, secretKey: sk, data: ["unfreeze_on_demand": enabled]) +} + /// Get bootstrap/runtime logs for a service func getServiceLogs(_ serviceId: String, allLogs: Bool = false, publicKey: String? = nil, secretKey: String? = nil) throws -> [String: Any] { let (pk, sk) = try resolveCredentials(publicKey: publicKey, secretKey: secretKey) diff --git a/clients/tcl/sync/src/un.tcl b/clients/tcl/sync/src/un.tcl index 69c7d62..a1bea10 100755 --- a/clients/tcl/sync/src/un.tcl +++ b/clients/tcl/sync/src/un.tcl @@ -979,6 +979,9 @@ proc cmd_service {args} { set env_file "" set env_action "" set env_target "" + set unfreeze_on_demand 0 + set unfreeze_on_demand_id "" + set unfreeze_on_demand_enabled "" # Parse arguments for {set i 0} {$i < [llength $args]} {incr i} { @@ -1076,6 +1079,15 @@ proc cmd_service {args} { incr i set env_file [lindex $args $i] } + --unfreeze-on-demand { + set unfreeze_on_demand 1 + } + --set-unfreeze-on-demand { + incr i + set unfreeze_on_demand_id [lindex $args $i] + incr i + set unfreeze_on_demand_enabled [lindex $args $i] + } } } @@ -1085,6 +1097,19 @@ proc cmd_service {args} { return } + # Handle set-unfreeze-on-demand + if {$unfreeze_on_demand_id ne ""} { + set enabled_val [expr {$unfreeze_on_demand_enabled eq "true" || $unfreeze_on_demand_enabled eq "1"}] + set payload [list unfreeze_on_demand [::json::write string [expr {$enabled_val ? "true" : "false"}]]] + api_request "/services/$unfreeze_on_demand_id" "PATCH" $payload $public_key $secret_key + if {$enabled_val} { + puts "${::GREEN}Unfreeze-on-demand enabled for service $unfreeze_on_demand_id${::RESET}" + } else { + puts "${::GREEN}Unfreeze-on-demand disabled for service $unfreeze_on_demand_id${::RESET}" + } + return + } + if {$list_mode} { set result [api_request "/services" "GET" {} $public_key $secret_key] set services [dict get $result services] @@ -1212,6 +1237,9 @@ proc cmd_service {args} { if {$vcpu > 0} { lappend payload vcpu $vcpu } + if {$unfreeze_on_demand} { + lappend payload unfreeze_on_demand true + } # Add input files if {[llength $input_files] > 0} { diff --git a/clients/v/sync/src/un.v b/clients/v/sync/src/un.v index 6c6469c..9102c8c 100644 --- a/clients/v/sync/src/un.v +++ b/clients/v/sync/src/un.v @@ -411,7 +411,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, bootstrap_file string, list bool, info string, logs string, tail string, sleep string, wake string, destroy string, resize string, execute string, command string, dump_bootstrap string, dump_file string, network string, vcpu int, input_files []string, svc_envs []string, svc_env_file string, api_key string) { +fn cmd_service(name string, ports string, service_type string, bootstrap string, bootstrap_file string, list bool, info string, logs string, tail string, sleep string, wake string, destroy string, resize string, execute string, command string, dump_bootstrap string, dump_file string, network string, vcpu int, input_files []string, svc_envs []string, svc_env_file string, unfreeze_on_demand bool, set_unfreeze_on_demand_id string, set_unfreeze_on_demand_enabled string, api_key string) { pub_key := get_public_key() secret_key := get_secret_key() @@ -489,6 +489,21 @@ fn cmd_service(name string, ports string, service_type string, bootstrap string, return } + // Handle set_unfreeze_on_demand + if set_unfreeze_on_demand_id != '' { + enabled := set_unfreeze_on_demand_enabled == 'true' || set_unfreeze_on_demand_enabled == '1' + enabled_str := if enabled { 'true' } else { 'false' } + json := '{"unfreeze_on_demand":${enabled_str}}' + cmd := "BODY='${json}'; TIMESTAMP=\$(date +%s); MESSAGE=\"\$TIMESTAMP:PATCH:/services/${set_unfreeze_on_demand_id}:\$BODY\"; SIGNATURE=\$(echo -n \"\$MESSAGE\" | openssl dgst -sha256 -hmac '${secret_key}' -hex | sed 's/.*= //'); curl -s -X PATCH '${api_base}/services/${set_unfreeze_on_demand_id}' -H 'Content-Type: application/json' -H 'Authorization: Bearer ${pub_key}' -H \"X-Timestamp: \$TIMESTAMP\" -H \"X-Signature: \$SIGNATURE\" -d \"\$BODY\"" + exec_curl(cmd) + if enabled { + println('${green}Unfreeze-on-demand enabled for service ${set_unfreeze_on_demand_id}${reset}') + } else { + println('${green}Unfreeze-on-demand disabled for service ${set_unfreeze_on_demand_id}${reset}') + } + return + } + if dump_bootstrap != '' { eprintln('Fetching bootstrap script from ${dump_bootstrap}...') json := '{"command":"cat /tmp/bootstrap.sh"}' @@ -543,6 +558,9 @@ fn cmd_service(name string, ports string, service_type string, bootstrap string, if vcpu > 0 { json += ',"vcpu":${vcpu}' } + if unfreeze_on_demand { + json += ',"unfreeze_on_demand":true' + } json += build_input_files_json(input_files) json += '}' @@ -960,6 +978,9 @@ fn main() { mut svc_env_file := '' mut env_action := '' mut env_target := '' + mut unfreeze_on_demand := false + mut set_unfreeze_on_demand_id := '' + mut set_unfreeze_on_demand_enabled := '' mut i := 2 for i < os.args.len { @@ -1046,6 +1067,15 @@ fn main() { i++ svc_env_file = os.args[i] } + '--unfreeze-on-demand' { unfreeze_on_demand = true } + '--set-unfreeze-on-demand' { + i++ + set_unfreeze_on_demand_id = os.args[i] + i++ + if i < os.args.len { + set_unfreeze_on_demand_enabled = os.args[i] + } + } '-n' { i++ network = os.args[i] @@ -1080,7 +1110,7 @@ fn main() { } cmd_service(name, ports, service_type, bootstrap, bootstrap_file, list, info, logs, tail, sleep, wake, destroy, resize, execute, command, dump_bootstrap, dump_file, network, - vcpu, input_files, svc_envs, svc_env_file, api_key) + vcpu, input_files, svc_envs, svc_env_file, unfreeze_on_demand, set_unfreeze_on_demand_id, set_unfreeze_on_demand_enabled, api_key) return } diff --git a/clients/zig/sync/src/un.zig b/clients/zig/sync/src/un.zig index 285a937..1a38441 100644 --- a/clients/zig/sync/src/un.zig +++ b/clients/zig/sync/src/un.zig @@ -430,6 +430,9 @@ pub fn main() !u8 { var dump_file: ?[]const u8 = null; var resize: ?[]const u8 = null; var vcpu: i32 = 0; + var unfreeze_on_demand = false; + var set_unfreeze_on_demand_id: ?[]const u8 = null; + var set_unfreeze_on_demand_enabled: ?[]const u8 = null; var input_files = std.ArrayList([]const u8).init(allocator); defer input_files.deinit(); var svc_envs = std.ArrayList([]const u8).init(allocator); @@ -489,6 +492,13 @@ pub fn main() !u8 { } else if (mem.eql(u8, args[i], "--env-file") and i + 1 < args.len) { i += 1; svc_env_file = args[i]; + } else if (mem.eql(u8, args[i], "--unfreeze-on-demand")) { + unfreeze_on_demand = true; + } else if (mem.eql(u8, args[i], "--set-unfreeze-on-demand") and i + 2 < args.len) { + i += 1; + set_unfreeze_on_demand_id = args[i]; + i += 1; + set_unfreeze_on_demand_enabled = args[i]; } else if (mem.eql(u8, args[i], "-k") and i + 1 < args.len) { i += 1; allocator.free(public_key); @@ -620,6 +630,31 @@ pub fn main() !u8 { // Calculate RAM const ram = vcpu * 2; std.debug.print("\n{s}Service resized to {d} vCPU, {d} GB RAM{s}\n", .{ GREEN, vcpu, ram, RESET }); + } else if (set_unfreeze_on_demand_id) |svc_id| { + // Handle set-unfreeze-on-demand + const enabled = if (set_unfreeze_on_demand_enabled) |e| + mem.eql(u8, e, "true") or mem.eql(u8, e, "1") + else + false; + const enabled_str = if (enabled) "true" else "false"; + const json = try std.fmt.allocPrint(allocator, "{{\"unfreeze_on_demand\":{s}}}", .{enabled_str}); + defer allocator.free(json); + + const path = try std.fmt.allocPrint(allocator, "/services/{s}", .{svc_id}); + defer allocator.free(path); + + const auth_headers = try buildAuthCmd(allocator, "PATCH", path, json, public_key, secret_key); + defer allocator.free(auth_headers); + + const cmd = try std.fmt.allocPrint(allocator, "curl -s -X PATCH '{s}/services/{s}' -H 'Content-Type: application/json' {s} -d '{s}'", .{ API_BASE, svc_id, auth_headers, json }); + defer allocator.free(cmd); + _ = std.c.system(cmd.ptr); + + if (enabled) { + std.debug.print("\n{s}Unfreeze-on-demand enabled for service {s}{s}\n", .{ GREEN, svc_id, RESET }); + } else { + std.debug.print("\n{s}Unfreeze-on-demand disabled for service {s}{s}\n", .{ GREEN, svc_id, RESET }); + } } else if (name) |n| { var json_buf: [65536]u8 = undefined; var json_stream = std.io.fixedBufferStream(&json_buf); @@ -666,6 +701,9 @@ pub fn main() !u8 { } try writer.writeAll("\""); } + if (unfreeze_on_demand) { + try writer.writeAll(",\"unfreeze_on_demand\":true"); + } // Add input_files JSON const input_files_json = try buildInputFilesJson(allocator, input_files); defer allocator.free(input_files_json);