seristack
Run shell workflows via CLI, HTTP, or AI agents
Seristack is a lightweight automation engine designed for DevOps, Platform, SRE, and Cloud teams. Define shell workflows in YAML, manage dependencies, expose them as HTTP endpoints, and let AI agents call them as MCP tools.
Documentation:
Features
🚀 Run multiple command stacks from a single config
🔁 Repeat stacks with serial or concurrent execution
🔗 Define dependencies between stacks
🧩 Variable substitution with validation rules
📦 Share output between stacks
🌐 Expose stacks as HTTP endpoints
🔐 Per-stack authorization via identity headers
📋 Structured audit log for every stack execution
🧠 Run as an MCP server for AI agent and IDE integrations
🛠 Works with mvdan shell (default), Bash, sh, and PowerShell
Installation
Using Homebrew (Mac and Linux)
brew install TechXploreLabs/tap/seristack
Linux (using release archive)
- Go to Seristack Releases and download the latest
seristack_VERSION_linux_ARCH.tar.gz. - Extract the archive:
tar -xzf seristack_VERSION_linux_ARCH.tar.gz - Move the binary to your PATH:
sudo mv seristack /usr/local/bin/ sudo chmod +x /usr/local/bin/seristack - Verify:
seristack --help
Windows (using release archive)
- Go to Seristack Releases and download the latest
seristack_VERSION_windows_ARCH.zip. - Extract and move
seristack.exeto a folder in your%PATH%. - Verify:
seristack --help
Sample config
For a full explanation of every YAML attribute, see the Config Reference.
stacks:
- name: stack1
workDir: ./
description: Print welcome message
method: GET
urlPath: /show
continueOnError: false
count: 3
timeouts: 1h
executionMode: PARALLEL
vars:
- name: samplekey
value: samplevalue
required: true
allowed_value: [samplevalue, devvalue]
cmds:
- |
export samplekey={{.Vars.samplekey}}
echo $samplekey
echo "count={{.Count.index}}"
echo "Hey i'm seristack!"
- name: stack2
workDir: ./
continueOnError: false
count: 3
executionMode: SEQUENTIAL
vars:
- name: env
value: Dev
dependsOn: [stack1]
cmds:
- |
echo "{\"index\": {{.Count.index}}, \"step\": \"metadata\", \"status\": \"ok\"}"
- |
echo "{\"index\": {{.Count.index}}, \"step\": \"metrics\", \"value\": $((RANDOM % 100))}"
output: |
echo "--- Aggregation Summary ---"
echo '{{.Self.result}}' | grep "^{" | jq -s '{
total_records: length,
environment: "{{.Vars.env}}",
results: .
}'
Running stacks
# Trigger all stacks
seristack trigger -c config.yaml
# Trigger a specific stack
seristack trigger -c config.yaml -s stack1
# Start the HTTP server
seristack run -c config.yaml
# Start the MCP server
seristack mcp -t streamableHTTP
Production deployment
Seristack executes shell commands. Never expose it directly to the public internet.
The production pattern is:
Client → nginx / caddy (TLS, AuthN) → seristack on 127.0.0.1 (AuthZ, execution)
Start seristack bound to localhost:
seristack run --config config.yaml --addr 127.0.0.1 --port 8080
The reverse proxy handles TLS, authentication, and rate limiting. Seristack handles per-stack authorization and execution.
Per-stack authorization
Seristack checks identity headers forwarded by nginx/caddy after they validate the user. Every IdP (Entra ID, OCI IAM, GCP IAM, AWS Cognito, Okta) forwards group and role information as HTTP headers via oauth2-proxy or a native OIDC integration.
Add an access block to any stack to restrict who can execute it:
stacks:
- name: deploy-production
method: POST
urlPath: /deploy/production
matchAccess: ANY # ANY (default) or ALL
access:
- headerName: "X-Auth-Request-Groups"
headerValue: ["sre", "platform"]
- headerName: "X-Auth-Request-Roles"
headerValue: ["admin"]
count: 1
cmds:
- ./deploy.sh
matchAccess: ANY— access is granted if any one rule matches (OR logic). Default.matchAccess: ALL— every rule must match (AND logic).- No
accessblock — any authenticated user can execute the stack.
The header names depend on your IdP and proxy:
| IdP | Proxy | Headers forwarded |
|---|---|---|
| Entra ID / Azure AD | oauth2-proxy | X-Auth-Request-Groups, X-Auth-Request-Roles, X-Auth-Request-Email |
| GCP IAM | IAP | X-Goog-Authenticated-User-Email |
| AWS Cognito | ALB | X-Amzn-Oidc-Data |
| OCI IAM | nginx + oauth2-proxy | X-Auth-Request-Groups, X-Auth-Request-Email |
| Okta / Auth0 | oauth2-proxy | X-Auth-Request-Groups, X-Auth-Request-Roles |
Audit log
Enable a structured JSON audit trail for every stack execution:
seristack run \
--audit-log /var/log/seristack/audit.log \
--identity-header "user=X-Auth-Request-Email" \
--identity-header "groups=X-Auth-Request-Groups" \
--identity-header "roles=X-Auth-Request-Roles"
Every execution — success or failure — writes one JSON line:
{
"timestamp": "2024-01-15T10:30:00Z",
"event": "stack_executed",
"request_id": "abc-123",
"stack": "deploy-production",
"path": "/deploy/production",
"method": "POST",
"source_ip": "127.0.0.1:54231",
"identity": {
"user": "[email protected]",
"groups": "sre,platform"
},
"vars": {"env": "production", "version": "v1.2.3"},
"success": true,
"duration_ms": 1423
}
Query the audit log with jq:
# Who ran deploys today
jq 'select(.stack == "deploy-production")' /var/log/seristack/audit.log
# All failures
jq 'select(.success == false)' /var/log/seristack/audit.log
# Everything a specific user ran
jq 'select(.identity.user == "[email protected]")' /var/log/seristack/audit.log
# Executions over 30 seconds
jq 'select(.duration_ms > 30000)' /var/log/seristack/audit.log
Use logrotate to manage audit log rotation. Do not pass secrets as stack vars — they will appear in the audit log. Secrets should come from environment variables or a secrets manager inside the shell script.
nginx example with IdP integration
server {
listen 443 ssl;
server_name seristack.example.com;
ssl_certificate /etc/letsencrypt/live/seristack.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/seristack.example.com/privkey.pem;
location /oauth2/ {
proxy_pass http://127.0.0.1:4180;
proxy_set_header Host $host;
}
location / {
# Validate identity via oauth2-proxy
auth_request /oauth2/auth;
# Forward identity headers from oauth2-proxy to seristack
auth_request_set $user $upstream_http_x_auth_request_email;
auth_request_set $groups $upstream_http_x_auth_request_groups;
proxy_set_header X-Auth-Request-Email $user;
proxy_set_header X-Auth-Request-Groups $groups;
proxy_set_header X-Request-ID $request_id;
proxy_pass http://127.0.0.1:8080;
}
}
Caddy example with IdP integration
seristack.example.com {
forward_auth http://oauth2-proxy:4180 {
uri /oauth2/auth
copy_headers X-Auth-Request-Email X-Auth-Request-Groups X-Auth-Request-Roles
}
reverse_proxy 127.0.0.1:8080 {
header_up X-Request-ID {http.request.uuid}
}
}
MCP server
seristack mcp -t streamableHTTP --addr 127.0.0.1 --port 8081
Stacks with a description field are registered as MCP tools. AI agents (Claude, Cursor, Copilot) can call them directly. Apply the same nginx/caddy security posture in front of the MCP server as you would for the HTTP server.
Variable validation
vars:
- name: env
value: staging
required: true
allowed_value: [staging, production]
- name: version
required: true
allowed_regex: regex("^[a-zA-Z0-9._-]+$")
- name: command
denied_regex: regex("(?i)rm|delete|drop")
Only variables declared in vars can be overridden by HTTP requests or MCP arguments. Undeclared variable names from HTTP inputs are dropped.
run command reference
seristack run [flags]
Flags:
-c, --config string config file (default "config.yaml")
-a, --addr string bind address (default "127.0.0.1")
-p, --port string server port (default "8080")
--audit-log string path to audit log file (enables audit logging when set)
--identity-header strings map identity header to a key: "user=X-Auth-Request-Email" (repeatable)
Support the project
If Seristack helps your team turn shell scripts or runbooks into internal APIs and MCP tools, consider supporting the project by starring the repository, sharing feedback, opening issues, or contributing examples.
License
Apache License
No comments yet
Be the first to share your take.