feat: add unfreeze_on_demand support to all SDKs

Updated Python, JavaScript, Go, Rust, Ruby, Java, and TypeScript SDKs:
- Add unfreeze_on_demand field to Service struct/class
- Add setUnfreezeOnDemand function to toggle setting via PATCH /services/:id
- Add unfreeze_on_demand parameter to createService function
This commit is contained in:
russell@unturf.com 2026-01-22 16:40:36 -05:00
parent 04060aa4da
commit e58adcd5fe
13 changed files with 435 additions and 10 deletions

View file

@ -1100,9 +1100,10 @@ func ShellSession(creds *Credentials, sessionID, command string) <-chan SessionR
// ServiceOptions contains optional parameters for service creation. // ServiceOptions contains optional parameters for service creation.
type ServiceOptions struct { type ServiceOptions struct {
NetworkMode string // "zerotrust" (default) or "semitrusted" NetworkMode string // "zerotrust" (default) or "semitrusted"
Shell string // Shell to use for bootstrap Shell string // Shell to use for bootstrap
VCPU int // Number of virtual CPUs VCPU int // Number of virtual CPUs
UnfreezeOnDemand bool // Enable automatic unfreezing on HTTP request
} }
// ServiceUpdateOptions contains optional parameters for service updates. // ServiceUpdateOptions contains optional parameters for service updates.
@ -1184,6 +1185,9 @@ func CreateService(creds *Credentials, name string, ports []int, bootstrap strin
if opts.VCPU > 0 { if opts.VCPU > 0 {
data["vcpu"] = opts.VCPU data["vcpu"] = opts.VCPU
} }
if opts.UnfreezeOnDemand {
data["unfreeze_on_demand"] = true
}
} }
response, err := makeRequest("POST", "/services", creds, data) response, err := makeRequest("POST", "/services", creds, data)
@ -1304,6 +1308,23 @@ func UnlockService(creds *Credentials, serviceID string) <-chan ServiceResult {
return resultChan return resultChan
} }
// SetUnfreezeOnDemand enables or disables automatic unfreezing on HTTP request.
// Returns a channel that receives exactly one ServiceResult then closes.
func SetUnfreezeOnDemand(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{}{
"unfreeze_on_demand": enabled,
})
resultChan <- ServiceResult{Data: response, Err: err}
}()
return resultChan
}
// GetServiceLogs retrieves logs from a service. // GetServiceLogs retrieves logs from a service.
// Returns a channel that receives exactly one ServiceResult then closes. // Returns a channel that receives exactly one ServiceResult then closes.
// //

View file

@ -773,9 +773,10 @@ func ShellSession(creds *Credentials, sessionID, command string) (map[string]int
// ServiceOptions contains optional parameters for service creation. // ServiceOptions contains optional parameters for service creation.
type ServiceOptions struct { type ServiceOptions struct {
NetworkMode string // "zerotrust" (default) or "semitrusted" NetworkMode string // "zerotrust" (default) or "semitrusted"
Shell string // Shell to use for bootstrap Shell string // Shell to use for bootstrap
VCPU int // Number of virtual CPUs VCPU int // Number of virtual CPUs
UnfreezeOnDemand bool // Enable automatic unfreezing on HTTP request
} }
// ServiceUpdateOptions contains optional parameters for service updates. // ServiceUpdateOptions contains optional parameters for service updates.
@ -829,6 +830,9 @@ func CreateService(creds *Credentials, name string, ports []int, bootstrap strin
if opts.VCPU > 0 { if opts.VCPU > 0 {
data["vcpu"] = opts.VCPU data["vcpu"] = opts.VCPU
} }
if opts.UnfreezeOnDemand {
data["unfreeze_on_demand"] = true
}
} }
return makeRequest("POST", "/services", creds, data) return makeRequest("POST", "/services", creds, data)
@ -875,6 +879,13 @@ func UnlockService(creds *Credentials, serviceID string) (map[string]interface{}
return makeRequest("POST", fmt.Sprintf("/services/%s/unlock", serviceID), creds, map[string]interface{}{}) return makeRequest("POST", fmt.Sprintf("/services/%s/unlock", serviceID), creds, map[string]interface{}{})
} }
// SetUnfreezeOnDemand enables or disables automatic unfreezing on HTTP request.
func SetUnfreezeOnDemand(creds *Credentials, serviceID string, enabled bool) (map[string]interface{}, error) {
return makeRequest("PATCH", fmt.Sprintf("/services/%s", serviceID), creds, map[string]interface{}{
"unfreeze_on_demand": enabled,
})
}
// GetServiceLogs retrieves logs from a service. // GetServiceLogs retrieves logs from a service.
// //
// Args: // Args:

