From eeb1dcb3dc23e9514e36db7e5c87ae531c2cae5a Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sun, 25 Jan 2026 04:28:12 -0500 Subject: [PATCH] feat: add --show-freeze-page and --no-show-freeze-page CLI flags Add CLI support for toggling the show_freeze_page service setting, which controls whether frozen services display an HTML payment page or return a JSON error response. Updated 18 SDK implementations + CLI_SPEC.md documentation. --- clients/CLI_SPEC.md | 4 ++ clients/c/src/un.c | 77 ++++++++++++++++++++++ clients/csharp/sync/src/Un.cs | 18 +++++ clients/dart/sync/src/un.dart | 18 +++++ clients/dotnet/sync/src/Un.cs | 15 +++++ clients/go/async/src/un_async.go | 18 +++++ clients/java/async/src/UnsandboxAsync.java | 24 +++++++ clients/java/sync/src/Un.java | 27 ++++++++ clients/javascript/async/src/un_async.js | 20 +++++- clients/javascript/sync/src/un.js | 19 +++++- clients/kotlin/sync/src/un.kt | 20 ++++++ clients/perl/sync/src/un.pl | 16 +++++ clients/php/async/src/UnsandboxAsync.php | 14 ++++ clients/php/sync/src/un.php | 16 +++++ clients/python/async/src/un_async.py | 37 +++++++++++ clients/python/sync/src/un.py | 37 +++++++++++ clients/ruby/async/src/un_async.rb | 21 ++++++ clients/ruby/sync/src/un.rb | 19 ++++++ clients/typescript/sync/src/un.ts | 20 ++++++ 19 files changed, 438 insertions(+), 2 deletions(-) diff --git a/clients/CLI_SPEC.md b/clients/CLI_SPEC.md index 137d74c..b272e93 100644 --- a/clients/CLI_SPEC.md +++ b/clients/CLI_SPEC.md @@ -165,6 +165,10 @@ un service --snapshot ID # Create snapshot | `--destroy ID` | Delete service | | `--lock ID` | Prevent deletion | | `--unlock ID` | Allow deletion | +| `--auto-unfreeze ID` | Enable auto-unfreeze on HTTP request | +| `--no-auto-unfreeze ID` | Disable auto-unfreeze | +| `--show-freeze-page ID` | Show payment page when frozen (default) | +| `--no-show-freeze-page ID` | Return JSON error when frozen | | `--resize ID` | Resize (with --vcpu) | | `--redeploy ID` | Re-run bootstrap | | `--execute ID CMD` | Run command | diff --git a/clients/c/src/un.c b/clients/c/src/un.c index 05cd5b7..b6c0c16 100644 --- a/clients/c/src/un.c +++ b/clients/c/src/un.c @@ -3411,6 +3411,67 @@ static int set_unfreeze_on_demand(const UnsandboxCredentials *creds, const char return 0; } +// Set show_freeze_page for a service (controls whether frozen services show payment page or JSON error) +static int set_show_freeze_page(const UnsandboxCredentials *creds, const char *service_id, int enabled) { + CURL *curl = curl_easy_init(); + if (!curl) return 1; + + struct ResponseBuffer response = {0}; + response.data = malloc(1); + response.size = 0; + + char url[512]; + snprintf(url, sizeof(url), "%s/services/%s", API_BASE, service_id); + + char path[256]; + snprintf(path, sizeof(path), "/services/%s", service_id); + + char body[128]; + snprintf(body, sizeof(body), "{\"show_freeze_page\":%s}", enabled ? "true" : "false"); + + struct curl_slist *headers = NULL; + headers = add_hmac_auth_headers(headers, creds, "PATCH", path, body); + headers = curl_slist_append(headers, "Content-Type: application/json"); + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + + CURLcode res = curl_easy_perform(curl); + + long http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + + if (res != CURLE_OK) { + fprintf(stderr, "Error: %s\n", curl_easy_strerror(res)); + free(response.data); + return 1; + } + + if (http_code == 404) { + fprintf(stderr, "Error: Service not found\n"); + free(response.data); + return 1; + } + + if (http_code != 200) { + fprintf(stderr, "Error: HTTP %ld\n", http_code); + if (response.data) fprintf(stderr, "%s\n", response.data); + free(response.data); + return 1; + } + + printf("\033[32mFreeze page %s\033[0m\n", enabled ? "enabled" : "disabled"); + free(response.data); + return 0; +} + // Resize a service (change vCPU/memory live) static int resize_service(const UnsandboxCredentials *creds, const char *service_id, int vcpu) { CURL *curl = curl_easy_init(); @@ -5897,6 +5958,8 @@ void print_usage(const char *prog) { fprintf(stderr, " --unlock ID Unlock a service to allow deletion\n"); fprintf(stderr, " --auto-unfreeze ID Enable auto-unfreeze on HTTP request\n"); fprintf(stderr, " --no-auto-unfreeze ID Disable auto-unfreeze on HTTP request\n"); + fprintf(stderr, " --show-freeze-page ID Enable freeze page (show payment page when frozen)\n"); + fprintf(stderr, " --no-show-freeze-page ID Disable freeze page (return JSON error when frozen)\n"); fprintf(stderr, " --resize ID Resize service vCPU/memory (requires --vcpu)\n"); fprintf(stderr, " --redeploy ID Re-run bootstrap script (optional: --bootstrap or --bootstrap-file)\n"); fprintf(stderr, " --execute ID CMD Run a command in a running service\n"); @@ -9068,6 +9131,8 @@ int main(int argc, char *argv[]) { int do_unlock = 0; int do_auto_unfreeze = 0; int do_no_auto_unfreeze = 0; + int do_show_freeze_page = 0; + int do_no_show_freeze_page = 0; int do_resize = 0; int do_redeploy = 0; int do_execute = 0; @@ -9229,6 +9294,14 @@ int main(int argc, char *argv[]) { do_no_auto_unfreeze = 1; i++; service_id = argv[i]; + } else if (strcmp(argv[i], "--show-freeze-page") == 0 && i + 1 < argc) { + do_show_freeze_page = 1; + i++; + service_id = argv[i]; + } else if (strcmp(argv[i], "--no-show-freeze-page") == 0 && i + 1 < argc) { + do_no_show_freeze_page = 1; + i++; + service_id = argv[i]; } else if (strcmp(argv[i], "--resize") == 0 && i + 1 < argc) { do_resize = 1; i++; @@ -9426,6 +9499,10 @@ int main(int argc, char *argv[]) { ret = set_unfreeze_on_demand(creds, service_id, 1); } else if (do_no_auto_unfreeze) { ret = set_unfreeze_on_demand(creds, service_id, 0); + } else if (do_show_freeze_page) { + ret = set_show_freeze_page(creds, service_id, 1); + } else if (do_no_show_freeze_page) { + ret = set_show_freeze_page(creds, service_id, 0); } else if (do_resize) { if (vcpu < 1 || vcpu > 8) { fprintf(stderr, "Error: --vcpu must be 1-8 for resize\n"); diff --git a/clients/csharp/sync/src/Un.cs b/clients/csharp/sync/src/Un.cs index 40b61b8..c44da42 100644 --- a/clients/csharp/sync/src/Un.cs +++ b/clients/csharp/sync/src/Un.cs @@ -419,6 +419,18 @@ class Un return; } + if (args.ServiceShowFreezePage != null) + { + var payload = new Dictionary + { + ["show_freeze_page"] = args.ServiceShowFreezePageEnabled + }; + ApiRequest($"/services/{args.ServiceShowFreezePage}", "PATCH", payload, publicKey, secretKey); + string status = args.ServiceShowFreezePageEnabled ? "enabled" : "disabled"; + Console.WriteLine($"{GREEN}Show-freeze-page {status} for service: {args.ServiceShowFreezePage}{RESET}"); + return; + } + if (args.ServiceDestroy != null) { ApiRequest($"/services/{args.ServiceDestroy}", "DELETE", null, publicKey, secretKey); @@ -1163,6 +1175,8 @@ class Un public string ServiceDumpFile = null; public string ServiceUnfreezeOnDemand = null; public bool ServiceUnfreezeOnDemandEnabled = true; + public string ServiceShowFreezePage = null; + public bool ServiceShowFreezePageEnabled = true; public bool ServiceCreateUnfreezeOnDemand = false; public string EnvFile = null; public string EnvAction = null; @@ -1222,6 +1236,8 @@ class Un 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 == "--show-freeze-page") result.ServiceShowFreezePage = args[++i]; + else if (arg == "--show-freeze-page-enabled") result.ServiceShowFreezePageEnabled = 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; @@ -1264,6 +1280,8 @@ Service options: --unfreeze ID Unfreeze service --unfreeze-on-demand ID Set unfreeze-on-demand for service --unfreeze-on-demand-enabled BOOL Enable/disable (default: true) + --show-freeze-page ID Set show-freeze-page for service + --show-freeze-page-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 diff --git a/clients/dart/sync/src/un.dart b/clients/dart/sync/src/un.dart index 1cb9ebc..68b3950 100644 --- a/clients/dart/sync/src/un.dart +++ b/clients/dart/sync/src/un.dart @@ -104,6 +104,8 @@ class Args { String? serviceUnfreezeOnDemand; bool serviceUnfreezeOnDemandEnabled = true; bool serviceCreateUnfreezeOnDemand = false; + String? serviceShowFreezePage; + bool serviceShowFreezePageEnabled = true; bool keyExtend = false; String? envFile; String? envAction; @@ -657,6 +659,14 @@ Future cmdService(Args args) async { return; } + if (args.serviceShowFreezePage != null) { + final payload = {'show_freeze_page': args.serviceShowFreezePageEnabled}; + await apiRequestCurl('/services/${args.serviceShowFreezePage}', 'PATCH', jsonEncode(payload), publicKey, secretKey); + final status = args.serviceShowFreezePageEnabled ? 'enabled' : 'disabled'; + print('${green}Show-freeze-page $status for service: ${args.serviceShowFreezePage}$reset'); + return; + } + if (args.serviceDestroy != null) { await apiRequestCurl('/services/${args.serviceDestroy}', 'DELETE', null, publicKey, secretKey); print('${green}Service destroyed: ${args.serviceDestroy}$reset'); @@ -1114,6 +1124,12 @@ Args parseArgs(List argv) { case '--with-unfreeze-on-demand': args.serviceCreateUnfreezeOnDemand = true; break; + case '--show-freeze-page': + args.serviceShowFreezePage = argv[++i]; + break; + case '--show-freeze-page-enabled': + args.serviceShowFreezePageEnabled = argv[++i].toLowerCase() == 'true'; + break; case '--extend': args.keyExtend = true; break; @@ -1228,6 +1244,8 @@ Service options: --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 + --show-freeze-page ID Set show-freeze-page for service + --show-freeze-page-enabled BOOL Enable/disable (default: true) --destroy ID Destroy service --execute ID Execute command in service --command CMD Command to execute (with --execute) diff --git a/clients/dotnet/sync/src/Un.cs b/clients/dotnet/sync/src/Un.cs index c720f14..c76f761 100644 --- a/clients/dotnet/sync/src/Un.cs +++ b/clients/dotnet/sync/src/Un.cs @@ -351,6 +351,15 @@ void CmdService(Args args) return; } + if (args.ServiceShowFreezePage != null) + { + var payload = new Dictionary { ["show_freeze_page"] = args.ServiceShowFreezePageEnabled }; + await ApiRequestAsync($"/services/{args.ServiceShowFreezePage}", new HttpMethod("PATCH"), payload, publicKey, secretKey); + string status = args.ServiceShowFreezePageEnabled ? "enabled" : "disabled"; + Console.WriteLine($"{GREEN}Show-freeze-page {status} for service: {args.ServiceShowFreezePage}{RESET}"); + return; + } + if (args.ServiceDestroy != null) { ApiRequest($"/services/{args.ServiceDestroy}", HttpMethod.Delete, null, publicKey, secretKey); @@ -934,6 +943,8 @@ Args ParseArgs(string[] args) 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 == "--show-freeze-page") result.ServiceShowFreezePage = args[++i]; + else if (arg == "--show-freeze-page-enabled") result.ServiceShowFreezePageEnabled = args[++i].ToLower() == "true"; else if (arg == "--with-unfreeze-on-demand") result.ServiceCreateUnfreezeOnDemand = true; else if (arg == "--extend") result.KeyExtend = true; else if (arg == "--delete") @@ -1016,6 +1027,8 @@ Service options: --snapshot ID Create snapshot from service --unfreeze-on-demand ID Set unfreeze-on-demand for service --unfreeze-on-demand-enabled BOOL Enable/disable (default: true) + --show-freeze-page ID Set show-freeze-page for service + --show-freeze-page-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 @@ -1081,6 +1094,8 @@ class Args public string? ServiceDumpBootstrap, ServiceDumpFile; public string? ServiceUnfreezeOnDemand; public bool ServiceUnfreezeOnDemandEnabled = true; + public string? ServiceShowFreezePage; + public bool ServiceShowFreezePageEnabled = true; public bool ServiceCreateUnfreezeOnDemand; public string? EnvFile, EnvAction, EnvTarget; public bool KeyExtend; diff --git a/clients/go/async/src/un_async.go b/clients/go/async/src/un_async.go index 90ea19e..ced6ea8 100644 --- a/clients/go/async/src/un_async.go +++ b/clients/go/async/src/un_async.go @@ -1325,6 +1325,24 @@ func SetUnfreezeOnDemand(creds *Credentials, serviceID string, enabled bool) <-c return resultChan } +// SetShowFreezePage enables or disables the freeze page display for a service. +// When enabled, frozen services show a branded "waking up" page instead of an error. +// Returns a channel that receives exactly one ServiceResult then closes. +func SetShowFreezePage(creds *Credentials, serviceID string, enabled bool) <-chan ServiceResult { + resultChan := make(chan ServiceResult, 1) + + go func() { + defer close(resultChan) + + response, err := makeRequest("PATCH", fmt.Sprintf("/services/%s", serviceID), creds, map[string]interface{}{ + "show_freeze_page": enabled, + }) + resultChan <- ServiceResult{Data: response, Err: err} + }() + + return resultChan +} + // GetServiceLogs retrieves logs from a service. // Returns a channel that receives exactly one ServiceResult then closes. // diff --git a/clients/java/async/src/UnsandboxAsync.java b/clients/java/async/src/UnsandboxAsync.java index e045f9a..cada1da 100644 --- a/clients/java/async/src/UnsandboxAsync.java +++ b/clients/java/async/src/UnsandboxAsync.java @@ -1545,6 +1545,30 @@ public class UnsandboxAsync { return makeRequestWithMethod("PATCH", "/services/" + serviceId, creds[0], creds[1], data); } + /** + * Set show-freeze-page for a service. + * + *

