A lightweight HTTP API server that exposes command-line programs as REST endpoints. Query parameters are translated into command-line arguments, with fine-grained control over allowed flags, flag prefixes, argument–value separators, positional arguments, boolean flags, and execution timeouts.
The server supports API key–based authentication and configurable CORS headers. It also provides gzip and Brotli compression when supported by the client.
The granular control over the allowed flags and its types make the rest api robust and secure.
In the release section binaries are available in 15 OS and architecture combinations.
To update the binary to the latest release, simply run
cmd2api -upgradeTo compile the binary instead of downloading.
# Requires Go 1.22+
GOFLAGS="-mod=vendor" CGO_ENABLED=0 go build --ldflags '-w -s -buildid=' -o cmd2api .cmd2api [flags]
-config string Path to config.yml (default: config.yml next to binary)
-listen string IP address or interface name to listen on
Examples: 192.168.1.10, lo, eth0, docker0
Empty = all interfaces (default)
-port int Port to listen on (default 11555)
-upgrade Self update to latest version
Command-line flags take precedence over values in config.yml.
global:
listen: "" # (optional) IP or interface; empty = all interfaces
port: 11555 # (optional) default port
global_api_keys: # keys valid for every route
- supersecretkey
routes:
myroute: # served at GET /myroute
command: mycmd --preset a # binary + hard-coded args
allowed_args: "w x y z" # space-separated list of accepted query params
# All below are optional
arg_prefix: "--" # flag prefix (default "--", use "-" for single-dash)
arg_value_joiner: " " # joins flag+value (default space, use "=" for --k=v)
positional_args: "w x" # these args become positional values (no prefix)
flag_args: "y" # these are boolean flags with NO value
append_args: "--extra 'v'" # always appended after API-built args
cmd_workdir: /home/user # working directory for the command
exec_timeout: 2s # timeout duration for command execution
api_keys: "key1 key2" # extra keys only for this route
cors_origins: "*" # Access-Control-Allow-Origin for CORS headers
# can add more routesRead the included config-example.yml for examples and more detail configuration options for routes.
Given command: "mycmd --preset X" allowed_args: "a b c y", positional_args: "a", flag_args: "y", arg_prefix: "--",
arg_value_joiner: "=", and a request GET /route?a=1&b=2&c=3&y=4, the command-line argument constructed as:
mycmd --preset X 1 --b=2 --c=3 --y [append_args...]
^positional ^flags with joiner
API keys are checked via (in order):
X-API-Key: <key>request headerAuthorization: Bearer <key>query parameter
A request is authorised if its key appears in global.global_api_keys or
the route's api_keys list. If neither list has any entries, the route is
open (no auth required).
All routes return JSON:
{
"success": true,
"stdout": "command output here\n",
"stderr": "",
"exit_code": 0
}On error:
{
"success": false,
"error": "description",
"exit_code": 1
}GET /healthz → {"status":"ok"}
# Start
./cmd2api --config config.yml --port 8080
# Call /ls with a positional path arg, auth via header
curl -H "X-API-Key: supersecretkey" "http://localhost:8080/ls?path=/tmp"
# Call /date with a custom format
curl -H "Authorization: Bearer supersecretkey" "http://localhost:8080/date?format=%25Y-%25m-%25d"In the examples only GET requests are shows, however, the API can also accept POST and JSON formatted requests, which are preferred if the values contain sensitive information.
The API server is secure enough against most common malformed argument injection at command execution level. However, it ultimately depends on the command-line program how it process the arguments and exposes risk to host system.
The best practice would be to run the API server and command-line program within a docker container, having an isolated filesystem from host, and also to make the filesystem read-only if that permits the use case. Here is a minimal example:
docker run -it --rm -v ./cmd2api_dir:/cmd2api:ro -p 11555:11555 alpine /cmd2api/cmd2api
#OR as a always on background process
docker run -itd --restart unless-stopped -v ./cmd2api_dir:/cmd2api:ro -p 11555:11555 alpine /cmd2api/cmd2api