A simple API and UI for executing and scheduling system commands or scripts. Great for webhooks and automating Linux server operations over HTTPS contained in a small binary.
- Use Cases
- Key Features
- Quick Start
- YAML Definitions Configuration
- API Endpoints
- Configurations
- Built-In Variables
- YAML Server Configurations
- Example Action Definition YML
- Tiny CI / Job Server
- Remote Server Management
- Server Monitoring / Testing
- Homelab Automation
- Hide command output
- Cache last response / command output
- Create basic notifications inside pal
- Dynamic routing with easy YAML configurations
- Secure HTTP endpoints with auth header restriction
- File upload/download via a basic UI with Basic Auth
- Optional easy to use HTML UI (Works Offline/Air-Gap)
- Single dynamically linked binary (32MB~)
- Control command execution: concurrent or sequential, background processes
- Secure key-value storage with BadgerDB (encrypted local filesystem database)
- Pass data to commands or scripts via env variables (Built-In Env Variables)
Prerequisites: Go 1.25 or higher
Recommended Podman Deps: https://podman.io/docs/installation#build-and-run-dependencies
make make certs ./pal -c ./pal.yml -d ./testmake linux make certs # Default insecure test configurations on debian:stable-slim make debiandocker run -d --name=pal -p 8443:8443 -v "$(pwd)"/pal.yml:/etc/pal/pal.yml:ro -v "$(pwd)"/actions:/etc/pal/actions:ro --health-cmd 'curl -sfk https://127.0.0.1:8443/v1/pal/health || exit 1' --init --restart=unless-stopped pal:latest # See generated random secrets docker logs palmkdir -p ./actions ./upload ./pal.db # If UID of docker user isn't UID/GID 101010 same as pal user inside container sudo chown -Rf 101010:101010 ./ docker run -d --name=pal -p 8443:8443 -v "$(pwd)"/actions:/etc/pal/actions:ro -v "$(pwd)"/pal.yml:/etc/pal/pal.yml:ro -v "$(pwd)"/pal.db:/etc/pal/pal.db:rw -v "$(pwd)"/upload:/pal/upload:rw --health-cmd 'curl -sfk https://127.0.0.1:8443/v1/pal/health || exit 1' --init --restart=unless-stopped pal:latestAvailable Docker Run Env Variables:
# Default insecure test values -e HTTP_LISTEN="0.0.0.0:8443" -e HTTP_TIMEOUT_MIN="10" -e HTTP_BODY_LIMIT="90M" -e HTTP_MAX_AGE="3600" -e HTTP_HEADERS='[]' -e HTTP_PROMETHEUS='false' -e HTTP_UI_UPLOAD_DIR='/pal/upload' -e HTTP_UI_BASIC_AUTH='pal __Check_Container_Log_Output__' -e HTTP_SESSION_SECRET='__Check_Container_Log_Output__' -e DB_ENCRYPT_KEY='__Check_Container_Log_Output__' -e DB_PATH='/etc/pal/pal.db' -e GLOBAL_DEBUG='false' -e GLOBAL_TIMEZONE='UTC' -e GLOBAL_CMD_PREFIX='/bin/sh -c' -e GLOBAL_WORKDIR='/pal' -e NOTIFICATIONS_STORE_MAX='100'# Need nfpm to build RPMs / Debs make install-deps make vagrant # debian make vagrant-rpm # rocky9 # If you want to ignore debs/rpm builds and installs just run: # vagrant upsudo cp pal.service /etc/systemd/system/pal.service sudo systemctl daemon-reload sudo systemctl start pal.service # (optional) enable auto startup on system restarts sudo systemctl enable pal.service# Need nfpm to build RPM / DEB files make install-deps make pkg-allDefault Access: https://127.0.0.1:8443 (See Configurations to customize)
# REQUIRED Group name: e.g., /v1/pal/run/deploy deploy: - # REQUIRED Action name: e.g., /v1/pal/run/deploy/app action: app # Description of action desc: Deploy app # Auth header: e.g., curl -H'X-Pal-Auth: secret_string_here' auth_header: X-Pal-Auth secret_string_here # Show command output (default: false) output: true # Set a default input if not set at request time input: # Run in background (default: false) background: false # Run concurrently (default: false) concurrent: true # Run in podman/docker container (default: null) container: # Run as sudo if not running pal with root or docker group perms sudo: false # Container image to use image: alpine:latest # Run options options: --security-opt=no-new-privileges:true --cap-drop=ALL --net=none # Run command inside podman container not using os/exec and instead use podman socket (default: null) image: alpine:latest # Set action to run multiple cron style schedules crons: - "*****" # Set command timeout in seconds (default: 600 seconds/10 mins) timeout: 600 # Set custom HTTP Response Headers headers: - header: value: # Validate input provided to run, valid options can be found here https://github.com/go-playground/validator?tab=readme-ov-file#baked-in-validations input_validate: required # Register / Put a key and value in the DB register: key: "$PAL_GROUP-$PAL_ACTION" value: "input=$PAL_INPUT status=$PAL_STATUS output=$PAL_OUTPUT" secret: false on_error: # Send notification when an error occurs using built-in vars $PAL_GROUP $PAL_ACTION $PAL_INPUT $PAL_OUTPUT notification: "deploy failed group=$PAL_GROUP action=$PAL_ACTION input=$PAL_INPUT status=$PAL_STATUS output=$PAL_OUTPUT" # Try cmd number of times retries: 1 # Pause in seconds before running the next retry retry_interval: 10 # Trigger webhook HTTP request with the name defined in the pal.yml file webhooks: - webhook_name_defined_in_pal.yml # Run action(s) on_error run: - group: group_name action: action_name # Input for run action on_error input: $PAL_OUTPUT on_success: # Send notification when no errors occurs using built-in vars $PAL_GROUP $PAL_ACTION $PAL_INPUT $PAL_OUTPUT notification: "deploy failed group=$PAL_GROUP action=$PAL_ACTION input=$PAL_INPUT status=$PAL_STATUS output=$PAL_OUTPUT" # Trigger webhook HTTP request with the name defined in the pal.yml file webhooks: - webhook_name_defined_in_pal.yml # Run action(s) when no errors occurs run: - group: group_name action: action_name # Input for run action when no errors occurs input: $PAL_OUTPUT # Command prefix can be anything e.g. python -c, pwsh -Command, etc. Default is /bin/sh -c cmd_prefix: /bin/sh -c # REQUIRED Command or script (use $PAL_INPUT for variables) cmd: echo "GROUP=$PAL_GROUP ACTION=$PAL_ACTION INPUT=$PAL_INPUT REQUEST=$PAL_REQUEST UPLOAD_DIR=$PAL_UPLOAD_DIR"Example Request
curl -sk -H'X-Pal-Auth: secret_string_here' 'https://127.0.0.1:8443/v1/pal/run/deploy/app?input=helloworld2' curl -sk -H'X-Pal-Auth: secret_string_here' -XPOST -d 'helloworld2' 'https://127.0.0.1:8443/v1/pal/run/deploy/app'Run command using either GET (query param) or POST (post body). Access last cached output of command run.
Query Parameters:
input: input to the running script/cmd also known as parameter or argumentlast_output: return only the last ran output and do not trigger a runlast_success: return only the last successful output and do not trigger a runlast_failure: return only the last failure output and do not trigger a run
GET /v1/pal/run/{{ group name }}/{{ action name }}?input={{ data }} GET /v1/pal/run/{{ group name }}/{{ action name }}?last_output=true POST {{ any data }} /v1/pal/run/{{ group name }}/{{ action name }}group name(Required): Key from your YAML configaction name(Required): Action value associated with the groupdata(Optional): Data (text, JSON) passed to your command/script as$PAL_INPUT
Get, put or dump all contents of the database. Meant to store small data <1028 characters in length (no limit, just recommendation).
PUT {{ any data }} /v1/pal/db/put?key={{ key_name }}&secret={{ secret }} GET /v1/pal/db/get?key={{ key_name }} GET /v1/pal/db/dump DELETE /v1/pal/db/delete?key={{ key_name }}any data(Required): Any type of data to storekey name(Required): Key to identify the stored datasecret(Optional): Boolean true or false to hide value in UIdumpreturns all key value pairs from DB in a JSON object
cURL Key-Value Example
curl -vsk -u 'username:password' -XPUT -d 'pal' 'https://127.0.0.1:8443/v1/pal/db/put?key=name&secret=true'Basic healthcheck endpoint. Enable Prometheus configuration for metrics endpoint.
GET /v1/pal/health - Returns "ok" response body
Upload and download files using a web request when enabled in the configuration.
GET [BASIC AUTH] /v1/pal/ui/files (Browser HTML View) GET [BASIC AUTH] /v1/pal/ui/files/download/{{ filename }} (Download File) POST [BASIC AUTH] /v1/pal/ui/files/upload (Multiform Upload)filename(Required): For downloading a specific file
cURL Upload Example
# Get Cookie curl -sSk -XPOST -d 'username=pal' -d 'password=p@LLy5' --cookie-jar ./pal.cookie 'https://127.0.0.1:8443/v1/pal/ui/login' # Use Cookie to Upload File curl -sSk -XPOST -F files='@{{ filename }}' -b ./pal.cookie 'https://127.0.0.1:8443/v1/pal/ui/files/upload'Create or get notifications and filter by group name.
GET /v1/pal/notifications GET /v1/pal/notifications?group={{ group_name }} PUT {{ json_data }} /v1/pal/notificationsgroup_name(Optional): Only show notifications for group provided
cURL Notification Example
curl -vks -u 'username:password' \ -d '{"notification":"THE QUICK BROWN FOX JUMPED OVER THE LAZY DOGS BACK 1234567890","group":"json"}' \ -H "content-type: application/json" -XPUT \ 'https://127.0.0.1:8443/v1/pal/notifications'Get configured cron actions or run cron action now.
GET /v1/pal/crons GET /v1/pal/crons?group={{ group }}&action={{ action }}&run={{ run }}group(Optional): group nameaction(Optional): action namerun(Optional): keyword "now" is only supported at this time. Runs action now.
Get actions configuration including last_output and other run stats.
GET /v1/pal/action?group={{ group }}&action={{ action }} GET /v1/pal/action?group={{ group }}&action={{ action }}&disabled={{ boolean }} GET /v1/pal/actionsgroup(Required): group nameaction(Required): action namedisabled(Optional): disabled boolean
Usage: pal [options] <args> -c, Set configuration file path location, default is ./pal.yml -d, Set action definitions file directory location, default is ./actions Example: pal -c ./pal.yml -d ./actionsEvery cmd run includes the below built-in env variables.
PAL_UPLOAD_DIR - Full directory path to upload directory
PAL_GROUP - Group name
PAL_ACTION - Action Name
PAL_INPUT - Input provided, override default value
PAL_REQUEST - HTTP Request Context In JSON
{ "method": "", "url": "", "headers": { "": "" }, "query_params": { "": "" }, "body": "" }When OnError.Notification or OnSuccess.Notification is configured for the action, you can use available substitution variables in the notification message:
$PAL_GROUP - Group name
$PAL_ACTION - Action name
$PAL_INPUT - Input provided, override default value
$PAL_STATUS - Status of action run
$PAL_OUTPUT - Command error output
See latest example reference, here: https://github.com/marshyski/pal/blob/main/pal.yml
monitor: - action: system desc: Get primary system stats for monitoring auth_header: X-Monitor-System q1w2e3r4t5 output: true cmd: | echo '|===/ DOCKER STATS \===|' command -v docker 1>/dev/null && sudo docker stats --no-stream; echo echo '|===/ FREE MEMORY \===|' free -hbvtw; echo echo '|===/ DISK SPACE \===|' df -hT; echo echo '|===/ TOP CPU \===|' ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%cpu | head; echo echo '|===/ TOP MEMORY \===|' ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%mem | head; echo echo '|===/ TOP OPEN FILES \===|' lsof 2>/dev/null | cut -d" " -f1 | sort | uniq -c | sort -r -n | head; echo echo '|===/ UPTIME AND LOAD \===|' uptimeExample Request
curl -sk -H'X-Monitor-System: q1w2e3r4t5' 'https://127.0.0.1:8443/v1/pal/run/monitor/system'All actions can be defined in one file, or split into multiple .yml files. The -d CLI argument tells the program what directory to verify action yml files.
For more complete examples, see: https://github.com/marshyski/pal/tree/main/test