One CLI over a directory of shell and Python scripts, driven by a YAML manifest.
A server accumulates scripts faster than anyone documents them. ssc reads YAML
front-matter out of the scripts themselves, writes a manifest.yaml from it, and
gives you one command to list, run, inspect and monitor what is there.
- Repository:
server-scripts-cli - Command:
ssc(short for Server Scripts CLI) - Binary:
ssc.shin the repository, installed asssc
This documentation uses ssc throughout for brevity.
- Manifest from front-matter:
generate-manifest.shreads a# ---YAML block from the head of each.shand.pyfile and writesmanifest.yaml - Four type tiers: Interactive, One-time, Background and Internal, listed in full
under
ssc list --help - Interactive types by default:
ssc listshows the five Tier 1 types (report,admin,diagnostic,check,orchestrator);--allshows the rest - One interface:
list,run,info,status,logs,validate,generate - systemd queries:
ssc statusreadssystemctl,ssc logsreadsjournalctlfor the service named in a script's front-matter - Filters: category, type, status, a name search, a limit, and full paths
- Bash and yq only: no Python or Node runtime, no package manager, no daemon
- Not a scheduler.
sscruns a script when you ask it to. Recurring execution stays with cron or a systemd timer;deployment: scheduledin the front-matter records that fact, it does not create the timer. - Not a sandbox.
ssc runexecs the script with your own permissions. The only gate is the script-name regex^[a-zA-Z0-9._-]+$(validate_script_nameinssc.sh) plus a confirmation prompt forrequires_root: true. Treat every script in the manifest as code you have read. See SECURITY.md. - Not remote. Everything happens on the machine you run it on. There is no agent, no inventory, no push.
statusandlogsneed systemd. They work on a Linux host withsystemctlandjournalctl. The other commands are portable Bash 4 and run on macOS too, but on macOS those two have nothing to query.
ssc list on the maintainer's own server when the screenshot was taken, with 150
scripts in the manifest, 65 of them carrying a Tier 1 type and therefore shown by
default. That tree has kept growing since, so read the two numbers as a shape, not
as a current count. A fresh clone of this repository has the four demo scripts
instead and shows three of them, since deploy-example is Tier 2.
git clone https://github.com/fidpa/server-scripts-cli
cd server-scripts-cli
# Read front-matter and write manifest.yaml
./generate-manifest.sh
# List the demo scripts, run one, inspect another
./ssc.sh list
./ssc.sh run backup-example
./ssc.sh info monitoring-examplegenerate-manifest.sh scans exactly one directory: scripts/ if it exists, and
examples/demo-scripts/ otherwise. The first match wins, so once you add a
scripts/ tree the demo scripts drop out of the manifest. The scan is recursive and
skips .venv/, venv/, node_modules/, __pycache__/ and __init__.py. A
script's category is the first directory below scripts/, which is why the demo
scripts come out with an empty category.
# One-liner, adds an alias to ~/.bashrc
curl -sSL https://raw.githubusercontent.com/fidpa/server-scripts-cli/main/install.sh | bash
./install.sh --local # alias in ~/.bashrc (default)
./install.sh --user # symlink to ~/.local/bin
./install.sh --system # copy to /usr/local/bin (requires sudo)Piping a script from the network into a shell means trusting the source. Clone first
and read install.sh if you would rather not.
Click to expand manual options
Option 1: Local repository (alias)
git clone https://github.com/fidpa/server-scripts-cli ~/server-scripts-cli
cd ~/server-scripts-cli
./generate-manifest.sh
echo "alias ssc='~/server-scripts-cli/ssc.sh'" >> ~/.bashrc
source ~/.bashrcOption 2: System-wide (copy binary)
sudo cp ssc.sh /usr/local/bin/ssc
sudo cp generate-manifest.sh /usr/local/bin/
sudo chmod +x /usr/local/bin/{ssc,generate-manifest.sh}Option 3: User-local (symlink)
mkdir -p ~/.local/bin
ln -s $(pwd)/ssc.sh ~/.local/bin/sscAll three end up with the command named ssc. ssc looks for manifest.yaml in the
repository root it resolves through git rev-parse --show-toplevel, falling back to
the directory of the resolved script path, so a symlink keeps working while a copied
binary needs its own manifest next to it, or SSC_MANIFEST_FILE pointing at one.
| Command | Purpose |
|---|---|
ssc list |
List scripts with filters (Tier 1 types by default) |
ssc list --all |
Include One-time, Background and Internal types |
ssc run <name> [args...] |
Execute a script, passing further arguments through |
ssc info <name> |
Show a script's manifest entry |
ssc status |
Query systemd service state, timers and recent logs |
ssc logs <name> |
Show journalctl output for the script's service |
ssc validate |
Check YAML syntax and that every path in the manifest exists |
ssc generate |
Regenerate the manifest from front-matter |
Output of ssc --help, verbatim except for the closing MANIFEST: line, which
prints the absolute path of the manifest ssc resolved for you:
ssc - Server Scripts CLI v1.4.0
USAGE:
ssc <command> [options]
COMMANDS:
list List all scripts in manifest
run Execute a script by name
info Show detailed script information
status Show systemd service status
logs Show service logs (journalctl)
validate Validate manifest integrity
generate Regenerate manifest from front-matter
help Show this help message
LIST OPTIONS:
-c, --category <name> Filter by category (production, operations, etc.)
-s, --status <status> Filter by status (active, deprecated, unknown)
-t, --type <type> Filter by type (admin, report, diagnostic, library, helper, daemon)
-p, --paths Show full paths
-n, --limit <num> Limit output
--search <term> Search by name
RUN OPTIONS:
-f, --force Force run even if deprecated
STATUS OPTIONS:
-s, --script <name> Status for specific script
-t, --timers Show timer overview
-l, --logs Include recent logs
-n, --lines <num> Number of log lines
EXAMPLES:
ssc list --status active
ssc list --category operations --type backup
ssc run backup-example --help
ssc info monitoring-example
ssc status --timers
ssc logs health-check -n 50
ENVIRONMENT:
SSC_MANIFEST_FILE Manifest to read (default: manifest.yaml in the repo root)
SSC_REPO_ROOT Directory that script paths resolve against
SSC_LOG_LEVEL debug, info (default), warning, error
SSC_COLOR auto (default), always, never
SSC_SYSTEMD_ENABLED false makes status and logs skip systemd
SSC_EXEC_TIMEOUT Seconds before run kills the script (0 = off)
SSC_STRICT_VALIDATION true checks every manifest field against the schema
Also read from ~/.config/ssc/ssc.env; see config/ssc.env.example
The top-level help omits one option that ssc list accepts: -a, --all. It is
listed by ssc list --help, which also prints the full type tiers.
Add a # --- block to the head of a script; generate-manifest.sh reads the first
30 lines of each file looking for it:
#!/bin/bash
# ---
# deployment: manual
# service: backup.service
# status: active
# type: admin
# requires_root: true
# ---
#
# Your script hereAll five fields plus the derived path and category end up in manifest.yaml.
See docs/MANIFEST_SCHEMA.md for the value sets.
- Bash: 4.0+ (
ssc.shuses${var,,}case conversion, which Bash 3 rejects) - yq: mikefarah/yq v4+.
sscfeature-tests it withyq eval '.' /dev/nulland refuses the Pythonyqwrapper, which has noevalsubcommand - systemd: only for
ssc statusandssc logs
ssc needs none: every variable below has a default that matches what it does when
you set nothing.
| Variable | Default | Description |
|---|---|---|
SSC_MANIFEST_FILE |
manifest.yaml in the repository root |
Read the manifest from somewhere else |
SSC_REPO_ROOT |
git rev-parse --show-toplevel, else the directory of ssc.sh |
Directory that script paths are resolved against |
SSC_LOG_LEVEL |
info |
debug, info, warning, error |
SSC_COLOR |
auto |
auto (color when stdout is a terminal), always, never |
SSC_SYSTEMD_ENABLED |
true |
false makes status and logs return without calling systemd |
SSC_EXEC_TIMEOUT |
0 |
Seconds before ssc run kills the script; 0 disables it |
SSC_STRICT_VALIDATION |
false |
true checks every manifest field against the schema |
The log level filters the status messages of ssc itself. What list, info and
status print is data and goes out at every level, so SSC_LOG_LEVEL=error gives
you the table without the commentary around it. debug prints the resolved
repository root, the resolved manifest and the exact command behind ssc run to
stderr.
Set them in your shell, or copy config/ssc.env.example
to ~/.config/ssc/ssc.env, which ssc sources at startup. A variable set in the
environment wins over the file. Sourcing executes the file, so ssc skips it and
says so when it is not owned by you or when group or others can write it -
chmod 600 it.
- Documentation Index - all documents
- Setup Guide - installation, configuration, systemd integration
- Manifest Schema - the YAML reference
- Troubleshooting - common issues
- CHANGELOG - what changed per release
examples/demo-scripts/ holds four runnable scripts, and
they are what a fresh clone puts into the manifest:
backup-example.sh- scheduled backup,requires_root: truemonitoring-example.sh- collects system metricshealth-check.sh- service health validation, tied tohealth-check.servicedeploy-example.sh- deployment automation, Tier 2, so--allto see it
Issues and pull requests are welcome. CONTRIBUTING.md has the workflow, CODE_OF_CONDUCT.md the ground rules, and SECURITY.md the address for anything that should not be a public issue.
MIT License - Copyright (c) 2025-2026 Marc Allgeier (fidpa)
See LICENSE for details.
Marc Allgeier (@fidpa)
