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
This commit is contained in:
russell@unturf.com 2026-01-22 17:01:19 -05:00
parent e58adcd5fe
commit 7e56327056
35 changed files with 1047 additions and 104 deletions

View file

@ -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") {

View file

@ -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"

View file

@ -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*)

View file

@ -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".

View file

@ -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<string>& 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<string>& 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<string>& 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<string>& 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<string>& 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<string> files;
vector<string> 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;
}

View file

@ -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 }

View file

@ -407,6 +407,18 @@ class Un
return;
}
if (args.ServiceUnfreezeOnDemand != null)
{
var payload = new Dictionary<string, object>
{
["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)

View file

@ -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;
}

View file

@ -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<void> 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<void> 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<String> 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)

View file

@ -299,6 +299,15 @@ async Task CmdServiceAsync(Args args)
return;
}
if (args.ServiceUnfreezeOnDemand != null)
{
var payload = new Dictionary<string, object> { ["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;
}

View file

@ -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)

View file

@ -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};

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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<String, Any>("command" to args.serviceCommand!!)
val result = apiRequest("/services/${args.serviceExecute}/execute", "POST", payload, publicKey, secretKey)
@ -1145,6 +1158,12 @@ fun parseArgs(args: Array<String>): 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

View file

@ -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"))

View file

@ -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] <source_file>")
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")

View file

@ -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

View file

@ -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]);

View file

@ -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

View file

@ -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;

View file

@ -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++;

View file

@ -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++;

View file

@ -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)

View file

@ -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)

View file

@ -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]

View file

@ -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> = $network if $network;
%payload<vcpu> = $vcpu if $vcpu > 0;
%payload<unfreeze_on_demand> = True if $unfreeze-on-demand;
# Add input files
if @input-files {

View file

@ -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))))

View file

@ -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)

View file

@ -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} {

View file

@ -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
}

View file

@ -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);