Terminal: System Monitoring
Section titled “Terminal: System Monitoring”Use these endpoints to inspect container processes, network listeners, system resources, connected displays, daemon programs, and to manage power state. Most endpoints are read-only and require no input. The write operations (signal, freeze, unfreeze, reboot, shutdown) all enforce kernel-level permission checks.
All endpoints in this service are scoped to a specific container. Substitute {projectId}, {containerId}, and {server} in the base URL with the values for your target deployment.
System Resources & Info
Section titled “System Resources & Info”GET /api/v1/system/resources
Section titled “GET /api/v1/system/resources”Returns comprehensive system statistics including CPU usage, memory, network interfaces, uptime, and disk usage.
This endpoint takes no parameters.
curl -X GET "https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.icu/api/v1/system/resources" \ -H "Authorization: Bearer <token>"await client.terminal.system.getResources();{ "cpu": { "percent": 12.4, "cores": 8, "load_avg": [0.31, 0.42, 0.55] }, "memory": { "total": 16777216000, "available": 8392012800, "percent": 50.0 }, "uptime_seconds": 842113, "disk": { "total": 536870912000, "used": 214748364800, "percent": 40.0 }}GET /api/v1/system/daemon
Section titled “GET /api/v1/system/daemon”Returns the JSON array of daemon programs from the hoody-daemon configuration.
This endpoint takes no parameters.
curl -X GET "https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.icu/api/v1/system/daemon" \ -H "Authorization: Bearer <token>"await client.terminal.system.getDaemonConfig();[ { "name": "hoody-monitor", "command": "/usr/local/bin/hoody-monitor", "enabled": true }, { "name": "log-shipper", "command": "/usr/local/bin/log-shipper --config /etc/hoody/shipper.yaml", "enabled": true }]GET /api/v1/system/displays
Section titled “GET /api/v1/system/displays”Returns information about connected displays from the external display script.
This endpoint takes no parameters.
curl -X GET "https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.icu/api/v1/system/displays" \ -H "Authorization: Bearer <token>"await client.terminal.system.getDisplayInfo();[ { "id": "HDMI-1", "resolution": "3840x2160", "connected": true }]GET /api/v1/terminal/health
Section titled “GET /api/v1/terminal/health”Service health check. Unauthenticated. Always returns HTTP 200 with application/json when the service is up.
This endpoint takes no parameters.
curl -X GET "https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.icu/api/v1/terminal/health"await client.terminal.health.check();{ "status": "ok", "service": "terminal", "version": "1.0.0", "uptime_seconds": 842113}Processes
Section titled “Processes”GET /api/v1/system/processes
Section titled “GET /api/v1/system/processes”Returns a JSON array of all processes with CPU, memory, and state information. Supports filtering, sorting, and limiting results.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
sort | query | string | No | Sort by field: cpu, memory, pid, name. Default: pid |
limit | query | integer | No | Maximum number of processes to return. Default: all |
filter | query | string | No | Filter by process name (substring match, case-insensitive) |
curl -X GET "https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.icu/api/v1/system/processes?sort=cpu&limit=10&filter=python" \ -H "Authorization: Bearer <token>"await client.terminal.system.listProcessesIterator({ sort: 'cpu', limit: 10, filter: 'python' });[ { "pid": 1042, "ppid": 1, "name": "python3", "cpu_percent": 38.7, "memory_percent": 12.1, "state": "S", "cmdline": "python3 /srv/train.py" }, { "pid": 1124, "ppid": 1042, "name": "python3", "cpu_percent": 4.2, "memory_percent": 1.8, "state": "S", "cmdline": "python3 /srv/train.py --worker" }]GET /api/v1/system/processes/{pid}
Section titled “GET /api/v1/system/processes/{pid}”Returns detailed information about a specific process including all stats, cmdline, environment, and open files.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
pid | path | integer | Yes | Process ID |
curl -X GET "https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.icu/api/v1/system/processes/1042" \ -H "Authorization: Bearer <token>"await client.terminal.system.getProcess(1042);{ "pid": 1042, "ppid": 1, "name": "python3", "state": "S", "cmdline": "python3 /srv/train.py", "cwd": "/srv", "environ": { "PATH": "/usr/local/bin:/usr/bin", "PYTHONUNBUFFERED": "1" }, "open_files": ["/srv/train.py", "/dev/null"], "cpu_percent": 38.7, "memory_percent": 12.1, "create_time": 1714401234.56}{ "statusCode": 404, "error": "Not Found", "code": "PROCESS_NOT_FOUND"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PROCESS_NOT_FOUND | Process does not exist | Check PID is valid and process is running | Check PID is valid and process is running |
POST /api/v1/system/process/signal
Section titled “POST /api/v1/system/process/signal”Send a Unix signal to one or more processes by PID or name. Supports all standard signals (SIGTERM, SIGKILL, SIGSTOP, SIGCONT, etc.). When targeting by name, the signal is sent to ALL matching processes.
This endpoint takes no path, query, or header parameters.
Request Body
Section titled “Request Body”| Name | Type | Required | Description |
|---|---|---|---|
pid | integer | No | Process ID to signal (mutually exclusive with name) |
name | string | No | Process name to signal — signals ALL matching processes (mutually exclusive with pid) |
signal | string|integer | No | Signal to send. String form accepts SIGTERM, TERM, 15, etc. (with or without SIG prefix). Integer form accepts any value in [0, NSIG) including realtime signals SIGRTMIN..SIGRTMAX (typically 34..64 on Linux), which have no portable string names. |
force | boolean | No | Shorthand for SIGKILL (true) or SIGTERM (false). Overrides the signal parameter |
curl -X POST "https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.icu/api/v1/system/process/signal" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "pid": 1042, "signal": "SIGTERM" }'await client.terminal.system.sendSignal({ pid: 1042, signal: 'SIGTERM' });{ "signaled": [1042], "signal": "SIGTERM"}{ "statusCode": 400, "error": "Bad Request", "code": "MISSING_TARGET"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_TARGET | Must specify pid or name | Provide either pid or name parameter | Provide either pid or name parameter |
INVALID_SIGNAL | Invalid signal name | Use valid signal (SIGTERM, SIGKILL, etc.) | Use valid signal (SIGTERM, SIGKILL, etc.) |
{ "statusCode": 403, "error": "Forbidden", "code": "PERMISSION_DENIED"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PERMISSION_DENIED | No permission to signal process | Check process ownership | Check process ownership |
{ "statusCode": 405, "error": "method_not_allowed", "message": "Request method is not POST. Emits method_not_allowed with an Allow: POST header and Connection: close."}{ "statusCode": 500, "error": "Internal Server Error", "code": "SIGNAL_FAILED"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SIGNAL_FAILED | Failed to send signal | Check process exists | Check process exists |
Process Freeze / Unfreeze
Section titled “Process Freeze / Unfreeze”These two endpoints suspend and resume process execution by delivering SIGSTOP and SIGCONT respectively. They are useful for pausing background workloads (builds, fine-tunes, AI inference) without losing in-memory state. Both target either a specific PID or all processes matching a name, and both can optionally fan out across every descendant of the target.
POST /api/v1/system/processes/freeze
Section titled “POST /api/v1/system/processes/freeze”Suspend execution of one or more processes by delivering SIGSTOP. The kernel halts every thread of the target’s thread group until a matching SIGCONT is delivered (see unfreeze below).
This endpoint takes no path, query, or header parameters.
Request Body
Section titled “Request Body”| Name | Type | Required | Description |
|---|---|---|---|
pid | integer | No | Process ID to freeze (mutually exclusive with name). PIDs 1 (init), 2 (kthreadd), the server’s own PID, and the server’s parent PID are guarded and rejected with 403 |
name | string | No | Process name (case-insensitive comm match — freezes EVERY matching process; mutually exclusive with pid). Linux truncates comm to 15 chars; longer names silently match nothing |
include_descendants | boolean | No | When true, also freeze every descendant via a one-shot /proc PPID snapshot (bounded at 65535 PIDs). Default false. By-name dedupes overlapping subtrees |
curl -X POST "https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.icu/api/v1/system/processes/freeze" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "pid": 1042, "include_descendants": true }'await client.terminal.system.freezeProcess({ pid: 1042, include_descendants: true });{ "frozen": [1042, 1124, 1125]}{ "statusCode": 400, "error": "Bad Request", "code": "MISSING_TARGET"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_TARGET | Must specify pid or name | Provide either pid or name parameter | Provide either pid or name parameter |
INVALID_PID | pid must be a positive integer within int range | pid must be a positive integer within int range | Contact support |
{ "statusCode": 403, "error": "Forbidden", "code": "PERMISSION_DENIED"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PERMISSION_DENIED | No permission to freeze process | Either the kernel returned EPERM (different UID) or the PID is in the guarded set (init/kthreadd/self/parent) | Either the kernel returned EPERM (different UID) or the PID is in the guarded set (init/kthreadd/self/parent) |
{ "statusCode": 404, "error": "Not Found", "code": "PROCESS_NOT_FOUND"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PROCESS_NOT_FOUND | No process matching pid/name | No process matching pid/name | Contact support |
{ "statusCode": 405, "error": "method_not_allowed", "message": "Request method is not POST. Emits method_not_allowed with an Allow: POST header and Connection: close."}{ "statusCode": 500, "error": "Internal Server Error", "code": "FREEZE_FAILED"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
FREEZE_FAILED | Failed to freeze process | Check process exists | Check process exists |
PROC_SNAPSHOT_OOM | /proc snapshot exhausted memory; transient | /proc snapshot exhausted memory; transient | Contact support |
PROC_TABLE_TOO_LARGE | /proc PID table exceeds HT_PID_TABLE_MAX (65535) | /proc PID table exceeds HT_PID_TABLE_MAX (65535) | Contact support |
POST /api/v1/system/processes/unfreeze
Section titled “POST /api/v1/system/processes/unfreeze”Resume execution of one or more previously-stopped processes by delivering SIGCONT. Calling unfreeze on a process that is already running is harmless — SIGCONT is a no-op for non-stopped processes. Permission is enforced by the kernel.
This endpoint takes no path, query, or header parameters.
Request Body
Section titled “Request Body”| Name | Type | Required | Description |
|---|---|---|---|
pid | integer | No | Process ID to unfreeze (mutually exclusive with name). The guarded-PID set (1, 2, self, parent) is the same as for freeze |
name | string | No | Process name (case-insensitive comm match; mutually exclusive with pid). Linux truncates comm to 15 chars; longer names silently match nothing |
include_descendants | boolean | No | Also unfreeze all descendants via /proc PPID snapshot (bounded at 65535 PIDs). Default false. By-name dedupes overlapping subtrees |
curl -X POST "https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.icu/api/v1/system/processes/unfreeze" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "pid": 1042, "include_descendants": true }'await client.terminal.system.unfreezeProcess({ pid: 1042, include_descendants: true });{ "resumed": [1042, 1124, 1125], "skipped": []}{ "statusCode": 400, "error": "Bad Request", "code": "MISSING_TARGET"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
MISSING_TARGET | Must specify pid or name | Provide either pid or name parameter | Provide either pid or name parameter |
INVALID_PID | pid must be a positive integer within int range | pid must be a positive integer within int range | Contact support |
{ "statusCode": 403, "error": "Forbidden", "code": "PERMISSION_DENIED"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PERMISSION_DENIED | No permission to unfreeze process | Either the kernel returned EPERM (different UID) or the PID is in the guarded set (init/kthreadd/self/parent) | Either the kernel returned EPERM (different UID) or the PID is in the guarded set (init/kthreadd/self/parent) |
{ "statusCode": 404, "error": "Not Found", "code": "PROCESS_NOT_FOUND"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
PROCESS_NOT_FOUND | No process matching pid/name | No process matching pid/name | Contact support |
{ "statusCode": 405, "error": "method_not_allowed", "message": "Request method is not POST. Emits method_not_allowed with an Allow: POST header and Connection: close."}{ "statusCode": 500, "error": "Internal Server Error", "code": "UNFREEZE_FAILED"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
UNFREEZE_FAILED | Failed to unfreeze process | Check process exists | Check process exists |
PROC_SNAPSHOT_OOM | /proc snapshot exhausted memory; transient | /proc snapshot exhausted memory; transient | Contact support |
PROC_TABLE_TOO_LARGE | /proc PID table exceeds HT_PID_TABLE_MAX (65535) | /proc PID table exceeds HT_PID_TABLE_MAX (65535) | Contact support |
Network Ports
Section titled “Network Ports”GET /api/v1/system/ports
Section titled “GET /api/v1/system/ports”Returns a JSON array of all TCP/UDP ports with process information and extensive filtering options.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
protocol | query | string | No | Filter by protocol: tcp, udp, or comma-separated list |
user | query | string | No | Filter by user (exact match) |
port | query | integer | No | Filter by specific port number |
ip | query | string | No | Filter by IP address (comma-separated list) |
skip_program | query | string | No | Exclude specific programs (comma-separated list) |
http_only | query | boolean | No | Only return HTTP services |
hoody_only | query | boolean | No | Only return Hoody Kit services |
curl -X GET "https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.icu/api/v1/system/ports?protocol=tcp&http_only=true" \ -H "Authorization: Bearer <token>"await client.terminal.system.listPortsIterator({ protocol: 'tcp', http_only: true });[ { "protocol": "tcp", "address": "0.0.0.0", "port": 8080, "state": "LISTEN", "pid": 1042, "program": "python3", "user": "root" }, { "protocol": "tcp", "address": "127.0.0.1", "port": 5432, "state": "LISTEN", "pid": 712, "program": "postgres", "user": "postgres" }]Power Management
Section titled “Power Management”Both power management endpoints invoke shutdown(8), which schedules in whole minutes. The server rounds the requested delay UP to the nearest minute and reports the actual scheduled value as effective_minutes in the response.
POST /api/v1/system/reboot
Section titled “POST /api/v1/system/reboot”Initiate a system reboot with optional delay.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
delay | query | integer | No | Delay in seconds before reboot, 0..86400. Default 0 for immediate |
curl -X POST "https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.icu/api/v1/system/reboot?delay=60" \ -H "Authorization: Bearer <token>"await client.terminal.system.reboot({ delay: 60 });{ "accepted": true, "requested_delay_seconds": 60, "effective_minutes": 1}{ "statusCode": 400, "error": "Bad Request", "message": "Delay out of range (> 86400)"}{ "statusCode": 403, "error": "Forbidden", "code": "ROOT_REQUIRED"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ROOT_REQUIRED | Reboot requires root privileges | Run with sudo or as root user | Run with sudo or as root user |
{ "statusCode": 405, "error": "method_not_allowed", "message": "Request method is not POST. Emits method_not_allowed with an Allow: POST header and Connection: close."}{ "statusCode": 500, "error": "Internal Server Error", "code": "REBOOT_FAILED"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
REBOOT_FAILED | Failed to execute reboot | Check system logs | Check system logs |
POST /api/v1/system/shutdown
Section titled “POST /api/v1/system/shutdown”Initiate a system shutdown with optional delay.
Parameters
Section titled “Parameters”| Name | In | Type | Required | Description |
|---|---|---|---|---|
delay | query | integer | No | Delay in seconds before shutdown, 0..86400. Default 0 for immediate |
curl -X POST "https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.icu/api/v1/system/shutdown?delay=300" \ -H "Authorization: Bearer <token>"await client.terminal.system.shutdown({ delay: 300 });{ "accepted": true, "requested_delay_seconds": 300, "effective_minutes": 5}{ "statusCode": 400, "error": "Bad Request", "message": "Delay out of range (> 86400)"}{ "statusCode": 403, "error": "Forbidden", "code": "ROOT_REQUIRED"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
ROOT_REQUIRED | Shutdown requires root privileges | Run with sudo or as root user | Run with sudo or as root user |
{ "statusCode": 405, "error": "method_not_allowed", "message": "Request method is not POST. Emits method_not_allowed with an Allow: POST header and Connection: close."}{ "statusCode": 500, "error": "Internal Server Error", "code": "SHUTDOWN_FAILED"}| Error Code | Title | Description | Resolution |
|---|---|---|---|
SHUTDOWN_FAILED | Failed to execute shutdown | Check system logs | Check system logs |
Client Diagnostics Beacon
Section titled “Client Diagnostics Beacon”POST /api/v1/terminal/state
Section titled “POST /api/v1/terminal/state”Fire-and-forget diagnostics endpoint. A terminal client posts its own render/connection health so the server can correlate client-side stalls with server-side session state. This is optional telemetry — callers never need it to drive a terminal, and the response carries no useful body.
This endpoint takes no path, query, or header parameters.
Request Body
Section titled “Request Body”| Name | Type | Required | Description |
|---|---|---|---|
build_id | string | No | Frontend build identifier |
renderer | string | No | Effective renderer (webgl or dom) |
reason | string | No | What triggered this beacon |
curl -X POST "https://{projectId}-{containerId}-terminal-1.{server}.containers.hoody.icu/api/v1/terminal/state" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/json" \ -d '{ "build_id": "web-2024.04.01", "renderer": "webgl", "reason": "frame_skipped" }'await client.terminal.terminalState.postTerminalState({ build_id: 'web-2024.04.01', renderer: 'webgl', reason: 'frame_skipped'});{ "accepted": true}