Pal
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.
Install / Use
/learn @marshyski/PalREADME
pal
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.
Table of Contents
- Use Cases
- Key Features
- Quick Start
- YAML Definitions Configuration
- API Endpoints
- Configurations
- Built-In Variables
- YAML Server Configurations
- Example Action Definition YML
Use Cases
- Tiny CI / Job Server
- Remote Server Management
- Server Monitoring / Testing
- Homelab Automation
Key Features
- 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 binary (20MB~) with no external dependencies
- 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)
Quick Start
Local Development
Prerequisites: Go 1.26 or higher
make
make certs
./pal -c ./pal.yml -d ./test
Docker
Run default insecure test configs
make linux
make certs
# Choose between make debian / alpine
# Default insecure test configurations on debian:stable-slim
make debian
# Default insecure test configurations on alpine:latest
make alpine
Generate random secrets for one-time use
docker run -d --name=pal -p 8443:8443 -v "$(pwd)"/pal.yml:/etc/pal/pal.yml:ro -v "$(pwd)"/actions:/etc/pal/actions:ro --init --restart=unless-stopped pal:latest
# See generated random secrets
docker logs pal
Run with your own configs and mount db
mkdir -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 --init --restart=unless-stopped pal:latest
Available Docker Run Env Variables:
# Default insecure test values
-e HTTP_LISTEN="0.0.0.0:8443"
-e HTTP_IPV6="false"
-e HTTP_TIMEOUT_MIN="10"
-e HTTP_BODY_LIMIT="90"
-e HTTP_MAX_AGE="3600"
-e HTTP_HEADERS='[]'
-e HTTP_PROMETHEUS='false'
-e HTTP_DISABLE_UI='false'
-e HTTP_UPLOAD_DIR='/pal/upload'
-e HTTP_USERS='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'
Vagrant
# 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 up
Systemd
sudo 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
DEB & RPM Builds
# Need nfpm to build RPM / DEB arm64 and amd64 files
make install-deps
make pkg-all
Default Access: https://127.0.0.1:8443 (See Configurations to customize)
YAML Definitions Configuration
# 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/finch/nerdctl container (default: null)
container:
# Container image to use
image: alpine:latest
# Run options
options: --security-opt=no-new-privileges:true --cap-drop=ALL --net=none
# Set action to run multiple cron style schedules
schedule:
- "*****"
# 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'
API Endpoints
Command Execution
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
Key-Value Store
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:passw