View file

@ -1352,6 +1352,54 @@ public class UnsandboxAsync {
return makeRequest("POST", "/services", creds[0], creds[1], data); return makeRequest("POST", "/services", creds[0], creds[1], data);
} }
/**
* Create a new service (long-running container) with unfreeze-on-demand option.
*
* @param name Service name (used for hostname)
* @param ports Comma-separated list of ports to expose (e.g., "80,443")
* @param bootstrap Bootstrap script or URL to run on service creation
* @param unfreezeOnDemand If true, frozen service will auto-wake on HTTP request
* @param publicKey Optional API key
* @param secretKey Optional API secret
* @return CompletableFuture containing response map with service_id
*/
public static CompletableFuture<Map<String, Object>> createService(
String name,
String ports,
String bootstrap,
boolean unfreezeOnDemand,
String publicKey,
String secretKey
) {
String[] creds = resolveCredentials(publicKey, secretKey);
Map<String, Object> data = new LinkedHashMap<>();
data.put("name", name);
if (ports != null && !ports.isEmpty()) {
List<Integer> portList = new ArrayList<>();
for (String p : ports.split(",")) {
try {
portList.add(Integer.parseInt(p.trim()));
} catch (NumberFormatException e) {
// Skip invalid port
}
}
data.put("ports", portList);
}
if (bootstrap != null && !bootstrap.isEmpty()) {
if (bootstrap.startsWith("http://") || bootstrap.startsWith("https://")) {
data.put("bootstrap_url", bootstrap);
} else {
data.put("bootstrap", bootstrap);
}
}
if (unfreezeOnDemand) {
data.put("unfreeze_on_demand", true);
}
return makeRequest("POST", "/services", creds[0], creds[1], data);
}
/** /**
* Get details of a specific service. * Get details of a specific service.
* *
@ -1473,6 +1521,30 @@ public class UnsandboxAsync {
return makeRequest("POST", "/services/" + serviceId + "/unlock", creds[0], creds[1], new LinkedHashMap<>()); return makeRequest("POST", "/services/" + serviceId + "/unlock", creds[0], creds[1], new LinkedHashMap<>());
} }
/**
* Set unfreeze-on-demand for a service.
*
* <p>When enabled, a frozen service will automatically wake up when it receives
* an HTTP request, without requiring an explicit unfreeze API call.
*
* @param serviceId Service ID to configure
* @param enabled True to enable unfreeze-on-demand, false to disable
* @param publicKey Optional API key
* @param secretKey Optional API secret
* @return CompletableFuture containing response map with update confirmation
*/
public static CompletableFuture<Map<String, Object>> setUnfreezeOnDemand(
String serviceId,
boolean enabled,
String publicKey,
String secretKey
) {
String[] creds = resolveCredentials(publicKey, secretKey);
Map<String, Object> data = new LinkedHashMap<>();
data.put("unfreeze_on_demand", enabled);
return makeRequestWithMethod("PATCH", "/services/" + serviceId, creds[0], creds[1], data);
}
/** /**
* Get bootstrap logs for a service. * Get bootstrap logs for a service.
* *

View file

@ -1345,6 +1345,58 @@ public class Un {
return makeRequest("POST", "/services", creds[0], creds[1], data); return makeRequest("POST", "/services", creds[0], creds[1], data);
} }
/**
* Create a new service (long-running container) with unfreeze-on-demand option.
*
* @param name Service name (used for hostname)
* @param ports Comma-separated list of ports to expose (e.g., "80,443")
* @param bootstrap Bootstrap script or URL to run on service creation
* @param unfreezeOnDemand If true, frozen service will auto-wake on HTTP request
* @param publicKey Optional API key
* @param secretKey Optional API secret
* @return Response map containing service_id
* @throws IOException on network errors
* @throws CredentialsException if credentials cannot be found
* @throws ApiException if API returns an error
*/
public static Map<String, Object> createService(
String name,
String ports,
String bootstrap,
boolean unfreezeOnDemand,
String publicKey,
String secretKey
) throws IOException {
String[] creds = resolveCredentials(publicKey, secretKey);
Map<String, Object> data = new LinkedHashMap<>();
data.put("name", name);
if (ports != null && !ports.isEmpty()) {
// Parse ports string into list
List<Integer> portList = new ArrayList<>();
for (String p : ports.split(",")) {
try {
portList.add(Integer.parseInt(p.trim()));
} catch (NumberFormatException e) {
// Skip invalid port
}
}
data.put("ports", portList);
}
if (bootstrap != null && !bootstrap.isEmpty()) {
if (bootstrap.startsWith("http://") || bootstrap.startsWith("https://")) {
data.put("bootstrap_url", bootstrap);
} else {
data.put("bootstrap", bootstrap);
}
}
if (unfreezeOnDemand) {
data.put("unfreeze_on_demand", true);
}
return makeRequest("POST", "/services", creds[0], creds[1], data);
}
/** /**
* Get details of a specific service. * Get details of a specific service.
* *
@ -1487,6 +1539,33 @@ public class Un {
return makeRequest("POST", "/services/" + serviceId + "/unlock", creds[0], creds[1], new LinkedHashMap<>()); return makeRequest("POST", "/services/" + serviceId + "/unlock", creds[0], creds[1], new LinkedHashMap<>());
} }
/**
* Set unfreeze-on-demand for a service.
*
* <p>When enabled, a frozen service will automatically wake up when it receives
* an HTTP request, without requiring an explicit unfreeze API call.
*
* @param serviceId Service ID to configure
* @param enabled True to enable unfreeze-on-demand, false to disable
* @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<String, Object> setUnfreezeOnDemand(
String serviceId,
boolean enabled,
String publicKey,
String secretKey
) throws IOException {
String[] creds = resolveCredentials(publicKey, secretKey);
Map<String, Object> data = new LinkedHashMap<>();
data.put("unfreeze_on_demand", enabled);
return makeRequestWithMethod("PATCH", "/services/" + serviceId, creds[0], creds[1], data);
}
/** /**
* Get bootstrap logs for a service. * Get bootstrap logs for a service.
* *

View file

@ -13,7 +13,7 @@
* freezeSession, unfreezeSession, boostSession, unboostSession, shellSession, * freezeSession, unfreezeSession, boostSession, unboostSession, shellSession,
* // Service management * // Service management
* listServices, createService, getService, updateService, deleteService, * listServices, createService, getService, updateService, deleteService,
* freezeService, unfreezeService, lockService, unlockService, * freezeService, unfreezeService, lockService, unlockService, setUnfreezeOnDemand,
* getServiceLogs, getServiceEnv, setServiceEnv, deleteServiceEnv, * getServiceLogs, getServiceEnv, setServiceEnv, deleteServiceEnv,
* exportServiceEnv, redeployService, executeInService, * exportServiceEnv, redeployService, executeInService,
* // Snapshot management * // Snapshot management
@ -751,6 +751,7 @@ async function listServices(publicKey, secretKey) {
* - vcpu: Number of vCPUs (1-8) * - vcpu: Number of vCPUs (1-8)
* - domains: Array of custom domains * - domains: Array of custom domains
* - serviceType: Service type for SRV records (minecraft, mumble, etc.) * - serviceType: Service type for SRV records (minecraft, mumble, etc.)
* - unfreezeOnDemand: If true, frozen services wake automatically on HTTP traffic
* *
* Returns: Promise<Object> (service info with service_id) * Returns: Promise<Object> (service info with service_id)
*/ */
@ -771,6 +772,7 @@ async function createService(name, ports, bootstrap, opts = {}, publicKey, secre
if (opts.vcpu && opts.vcpu > 1) data.vcpu = opts.vcpu; if (opts.vcpu && opts.vcpu > 1) data.vcpu = opts.vcpu;
if (opts.domains) data.custom_domains = opts.domains; if (opts.domains) data.custom_domains = opts.domains;
if (opts.serviceType) data.service_type = opts.serviceType; if (opts.serviceType) data.service_type = opts.serviceType;
if (opts.unfreezeOnDemand) data.unfreeze_on_demand = true;
return makeRequest('POST', '/services', publicKey, secretKey, data); return makeRequest('POST', '/services', publicKey, secretKey, data);
} }
@ -870,6 +872,22 @@ async function unlockService(serviceId, publicKey, secretKey) {
return makeRequest('POST', `/services/${serviceId}/unlock`, publicKey, secretKey, {}); return makeRequest('POST', `/services/${serviceId}/unlock`, publicKey, secretKey, {});
} }
/**
* Set unfreeze-on-demand for a service.
*
* When enabled, frozen services will automatically wake when HTTP traffic arrives.
*
* Args:
* serviceId: Service ID to update
* enabled: true to enable, false to disable
*
* Returns: Promise<Object> (update confirmation)
*/
async function setUnfreezeOnDemand(serviceId, enabled, publicKey, secretKey) {
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
return makeRequest('PATCH', `/services/${serviceId}`, publicKey, secretKey, { unfreeze_on_demand: enabled });
}
/** /**
* Get service logs. * Get service logs.
* *
@ -1127,6 +1145,7 @@ export {
unfreezeService, unfreezeService,
lockService, lockService,
unlockService, unlockService,
setUnfreezeOnDemand,
getServiceLogs, getServiceLogs,
getServiceEnv, getServiceEnv,
setServiceEnv, setServiceEnv,
@ -1185,6 +1204,7 @@ export default {
unfreezeService, unfreezeService,
lockService, lockService,
unlockService, unlockService,
setUnfreezeOnDemand,
getServiceLogs, getServiceLogs,
getServiceEnv, getServiceEnv,
setServiceEnv, setServiceEnv,

View file

@ -13,7 +13,7 @@
* freezeSession, unfreezeSession, boostSession, unboostSession, shellSession, * freezeSession, unfreezeSession, boostSession, unboostSession, shellSession,
* // Service management * // Service management
* listServices, createService, getService, updateService, deleteService, * listServices, createService, getService, updateService, deleteService,
* freezeService, unfreezeService, lockService, unlockService, * freezeService, unfreezeService, lockService, unlockService, setUnfreezeOnDemand,
* getServiceLogs, getServiceEnv, setServiceEnv, deleteServiceEnv, * getServiceLogs, getServiceEnv, setServiceEnv, deleteServiceEnv,
* exportServiceEnv, redeployService, executeInService, * exportServiceEnv, redeployService, executeInService,
* // Snapshot management * // Snapshot management
@ -735,6 +735,7 @@ async function listServices(publicKey, secretKey) {
* - vcpu: Number of vCPUs (1-8) * - vcpu: Number of vCPUs (1-8)
* - domains: Array of custom domains * - domains: Array of custom domains
* - serviceType: Service type for SRV records (minecraft, mumble, etc.) * - serviceType: Service type for SRV records (minecraft, mumble, etc.)
* - unfreezeOnDemand: If true, frozen services wake automatically on HTTP traffic
* *
* Returns: Promise<Object> (service info with service_id) * Returns: Promise<Object> (service info with service_id)
*/ */
@ -755,6 +756,7 @@ async function createService(name, ports, bootstrap, opts = {}, publicKey, secre
if (opts.vcpu && opts.vcpu > 1) data.vcpu = opts.vcpu; if (opts.vcpu && opts.vcpu > 1) data.vcpu = opts.vcpu;
if (opts.domains) data.custom_domains = opts.domains; if (opts.domains) data.custom_domains = opts.domains;
if (opts.serviceType) data.service_type = opts.serviceType; if (opts.serviceType) data.service_type = opts.serviceType;
if (opts.unfreezeOnDemand) data.unfreeze_on_demand = true;
return makeRequest('POST', '/services', publicKey, secretKey, data); return makeRequest('POST', '/services', publicKey, secretKey, data);
} }
@ -854,6 +856,22 @@ async function unlockService(serviceId, publicKey, secretKey) {
return makeRequest('POST', `/services/${serviceId}/unlock`, publicKey, secretKey, {}); return makeRequest('POST', `/services/${serviceId}/unlock`, publicKey, secretKey, {});
} }
/**
* Set unfreeze-on-demand for a service.
*
* When enabled, frozen services will automatically wake when HTTP traffic arrives.
*
* Args:
* serviceId: Service ID to update
* enabled: true to enable, false to disable
*
* Returns: Promise<Object> (update confirmation)
*/
async function setUnfreezeOnDemand(serviceId, enabled, publicKey, secretKey) {
[publicKey, secretKey] = resolveCredentials(publicKey, secretKey);
return makeRequest('PATCH', `/services/${serviceId}`, publicKey, secretKey, { unfreeze_on_demand: enabled });
}
/** /**
* Get service logs. * Get service logs.
* *
@ -1356,6 +1374,7 @@ module.exports = {
unfreezeService, unfreezeService,
lockService, lockService,
unlockService, unlockService,
setUnfreezeOnDemand,
getServiceLogs, getServiceLogs,
getServiceEnv, getServiceEnv,
setServiceEnv, setServiceEnv,

View file

@ -35,6 +35,7 @@ Library Usage:
unfreeze_service, unfreeze_service,
lock_service, lock_service,
unlock_service, unlock_service,
set_unfreeze_on_demand,
get_service_logs, get_service_logs,
get_service_env, get_service_env,
set_service_env, set_service_env,
@ -1046,6 +1047,7 @@ async def create_service(
custom_domains: Optional[List[str]] = None, custom_domains: Optional[List[str]] = None,
vcpu: int = 1, vcpu: int = 1,
service_type: Optional[str] = None, service_type: Optional[str] = None,
unfreeze_on_demand: bool = False,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
""" """
Create a new persistent service. Create a new persistent service.
@ -1060,6 +1062,7 @@ async def create_service(
custom_domains: Optional list of custom domain names custom_domains: Optional list of custom domain names
vcpu: Number of vCPUs (1-8, default 1) vcpu: Number of vCPUs (1-8, default 1)
service_type: Optional service type for SRV records (e.g., "minecraft") service_type: Optional service type for SRV records (e.g., "minecraft")
unfreeze_on_demand: If True, automatically unfreeze service on incoming requests
Returns: Returns:
Response dict containing service_id, etc. Response dict containing service_id, etc.
@ -1087,6 +1090,8 @@ async def create_service(
data["vcpu"] = vcpu data["vcpu"] = vcpu
if service_type: if service_type:
data["service_type"] = service_type data["service_type"] = service_type
if unfreeze_on_demand:
data["unfreeze_on_demand"] = unfreeze_on_demand
return await _make_request("POST", "/services", public_key, secret_key, data) return await _make_request("POST", "/services", public_key, secret_key, data)
@ -1275,6 +1280,42 @@ async def unlock_service(
return await _make_request("POST", f"/services/{service_id}/unlock", public_key, secret_key, {}) return await _make_request("POST", f"/services/{service_id}/unlock", public_key, secret_key, {})
async def set_unfreeze_on_demand(
service_id: str,
enabled: bool,
public_key: Optional[str] = None,
secret_key: Optional[str] = None,
) -> Dict[str, Any]:
"""
Enable or disable automatic unfreezing on incoming requests.
When enabled, a frozen service will automatically wake up when it
receives an incoming HTTP request.
Args:
service_id: Service ID to configure
enabled: True to enable auto-unfreeze, False to disable
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,
{"unfreeze_on_demand": enabled},
)
async def get_service_logs( async def get_service_logs(
service_id: str, service_id: str,
all_logs: bool = False, all_logs: bool = False,

View file

@ -34,6 +34,7 @@ Library Usage:
unfreeze_service, unfreeze_service,
lock_service, lock_service,
unlock_service, unlock_service,
set_unfreeze_on_demand,
get_service_logs, get_service_logs,
get_service_env, get_service_env,
set_service_env, set_service_env,
@ -1060,6 +1061,7 @@ def create_service(
custom_domains: Optional[List[str]] = None, custom_domains: Optional[List[str]] = None,
vcpu: int = 1, vcpu: int = 1,
service_type: Optional[str] = None, service_type: Optional[str] = None,
unfreeze_on_demand: bool = False,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
""" """
Create a new persistent service. Create a new persistent service.
@ -1074,6 +1076,7 @@ def create_service(
custom_domains: Optional list of custom domain names custom_domains: Optional list of custom domain names
vcpu: Number of vCPUs (1-8, default 1) vcpu: Number of vCPUs (1-8, default 1)
service_type: Optional service type for SRV records (e.g., "minecraft") service_type: Optional service type for SRV records (e.g., "minecraft")
unfreeze_on_demand: If True, automatically unfreeze service on incoming requests
Returns: Returns:
Response dict containing service_id, etc. Response dict containing service_id, etc.
@ -1101,6 +1104,8 @@ def create_service(
data["vcpu"] = vcpu data["vcpu"] = vcpu
if service_type: if service_type:
data["service_type"] = service_type data["service_type"] = service_type
if unfreeze_on_demand:
data["unfreeze_on_demand"] = unfreeze_on_demand
return _make_request("POST", "/services", public_key, secret_key, data) return _make_request("POST", "/services", public_key, secret_key, data)
@ -1289,6 +1294,42 @@ def unlock_service(
return _make_request("POST", f"/services/{service_id}/unlock", public_key, secret_key, {}) return _make_request("POST", f"/services/{service_id}/unlock", public_key, secret_key, {})
def set_unfreeze_on_demand(
service_id: str,
enabled: bool,
public_key: Optional[str] = None,
secret_key: Optional[str] = None,
) -> Dict[str, Any]:
"""
Enable or disable automatic unfreezing on incoming requests.
When enabled, a frozen service will automatically wake up when it
receives an incoming HTTP request.
Args:
service_id: Service ID to configure
enabled: True to enable auto-unfreeze, False to disable
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,
{"unfreeze_on_demand": enabled},
)
def get_service_logs( def get_service_logs(
service_id: str, service_id: str,
all_logs: bool = False, all_logs: bool = False,

View file

@ -765,6 +765,7 @@ module UnAsync
# @param vcpu [Integer] Number of vCPUs (1-8) # @param vcpu [Integer] Number of vCPUs (1-8)
# @param custom_domains [Array<String>, nil] Custom domains for the service # @param custom_domains [Array<String>, nil] Custom domains for the service
# @param service_type [String, nil] Service type for SRV records # @param service_type [String, nil] Service type for SRV records
# @param unfreeze_on_demand [Boolean] If true, service will auto-wake on HTTP request (default: false)
# @return [Future<Hash>] Future resolving to response hash with service_id # @return [Future<Hash>] Future resolving to response hash with service_id
# @raise [CredentialsError] If no credentials found (on .value) # @raise [CredentialsError] If no credentials found (on .value)
# @raise [APIError] If API request fails (on .value) # @raise [APIError] If API request fails (on .value)
@ -772,7 +773,7 @@ module UnAsync
# @example # @example
# result = UnAsync.create_service("web", [80, 443], "apt install -y nginx && nginx").value # result = UnAsync.create_service("web", [80, 443], "apt install -y nginx && nginx").value
# puts result["service_id"] # puts result["service_id"]
def create_service(name, ports, bootstrap, public_key: nil, secret_key: nil, network_mode: 'semitrusted', vcpu: 1, custom_domains: nil, service_type: nil) def create_service(name, ports, bootstrap, public_key: nil, secret_key: nil, network_mode: 'semitrusted', vcpu: 1, custom_domains: nil, service_type: nil, unfreeze_on_demand: false)
Future.new do Future.new do
pk, sk = resolve_credentials(public_key, secret_key) pk, sk = resolve_credentials(public_key, secret_key)
data = { data = {
@ -784,6 +785,7 @@ module UnAsync
data[:vcpu] = vcpu if vcpu > 1 data[:vcpu] = vcpu if vcpu > 1
data[:custom_domains] = custom_domains if custom_domains data[:custom_domains] = custom_domains if custom_domains
data[:service_type] = service_type if service_type data[:service_type] = service_type if service_type
data[:unfreeze_on_demand] = unfreeze_on_demand if unfreeze_on_demand
make_request_sync('POST', '/services', pk, sk, data) make_request_sync('POST', '/services', pk, sk, data)
end end
end end
@ -920,6 +922,27 @@ module UnAsync
end end
end end
# Set unfreeze-on-demand for a service
#
# When enabled, a frozen service will automatically wake when it receives an HTTP request.
#
# @param service_id [String] Service ID to update
# @param enabled [Boolean] Whether to enable unfreeze-on-demand
# @param public_key [String, nil] Optional API key
# @param secret_key [String, nil] Optional API secret
# @return [Future<Hash>] 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_unfreeze_on_demand(service_id, true).value
def set_unfreeze_on_demand(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, { unfreeze_on_demand: enabled })
end
end
# Get service logs (bootstrap output) # Get service logs (bootstrap output)
# #
# @param service_id [String] Service ID to get logs for # @param service_id [String] Service ID to get logs for

View file

@ -894,6 +894,7 @@ module Un
# @param vcpu [Integer] Number of vCPUs (1-8) # @param vcpu [Integer] Number of vCPUs (1-8)
# @param custom_domains [Array<String>, nil] Custom domains for the service # @param custom_domains [Array<String>, nil] Custom domains for the service
# @param service_type [String, nil] Service type for SRV records (e.g., "minecraft") # @param service_type [String, nil] Service type for SRV records (e.g., "minecraft")
# @param unfreeze_on_demand [Boolean] If true, service will auto-wake on HTTP request (default: false)
# @return [Hash] Response hash with service_id # @return [Hash] Response hash with service_id
# @raise [CredentialsError] If no credentials found # @raise [CredentialsError] If no credentials found
# @raise [APIError] If API request fails # @raise [APIError] If API request fails
@ -901,7 +902,7 @@ module Un
# @example # @example
# result = Un.create_service("web", [80, 443], "apt install -y nginx && nginx") # result = Un.create_service("web", [80, 443], "apt install -y nginx && nginx")
# puts result["service_id"] # puts result["service_id"]
def create_service(name, ports, bootstrap, public_key: nil, secret_key: nil, network_mode: 'semitrusted', vcpu: 1, custom_domains: nil, service_type: nil) def create_service(name, ports, bootstrap, public_key: nil, secret_key: nil, network_mode: 'semitrusted', vcpu: 1, custom_domains: nil, service_type: nil, unfreeze_on_demand: false)
pk, sk = resolve_credentials(public_key, secret_key) pk, sk = resolve_credentials(public_key, secret_key)
data = { data = {
name: name, name: name,
@ -912,6 +913,7 @@ module Un
data[:vcpu] = vcpu if vcpu > 1 data[:vcpu] = vcpu if vcpu > 1
data[:custom_domains] = custom_domains if custom_domains data[:custom_domains] = custom_domains if custom_domains
data[:service_type] = service_type if service_type data[:service_type] = service_type if service_type
data[:unfreeze_on_demand] = unfreeze_on_demand if unfreeze_on_demand
make_request('POST', '/services', pk, sk, data) make_request('POST', '/services', pk, sk, data)
end end
@ -1033,6 +1035,25 @@ module Un
make_request('POST', "/services/#{service_id}/unlock", pk, sk, {}) make_request('POST', "/services/#{service_id}/unlock", pk, sk, {})
end end
# Set unfreeze-on-demand for a service
#
# When enabled, a frozen service will automatically wake when it receives an HTTP request.
#
# @param service_id [String] Service ID to update
# @param enabled [Boolean] Whether to enable unfreeze-on-demand
# @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_unfreeze_on_demand(service_id, true)
def set_unfreeze_on_demand(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, { unfreeze_on_demand: enabled })
end
# Get service logs (bootstrap output) # Get service logs (bootstrap output)
# #
# @param service_id [String] Service ID to get logs for # @param service_id [String] Service ID to get logs for

View file

@ -316,6 +316,9 @@ pub struct Service {
/// Whether the service is locked (cannot be modified) /// Whether the service is locked (cannot be modified)
#[serde(default)] #[serde(default)]
pub locked: bool, pub locked: bool,
/// Whether the service automatically unfreezes on incoming HTTP requests
#[serde(default)]
pub unfreeze_on_demand: bool,
/// Public URL for the service /// Public URL for the service
#[serde(default)] #[serde(default)]
pub url: String, pub url: String,
@ -382,6 +385,8 @@ pub struct ServiceCreateOptions {
pub bootstrap: Option<String>, pub bootstrap: Option<String>,
/// Bootstrap script URL /// Bootstrap script URL
pub bootstrap_url: Option<String>, pub bootstrap_url: Option<String>,
/// Whether to enable automatic unfreezing on incoming HTTP requests
pub unfreeze_on_demand: Option<bool>,
} }
/// Options for updating a service /// Options for updating a service
@ -1500,6 +1505,9 @@ pub async fn create_service(
if let Some(bootstrap_url) = opts.bootstrap_url { if let Some(bootstrap_url) = opts.bootstrap_url {
body["bootstrap_url"] = serde_json::json!(bootstrap_url); body["bootstrap_url"] = serde_json::json!(bootstrap_url);
} }
if let Some(unfreeze_on_demand) = opts.unfreeze_on_demand {
body["unfreeze_on_demand"] = serde_json::json!(unfreeze_on_demand);
}
} }
make_request("POST", "/services", creds, Some(&body)).await make_request("POST", "/services", creds, Some(&body)).await
@ -1618,6 +1626,26 @@ pub async fn unlock_service(service_id: &str, creds: &Credentials) -> Result<Ser
make_request("POST", &path, creds, Some(&body)).await make_request("POST", &path, creds, Some(&body)).await
} }
/// Set the unfreeze_on_demand flag for a service.
///
/// When enabled, the service will automatically unfreeze when it receives
/// an incoming HTTP request while frozen.
///
/// # Arguments
/// * `service_id` - Service ID to update
/// * `enabled` - Whether to enable automatic unfreezing on demand
/// * `creds` - API credentials
///
/// # Returns
/// Updated Service information
pub async fn set_unfreeze_on_demand(service_id: &str, enabled: bool, creds: &Credentials) -> Result<Service> {
let path = format!("/services/{}", service_id);
let body = serde_json::json!({
"unfreeze_on_demand": enabled
});
make_request("PATCH", &path, creds, Some(&body)).await
}
/// Get bootstrap logs for a service. /// Get bootstrap logs for a service.
/// ///
/// # Arguments /// # Arguments

View file

@ -311,6 +311,9 @@ pub struct Service {
/// Whether the service is locked (cannot be modified) /// Whether the service is locked (cannot be modified)
#[serde(default)] #[serde(default)]
pub locked: bool, pub locked: bool,
/// Whether the service automatically unfreezes on incoming HTTP requests
#[serde(default)]
pub unfreeze_on_demand: bool,
/// Public URL for the service /// Public URL for the service
#[serde(default)] #[serde(default)]
pub url: String, pub url: String,
@ -377,6 +380,8 @@ pub struct ServiceCreateOptions {
pub bootstrap: Option<String>, pub bootstrap: Option<String>,
/// Bootstrap script URL /// Bootstrap script URL
pub bootstrap_url: Option<String>, pub bootstrap_url: Option<String>,
/// Whether to enable automatic unfreezing on incoming HTTP requests
pub unfreeze_on_demand: Option<bool>,
} }
/// Options for updating a service /// Options for updating a service
@ -1807,6 +1812,9 @@ pub fn create_service(
if let Some(bootstrap_url) = opts.bootstrap_url { if let Some(bootstrap_url) = opts.bootstrap_url {
body["bootstrap_url"] = serde_json::json!(bootstrap_url); body["bootstrap_url"] = serde_json::json!(bootstrap_url);
} }
if let Some(unfreeze_on_demand) = opts.unfreeze_on_demand {
body["unfreeze_on_demand"] = serde_json::json!(unfreeze_on_demand);
}
} }
make_request("POST", "/services", creds, Some(&body)) make_request("POST", "/services", creds, Some(&body))
@ -1925,6 +1933,26 @@ pub fn unlock_service(service_id: &str, creds: &Credentials) -> Result<Service>
make_request("POST", &path, creds, Some(&body)) make_request("POST", &path, creds, Some(&body))
} }
/// Set the unfreeze_on_demand flag for a service.
///
/// When enabled, the service will automatically unfreeze when it receives
/// an incoming HTTP request while frozen.
///
/// # Arguments
/// * `service_id` - Service ID to update
/// * `enabled` - Whether to enable automatic unfreezing on demand
/// * `creds` - API credentials
///
/// # Returns
/// Updated Service information
pub fn set_unfreeze_on_demand(service_id: &str, enabled: bool, creds: &Credentials) -> Result<Service> {
let path = format!("/services/{}", service_id);
let body = serde_json::json!({
"unfreeze_on_demand": enabled
});
make_request("PATCH", &path, creds, Some(&body))
}
/// Get bootstrap logs for a service. /// Get bootstrap logs for a service.
/// ///
/// # Arguments /// # Arguments

View file

@ -141,6 +141,8 @@ interface Args {
visibilityMode: string | null; visibilityMode: string | null;
imageSpawn: string | null; imageSpawn: string | null;
imageClone: string | null; imageClone: string | null;
unfreezeOnDemand: boolean | null;
setUnfreezeOnDemand: string | null;
} }
interface ApiKeys { interface ApiKeys {
@ -687,6 +689,14 @@ async function cmdService(args: Args): Promise<void> {
return; return;
} }
if (args.setUnfreezeOnDemand) {
const enabled = args.unfreezeOnDemand === true;
const payload = { unfreeze_on_demand: enabled };
await apiRequest(`/services/${args.setUnfreezeOnDemand}`, "PATCH", payload, keys);
console.log(`${GREEN}Unfreeze on demand ${enabled ? 'enabled' : 'disabled'} for service ${args.setUnfreezeOnDemand}${RESET}`);
return;
}
if (args.execute) { if (args.execute) {
const payload = { command: args.command_arg }; const payload = { command: args.command_arg };
const result = await apiRequest(`/services/${args.execute}/execute`, "POST", payload, keys); const result = await apiRequest(`/services/${args.execute}/execute`, "POST", payload, keys);
@ -755,6 +765,7 @@ async function cmdService(args: Args): Promise<void> {
} }
if (args.network) payload.network = args.network; if (args.network) payload.network = args.network;
if (args.vcpu) payload.vcpu = args.vcpu; if (args.vcpu) payload.vcpu = args.vcpu;
if (args.unfreezeOnDemand) payload.unfreeze_on_demand = true;
const result = await apiRequest("/services", "POST", payload, keys); const result = await apiRequest("/services", "POST", payload, keys);
const serviceId = result.id; const serviceId = result.id;
@ -1010,6 +1021,8 @@ function parseArgs(argv: string[]): Args {
visibilityMode: null, visibilityMode: null,
imageSpawn: null, imageSpawn: null,
imageClone: null, imageClone: null,
unfreezeOnDemand: null,
setUnfreezeOnDemand: null,
}; };
let i = 2; let i = 2;
@ -1170,6 +1183,12 @@ function parseArgs(argv: string[]): Args {
args.clone = argv[++i]; args.clone = argv[++i];
} }
i++; i++;
} else if (arg === '--unfreeze-on-demand') {
args.unfreezeOnDemand = true;
i++;
} else if (arg === '--set-unfreeze-on-demand' && i + 1 < argv.length) {
args.setUnfreezeOnDemand = argv[++i];
i++;
} else if (!arg.startsWith('-')) { } else if (!arg.startsWith('-')) {
args.sourceFile = arg; args.sourceFile = arg;
i++; i++;
@ -1251,6 +1270,8 @@ Service options:
--command CMD Command to execute (with --execute) --command CMD Command to execute (with --execute)
--dump-bootstrap ID Dump bootstrap script --dump-bootstrap ID Dump bootstrap script
--dump-file FILE File to save bootstrap (with --dump-bootstrap) --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
Image options: Image options:
--list List all images --list List all images