feat: add unfreeze_on_demand support to un CLI

- Add --auto-unfreeze / --no-auto-unfreeze commands to toggle setting
- Add --unfreeze-on-demand flag for service create
- Display Auto-Unfreeze status in service --info output
- Add field to unsandbox_service_t struct and API function in un.h
This commit is contained in:
russell@unturf.com 2026-01-22 16:27:04 -05:00
parent 63b41b571e
commit a6aad7ec7b
2 changed files with 115 additions and 8 deletions

View file

@ -2654,7 +2654,7 @@ static char* get_service_logs(const UnsandboxCredentials *creds, const char *ser
// input_files: array of files to include (written to /tmp/ in container)
// input_file_count: number of input files
// golden_image: optional LXD image alias to use instead of default (for testing)
static char* create_service(const UnsandboxCredentials *creds, const char *name, const char *ports, const char *domains, const char *bootstrap, const char *bootstrap_content, const char *network_mode, int vcpu, const char *service_type, struct InputFile *input_files, int input_file_count, const char *golden_image) {
static char* create_service(const UnsandboxCredentials *creds, const char *name, const char *ports, const char *domains, const char *bootstrap, const char *bootstrap_content, const char *network_mode, int vcpu, const char *service_type, struct InputFile *input_files, int input_file_count, const char *golden_image, int unfreeze_on_demand) {
CURL *curl = curl_easy_init();
if (!curl) return NULL;
@ -2789,6 +2789,12 @@ static char* create_service(const UnsandboxCredentials *creds, const char *name,
p += sprintf(p, "]");
}
// Unfreeze on demand (auto-wake on HTTP request)
if (unfreeze_on_demand) {
if (p > payload + 1) p += sprintf(p, ",");
p += sprintf(p, "\"unfreeze_on_demand\":true");
}
p += sprintf(p, "}");
struct curl_slist *headers = NULL;
@ -3020,17 +3026,21 @@ static int get_service_info(const UnsandboxCredentials *creds, const char *servi
char *name = extract_json_string(response.data, "name");
char *status = extract_json_string(response.data, "status");
char *network_mode = extract_json_string(response.data, "network_mode");
int locked = (int)extract_json_number(response.data, "locked");
int unfreeze_on_demand = (int)extract_json_number(response.data, "unfreeze_on_demand");
printf("Service Information:\n");
printf(" ID: %s\n", id ? id : "-");
printf(" Name: %s\n", name ? name : "-");
printf(" Status: %s\n", status ? status : "-");
printf(" Network Mode: %s\n", network_mode ? network_mode : "-");
printf(" ID: %s\n", id ? id : "-");
printf(" Name: %s\n", name ? name : "-");
printf(" Status: %s\n", status ? status : "-");
printf(" Network Mode: %s\n", network_mode ? network_mode : "-");
printf(" Locked: %s\n", locked ? "yes" : "no");
printf(" Auto-Unfreeze: %s\n", unfreeze_on_demand ? "yes" : "no");
// Extract ports array
const char *ports_start = strstr(response.data, "\"ports\":[");
if (ports_start) {
printf(" Ports: ");
printf(" Ports: ");
const char *pos = ports_start + 9;
const char *ports_end = strchr(pos, ']');
if (ports_end) {
@ -3340,6 +3350,67 @@ static int unlock_service(const UnsandboxCredentials *creds, const char *service
return 0;
}
// Set unfreeze_on_demand for a service
static int set_unfreeze_on_demand(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), "{\"unfreeze_on_demand\":%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[32mAuto-unfreeze %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();
@ -5809,6 +5880,7 @@ void print_usage(const char *prog) {
fprintf(stderr, " --golden-image IMG Use custom LXD image alias (for testing, e.g., jammy-golden-22.04)\n");
fprintf(stderr, " --bootstrap CMD Bootstrap command or URI to run on startup\n");
fprintf(stderr, " --bootstrap-file FILE Upload local file as bootstrap script content\n");
fprintf(stderr, " --unfreeze-on-demand Enable auto-unfreeze when HTTP request arrives\n");
fprintf(stderr, " -e, --env KEY=VAL Set environment variable (can repeat, stored encrypted)\n");
fprintf(stderr, " --env-file FILE Load env vars from .env file (stored encrypted)\n");
fprintf(stderr, " -f FILE Upload file to /tmp/ (can use multiple times)\n");
@ -5823,6 +5895,8 @@ void print_usage(const char *prog) {
fprintf(stderr, " --destroy ID Destroy a service\n");
fprintf(stderr, " --lock ID Lock a service to prevent deletion\n");
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, " --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");
@ -6188,6 +6262,15 @@ int unsandbox_service_unlock(const char *service_id,
return result;
}
int unsandbox_service_set_unfreeze_on_demand(const char *service_id, int enabled,
const char *public_key, const char *secret_key) {
UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
if (!creds) return -1;
int result = set_unfreeze_on_demand(creds, service_id, enabled);
free_credentials(creds);
return result;
}
int unsandbox_service_redeploy(const char *service_id, const char *bootstrap,
const char *public_key, const char *secret_key) {
UnsandboxCredentials *creds = get_credentials(public_key, secret_key, -1);
@ -7283,6 +7366,7 @@ unsandbox_service_list_t *unsandbox_service_list(
list->services[i].domains = extract_json_string(pos, "domains");
list->services[i].vcpu = (int)extract_json_number(pos, "vcpu");
list->services[i].locked = (int)extract_json_number(pos, "locked");
list->services[i].unfreeze_on_demand = (int)extract_json_number(pos, "unfreeze_on_demand");
list->services[i].created_at = extract_json_number(pos, "created_at");
list->services[i].last_activity = extract_json_number(pos, "last_activity");
@ -7366,6 +7450,7 @@ unsandbox_service_t *unsandbox_service_get(
service->domains = extract_json_string(response.data, "domains");
service->vcpu = (int)extract_json_number(response.data, "vcpu");
service->locked = (int)extract_json_number(response.data, "locked");
service->unfreeze_on_demand = (int)extract_json_number(response.data, "unfreeze_on_demand");
service->created_at = extract_json_number(response.data, "created_at");
service->last_activity = extract_json_number(response.data, "last_activity");
@ -7382,7 +7467,7 @@ char *unsandbox_service_create(
set_last_error("No credentials available");
return NULL;
}
char *result = create_service(creds, name, ports, domains, bootstrap, NULL, network_mode, 0, NULL, NULL, 0, NULL);
char *result = create_service(creds, name, ports, domains, bootstrap, NULL, network_mode, 0, NULL, NULL, 0, NULL, 0);
free_credentials(creds);
return result;
}
@ -8981,6 +9066,8 @@ int main(int argc, char *argv[]) {
int do_destroy = 0;
int do_lock = 0;
int do_unlock = 0;
int do_auto_unfreeze = 0;
int do_no_auto_unfreeze = 0;
int do_resize = 0;
int do_redeploy = 0;
int do_execute = 0;
@ -8992,6 +9079,7 @@ int main(int argc, char *argv[]) {
const char *restore_snapshot_id = NULL;
const char *snapshot_name = NULL;
int hot_snapshot = 0;
int create_unfreeze_on_demand = 0; // For --unfreeze-on-demand flag on create
// Environment variables for service create
char *service_env_content = NULL; // Accumulated env vars (KEY=VALUE\n...)
@ -9133,6 +9221,14 @@ int main(int argc, char *argv[]) {
do_unlock = 1;
i++;
service_id = argv[i];
} else if (strcmp(argv[i], "--auto-unfreeze") == 0 && i + 1 < argc) {
do_auto_unfreeze = 1;
i++;
service_id = argv[i];
} else if (strcmp(argv[i], "--no-auto-unfreeze") == 0 && i + 1 < argc) {
do_no_auto_unfreeze = 1;
i++;
service_id = argv[i];
} else if (strcmp(argv[i], "--resize") == 0 && i + 1 < argc) {
do_resize = 1;
i++;
@ -9176,6 +9272,8 @@ int main(int argc, char *argv[]) {
snapshot_name = argv[i];
} else if (strcmp(argv[i], "--hot") == 0) {
hot_snapshot = 1;
} else if (strcmp(argv[i], "--unfreeze-on-demand") == 0) {
create_unfreeze_on_demand = 1;
} else if ((strcmp(argv[i], "-e") == 0 || strcmp(argv[i], "--env") == 0) && i + 1 < argc) {
// -e KEY=VALUE or --env KEY=VALUE - accumulate env vars
i++;
@ -9324,6 +9422,10 @@ int main(int argc, char *argv[]) {
ret = lock_service(creds, service_id);
} else if (do_unlock) {
ret = unlock_service(creds, service_id);
} else if (do_auto_unfreeze) {
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_resize) {
if (vcpu < 1 || vcpu > 8) {
fprintf(stderr, "Error: --vcpu must be 1-8 for resize\n");
@ -9406,7 +9508,7 @@ int main(int argc, char *argv[]) {
fprintf(stderr, " (%d files)...", service_input_file_count);
}
fflush(stderr);
char *created_id = create_service(creds, service_name, service_ports, service_domains, service_bootstrap, bootstrap_content, network_mode, vcpu, service_type, service_input_files, service_input_file_count, golden_image);
char *created_id = create_service(creds, service_name, service_ports, service_domains, service_bootstrap, bootstrap_content, network_mode, vcpu, service_type, service_input_files, service_input_file_count, golden_image, create_unfreeze_on_demand);
if (bootstrap_content) free(bootstrap_content);
// Free input file memory
for (int i = 0; i < service_input_file_count; i++) {

View file

@ -110,6 +110,7 @@ typedef struct {
char *domains;
int vcpu;
int locked;
int unfreeze_on_demand; /* if true, frozen services auto-unfreeze on HTTP request */
int64_t created_at;
int64_t last_activity;
} unsandbox_service_t;
@ -276,6 +277,10 @@ int unsandbox_service_unlock(
const char *service_id,
const char *public_key, const char *secret_key);
int unsandbox_service_set_unfreeze_on_demand(
const char *service_id, int enabled,
const char *public_key, const char *secret_key);
int unsandbox_service_redeploy(
const char *service_id,
const char *bootstrap, /* new bootstrap script, NULL to keep existing */