When enabled, visitors to a frozen service will see a branded "frozen" page + * instead of an error. This improves UX for services that use unfreeze-on-demand. + * + * @param serviceId Service ID to configure + * @param enabled True to show freeze page, false to hide it + * @param publicKey Optional API key + * @param secretKey Optional API secret + * @return CompletableFuture containing response map with update confirmation + */ + public static CompletableFuture> setShowFreezePage( + String serviceId, + boolean enabled, + String publicKey, + String secretKey + ) { + String[] creds = resolveCredentials(publicKey, secretKey); + Map data = new LinkedHashMap<>(); + data.put("show_freeze_page", enabled); + return makeRequestWithMethod("PATCH", "/services/" + serviceId, creds[0], creds[1], data); + } + /** * Get bootstrap logs for a service. * diff --git a/clients/java/sync/src/Un.java b/clients/java/sync/src/Un.java index bc67dac..f8ffedc 100644 --- a/clients/java/sync/src/Un.java +++ b/clients/java/sync/src/Un.java @@ -1566,6 +1566,33 @@ public class Un { return makeRequestWithMethod("PATCH", "/services/" + serviceId, creds[0], creds[1], data); } + /** + * Set show-freeze-page for a service. + * + *

When enabled, visitors to a frozen service will see a branded "frozen" page + * instead of an error. This improves UX for services that use unfreeze-on-demand. + * + * @param serviceId Service ID to configure + * @param enabled True to show freeze page, false to hide it + * @param publicKey Optional API key + * @param secretKey Optional API secret + * @return Response map with update confirmation + * @throws IOException on network errors + * @throws CredentialsException if credentials cannot be found + * @throws ApiException if API returns an error + */ + public static Map setShowFreezePage( + String serviceId, + boolean enabled, + String publicKey, + String secretKey + ) throws IOException { + String[] creds = resolveCredentials(publicKey, secretKey); + Map data = new LinkedHashMap<>(); + data.put("show_freeze_page", enabled); + return makeRequestWithMethod("PATCH", "/services/" + serviceId, creds[0], creds[1], data); + } + /** * Get bootstrap logs for a service. * diff --git a/clients/javascript/async/src/un_async.js b/clients/javascript/async/src/un_async.js index 844e261..504a592 100644 --- a/clients/javascript/async/src/un_async.js +++ b/clients/javascript/async/src/un_async.js @@ -14,7 +14,7 @@ * freezeSession, unfreezeSession, boostSession, unboostSession, shellSession, * // Service management * listServices, createService, getService, updateService, deleteService, - * freezeService, unfreezeService, lockService, unlockService, setUnfreezeOnDemand, + * freezeService, unfreezeService, lockService, unlockService, setUnfreezeOnDemand, setShowFreezePage, * getServiceLogs, getServiceEnv, setServiceEnv, deleteServiceEnv, * exportServiceEnv, redeployService, executeInService, * // Snapshot management @@ -1176,6 +1176,22 @@ async function setUnfreezeOnDemand(serviceId, enabled, publicKey, secretKey) { return makeRequest('PATCH', `/services/${serviceId}`, publicKey, secretKey, { unfreeze_on_demand: enabled }); } +/** + * Set show-freeze-page for a service. + * + * When enabled, frozen services will show a "frozen" page to visitors instead of an error. + * + * Args: + * serviceId: Service ID to update + * enabled: true to enable, false to disable + * + * Returns: Promise (update confirmation) + */ +async function setShowFreezePage(serviceId, enabled, publicKey, secretKey) { + [publicKey, secretKey] = resolveCredentials(publicKey, secretKey); + return makeRequest('PATCH', `/services/${serviceId}`, publicKey, secretKey, { show_freeze_page: enabled }); +} + /** * Get service logs. * @@ -1434,6 +1450,7 @@ export { lockService, unlockService, setUnfreezeOnDemand, + setShowFreezePage, getServiceLogs, getServiceEnv, setServiceEnv, @@ -1493,6 +1510,7 @@ export default { lockService, unlockService, setUnfreezeOnDemand, + setShowFreezePage, getServiceLogs, getServiceEnv, setServiceEnv, diff --git a/clients/javascript/sync/src/un.js b/clients/javascript/sync/src/un.js index db6a95d..70390a4 100644 --- a/clients/javascript/sync/src/un.js +++ b/clients/javascript/sync/src/un.js @@ -14,7 +14,7 @@ * freezeSession, unfreezeSession, boostSession, unboostSession, shellSession, * // Service management * listServices, createService, getService, updateService, deleteService, - * freezeService, unfreezeService, lockService, unlockService, setUnfreezeOnDemand, + * freezeService, unfreezeService, lockService, unlockService, setUnfreezeOnDemand, setShowFreezePage, * getServiceLogs, getServiceEnv, setServiceEnv, deleteServiceEnv, * exportServiceEnv, redeployService, executeInService, * // Snapshot management @@ -1160,6 +1160,22 @@ async function setUnfreezeOnDemand(serviceId, enabled, publicKey, secretKey) { return makeRequest('PATCH', `/services/${serviceId}`, publicKey, secretKey, { unfreeze_on_demand: enabled }); } +/** + * Set show-freeze-page for a service. + * + * When enabled, frozen services will show a "frozen" page to visitors instead of an error. + * + * Args: + * serviceId: Service ID to update + * enabled: true to enable, false to disable + * + * Returns: Promise (update confirmation) + */ +async function setShowFreezePage(serviceId, enabled, publicKey, secretKey) { + [publicKey, secretKey] = resolveCredentials(publicKey, secretKey); + return makeRequest('PATCH', `/services/${serviceId}`, publicKey, secretKey, { show_freeze_page: enabled }); +} + /** * Get service logs. * @@ -1663,6 +1679,7 @@ export { lockService, unlockService, setUnfreezeOnDemand, + setShowFreezePage, getServiceLogs, getServiceEnv, setServiceEnv, diff --git a/clients/kotlin/sync/src/un.kt b/clients/kotlin/sync/src/un.kt index 047b9a7..8d435b0 100644 --- a/clients/kotlin/sync/src/un.kt +++ b/clients/kotlin/sync/src/un.kt @@ -105,6 +105,8 @@ data class Args( var serviceResize: String? = null, var serviceUnfreezeOnDemand: String? = null, var serviceUnfreezeOnDemandValue: Boolean? = null, + var serviceShowFreezePage: String? = null, + var serviceshowFreezePageValue: Boolean? = null, var keyExtend: Boolean = false, var envFile: String? = null, var envAction: String? = null, @@ -376,6 +378,17 @@ fun cmdService(args: Args) { return } + if (args.serviceShowFreezePage != null) { + if (args.serviceshowFreezePageValue == null) { + System.err.println("${RED}Error: --show-freeze-page requires true or false${RESET}") + exitProcess(1) + } + val payload = mapOf("show_freeze_page" to args.serviceshowFreezePageValue!!) + apiRequestPatch("/services/${args.serviceShowFreezePage}", payload, publicKey, secretKey) + println("${GREEN}Service show_freeze_page set to ${args.serviceshowFreezePageValue}: ${args.serviceShowFreezePage}${RESET}") + return + } + if (args.serviceExecute != null) { val payload = mutableMapOf("command" to args.serviceCommand!!) val result = apiRequest("/services/${args.serviceExecute}/execute", "POST", payload, publicKey, secretKey) @@ -1164,6 +1177,12 @@ fun parseArgs(args: Array): Args { result.serviceUnfreezeOnDemandValue = args[++i].toBoolean() } } + "--show-freeze-page" -> { + result.serviceShowFreezePage = args[++i] + if (i + 1 < args.size && (args[i + 1] == "true" || args[i + 1] == "false")) { + result.serviceshowFreezePageValue = args[++i].toBoolean() + } + } "--execute" -> result.serviceExecute = args[++i] "--command" -> result.serviceCommand = args[++i] "--dump-bootstrap" -> result.serviceDumpBootstrap = args[++i] @@ -1264,6 +1283,7 @@ Service options: --destroy ID Destroy service --resize ID Resize service (requires --vcpu N) --unfreeze-on-demand ID true|false Enable/disable auto-unfreeze on HTTP request + --show-freeze-page ID true|false Enable/disable showing freeze page when frozen --execute ID Execute command in service --command CMD Command to execute (with --execute) --dump-bootstrap ID Dump bootstrap script diff --git a/clients/perl/sync/src/un.pl b/clients/perl/sync/src/un.pl index 48eecfb..844ce88 100644 --- a/clients/perl/sync/src/un.pl +++ b/clients/perl/sync/src/un.pl @@ -744,6 +744,17 @@ sub cmd_service { return; } + if ($options->{set_show_freeze_page}) { + my $enabled = ($options->{set_show_freeze_page_enabled} && + ($options->{set_show_freeze_page_enabled} eq 'true' || $options->{set_show_freeze_page_enabled} eq '1')) + ? JSON::PP::true : JSON::PP::false; + my $payload = { show_freeze_page => $enabled }; + api_request("/services/$options->{set_show_freeze_page}", 'PATCH', $payload, $public_key, $secret_key); + my $status = $enabled ? 'enabled' : 'disabled'; + print "${GREEN}Show-freeze-page $status for service: $options->{set_show_freeze_page}${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); @@ -1187,6 +1198,9 @@ sub main { } 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 eq '--set-show-freeze-page') { + $options{set_show_freeze_page} = $ARGV[++$i]; + $options{set_show_freeze_page_enabled} = $ARGV[++$i] if defined $ARGV[$i + 1]; } elsif ($arg =~ /^-/) { print STDERR "${RED}Unknown option: $arg${RESET}\n"; exit 1; @@ -1279,6 +1293,8 @@ Service options: --command CMD Command to execute (with --execute) --dump-bootstrap ID Dump bootstrap script --dump-file FILE File to save bootstrap (with --dump-bootstrap) + --set-unfreeze-on-demand ID BOOL Enable/disable auto-unfreeze on HTTP request + --set-show-freeze-page ID BOOL Enable/disable showing freeze page when frozen Key options: --extend Open browser to extend/renew key diff --git a/clients/php/async/src/UnsandboxAsync.php b/clients/php/async/src/UnsandboxAsync.php index d63e421..a0a48e3 100644 --- a/clients/php/async/src/UnsandboxAsync.php +++ b/clients/php/async/src/UnsandboxAsync.php @@ -877,6 +877,20 @@ class UnsandboxAsync { return $this->makeRequest('PATCH', "/services/{$serviceId}", $publicKey, $secretKey, ['unfreeze_on_demand' => $enabled]); } + /** + * Set show_freeze_page flag for a service. + * + * @param string $serviceId Service ID + * @param bool $enabled Whether to enable show freeze page + * @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 setShowFreezePage(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, ['show_freeze_page' => $enabled]); + } + /** * Get bootstrap logs for a service. * diff --git a/clients/php/sync/src/un.php b/clients/php/sync/src/un.php index 6be16d1..ab08343 100644 --- a/clients/php/sync/src/un.php +++ b/clients/php/sync/src/un.php @@ -1199,6 +1199,22 @@ class Unsandbox { return $this->makeRequest('PATCH', "/services/{$serviceId}", $publicKey, $secretKey, ['unfreeze_on_demand' => $enabled]); } + /** + * Set show_freeze_page flag for a service. + * + * @param string $serviceId Service ID + * @param bool $enabled Whether to enable show freeze page + * @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 setShowFreezePage(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, ['show_freeze_page' => $enabled]); + } + /** * Get bootstrap logs for a service. * diff --git a/clients/python/async/src/un_async.py b/clients/python/async/src/un_async.py index fe3dd2e..8542aa7 100644 --- a/clients/python/async/src/un_async.py +++ b/clients/python/async/src/un_async.py @@ -36,6 +36,7 @@ Library Usage: lock_service, unlock_service, set_unfreeze_on_demand, + set_show_freeze_page, get_service_logs, get_service_env, set_service_env, @@ -1316,6 +1317,42 @@ async def set_unfreeze_on_demand( ) +async def set_show_freeze_page( + service_id: str, + enabled: bool, + public_key: Optional[str] = None, + secret_key: Optional[str] = None, +) -> Dict[str, Any]: + """ + Enable or disable freeze page display for a service. + + When disabled, frozen services return a JSON error response instead of + displaying the payment/unfreeze page to visitors. + + Args: + service_id: Service ID to configure + enabled: True to show freeze page, False to return JSON error + public_key: Optional API key + secret_key: Optional API secret + + Returns: + Response dict with update confirmation + + Raises: + aiohttp.ClientError: Network errors + ValueError: Invalid response format + CredentialsError: Missing credentials + """ + public_key, secret_key = _resolve_credentials(public_key, secret_key) + return await _make_request( + "PATCH", + f"/services/{service_id}", + public_key, + secret_key, + {"show_freeze_page": enabled}, + ) + + async def get_service_logs( service_id: str, all_logs: bool = False, diff --git a/clients/python/sync/src/un.py b/clients/python/sync/src/un.py index 1d271f1..6bfb12b 100644 --- a/clients/python/sync/src/un.py +++ b/clients/python/sync/src/un.py @@ -35,6 +35,7 @@ Library Usage: lock_service, unlock_service, set_unfreeze_on_demand, + set_show_freeze_page, get_service_logs, get_service_env, set_service_env, @@ -1330,6 +1331,42 @@ def set_unfreeze_on_demand( ) +def set_show_freeze_page( + service_id: str, + enabled: bool, + public_key: Optional[str] = None, + secret_key: Optional[str] = None, +) -> Dict[str, Any]: + """ + Enable or disable freeze page display for a service. + + When disabled, frozen services return a JSON error response instead of + displaying the payment/unfreeze page to visitors. + + Args: + service_id: Service ID to configure + enabled: True to show freeze page, False to return JSON error + public_key: Optional API key + secret_key: Optional API secret + + Returns: + Response dict with update confirmation + + Raises: + requests.RequestException: Network errors + ValueError: Invalid response format + CredentialsError: Missing credentials + """ + public_key, secret_key = _resolve_credentials(public_key, secret_key) + return _make_request( + "PATCH", + f"/services/{service_id}", + public_key, + secret_key, + {"show_freeze_page": enabled}, + ) + + def get_service_logs( service_id: str, all_logs: bool = False, diff --git a/clients/ruby/async/src/un_async.rb b/clients/ruby/async/src/un_async.rb index 94740ed..016239a 100644 --- a/clients/ruby/async/src/un_async.rb +++ b/clients/ruby/async/src/un_async.rb @@ -943,6 +943,27 @@ module UnAsync end end + # Set show-freeze-page for a service + # + # When enabled, frozen services show a freeze page instead of 502 error. + # + # @param service_id [String] Service ID to update + # @param enabled [Boolean] Whether to enable show-freeze-page + # @param public_key [String, nil] Optional API key + # @param secret_key [String, nil] Optional API secret + # @return [Future] Future resolving to response hash with update confirmation + # @raise [CredentialsError] If no credentials found (on .value) + # @raise [APIError] If API request fails (on .value) + # + # @example + # UnAsync.set_show_freeze_page(service_id, true).value + def set_show_freeze_page(service_id, enabled, public_key: nil, secret_key: nil) + Future.new do + pk, sk = resolve_credentials(public_key, secret_key) + make_request_sync('PATCH', "/services/#{service_id}", pk, sk, { show_freeze_page: enabled }) + end + end + # Get service logs (bootstrap output) # # @param service_id [String] Service ID to get logs for diff --git a/clients/ruby/sync/src/un.rb b/clients/ruby/sync/src/un.rb index 4bd583d..ca76b92 100644 --- a/clients/ruby/sync/src/un.rb +++ b/clients/ruby/sync/src/un.rb @@ -1054,6 +1054,25 @@ module Un make_request('PATCH', "/services/#{service_id}", pk, sk, { unfreeze_on_demand: enabled }) end + # Set show-freeze-page for a service + # + # When enabled, frozen services show a freeze page instead of 502 error. + # + # @param service_id [String] Service ID to update + # @param enabled [Boolean] Whether to enable show-freeze-page + # @param public_key [String, nil] Optional API key + # @param secret_key [String, nil] Optional API secret + # @return [Hash] Response hash with update confirmation + # @raise [CredentialsError] If no credentials found + # @raise [APIError] If API request fails + # + # @example + # Un.set_show_freeze_page(service_id, true) + def set_show_freeze_page(service_id, enabled, public_key: nil, secret_key: nil) + pk, sk = resolve_credentials(public_key, secret_key) + make_request('PATCH', "/services/#{service_id}", pk, sk, { show_freeze_page: enabled }) + end + # Get service logs (bootstrap output) # # @param service_id [String] Service ID to get logs for diff --git a/clients/typescript/sync/src/un.ts b/clients/typescript/sync/src/un.ts index 97d5b78..3b200f8 100644 --- a/clients/typescript/sync/src/un.ts +++ b/clients/typescript/sync/src/un.ts @@ -143,6 +143,8 @@ interface Args { imageClone: string | null; unfreezeOnDemand: boolean | null; setUnfreezeOnDemand: string | null; + showFreezePage: boolean | null; + setShowFreezePage: string | null; } interface ApiKeys { @@ -697,6 +699,14 @@ async function cmdService(args: Args): Promise { return; } + if (args.setShowFreezePage) { + const enabled = args.showFreezePage === true; + const payload = { show_freeze_page: enabled }; + await apiRequest(`/services/${args.setShowFreezePage}`, "PATCH", payload, keys); + console.log(`${GREEN}Show freeze page ${enabled ? 'enabled' : 'disabled'} for service ${args.setShowFreezePage}${RESET}`); + return; + } + if (args.execute) { const payload = { command: args.command_arg }; const result = await apiRequest(`/services/${args.execute}/execute`, "POST", payload, keys); @@ -1023,6 +1033,8 @@ function parseArgs(argv: string[]): Args { imageClone: null, unfreezeOnDemand: null, setUnfreezeOnDemand: null, + showFreezePage: null, + setShowFreezePage: null, }; let i = 2; @@ -1189,6 +1201,12 @@ function parseArgs(argv: string[]): Args { } else if (arg === '--set-unfreeze-on-demand' && i + 1 < argv.length) { args.setUnfreezeOnDemand = argv[++i]; i++; + } else if (arg === '--show-freeze-page') { + args.showFreezePage = true; + i++; + } else if (arg === '--set-show-freeze-page' && i + 1 < argv.length) { + args.setShowFreezePage = argv[++i]; + i++; } else if (!arg.startsWith('-')) { args.sourceFile = arg; i++; @@ -1272,6 +1290,8 @@ Service options: --dump-file FILE File to save bootstrap (with --dump-bootstrap) --unfreeze-on-demand Enable unfreeze on demand (with --name or --set-unfreeze-on-demand) --set-unfreeze-on-demand ID Set unfreeze on demand for service + --show-freeze-page Enable show freeze page (with --name or --set-show-freeze-page) + --set-show-freeze-page ID Set show freeze page for service Image options: --list List all images