Version: v1 (core-only)
Palapatine is an open-source project built and maintained by Moutarlier Aldwin aka (tashikomaaa or corvus).
Licensed under the GNU General Public License v3.0.
Palpatine is a terminal-based fleet manager written in Bash. It provides a small, auditable, and extensible tool to run commands and manage services across many servers via SSH.
Design goals: simple, auditable, minimal dependencies (ssh, coreutils), friendly for ops engineers and open-source collaboration.
- PALPATINE — Galactic Server Control
- Table of contents
- What is Palpatine?
- Installation
- Project layout
- Quick start
- Configuration
- Usage — CLI & Interactive
- Scan JSON output format
- Security & best practices
- Troubleshooting (common issues)
- Contributing
- Testing
- Development notes (for maintainers)
- Example
~/.palpatine.conf(copy/paste) - License
Palpatine is a terminal-based fleet manager written in Bash. It provides:
- a TUI-like main menu for common fleet tasks (scan, run command, reboot, shutdown);
- a focus mode to control a single server interactively (run commands, SSH, reboot/shutdown);
- robust SSH handling: non-interactive first (good for automation), and optional interactive retry when a host requires a password;
- optional JSON scan output for integration with other tools;
- simple configuration via
/etc/palpatine.conf,~/.palpatine.conf, or./.palpatine.conf; - minimal i18n support (
UI_LANG=fr|en).
Palpatine intentionally keeps dependencies tiny so it runs on most Linux and macOS hosts with ssh installed.
Clone the repository and make the main script executable:
git clone https://github.com/tashikomaaa/palpatine.git palpatine
cd palpatine
chmod +x palpatineOptional — install Palpatine system-wide so palpatine is on your PATH:
sudo ./install.sh --prefix /usr/localCreate your server list files in the repository root. By default Palpatine uses servers.txt. You can also use servers-<group>.txt and set GROUP in config.
Example servers-prod.txt:
root@192.168.1.10
admin@192.168.1.11
web-01.example.com
palpatine/ # repo root
├── palpatine # main launcher (executable)
└── lib/
├── core.sh # core helpers, SSH wrapper, server loading
├── ui.sh # UI + i18n helpers
├── actions.sh # status/scan, run, reboot, shutdown
├── focus.sh # focus mode & server selection helpers
├── plugins.sh # plugin registry + loader
└── plugins/ # optional plugins (auto-loaded)
~/.palpatine.conf # optional per-user config (not in repo)
All code is pure Bash and intended to be easy to inspect and modify.
- Create or edit
~/.palpatine.conf(see configuration section below). - Create your
servers.txtorservers-<group>.txt. - Run:
./palpatineUse the TUI menu to scan your fleet, run commands, or focus on an individual server.
You can also run non-interactive actions from CLI:
./palpatine --group prod --action status
./palpatine --action run --cmd "df -h"
./palpatine --focus "root@web-01"
./palpatine --action reboot --dry-runPalpatine reads these config files (in order), where later files override earlier ones:
/etc/palpatine.conf~/.palpatine.conf./.palpatine.conf(project-local)
A sample ~/.palpatine.conf:
# ~/.palpatine.conf
GROUP="prod"
SSH_USER="root"
MAX_JOBS=8
SSH_TIMEOUT=5
UI_LANG="en" # supports en, fr, de, es, pt, it, ru, uk, pl, ja, ko, zh
DRY_RUN=false
# Scan output options
SCAN_OUTPUT_JSON=true
SCAN_OUTPUT_DIR="$HOME/palpatine_scans" # optional
SCAN_OUTPUT_FILE="" # optional (overrides DIR)
# Playbook / automation helpers
SCAN_INTERACTIVE_RETRY=true
# DISABLE_UPDATE_CHECK=true # uncomment to opt-out of git update checks
# UPDATE_CHECK_INTERVAL=86400 # seconds between update checks
# UPDATE_REMOTE=origin
# UPDATE_BRANCH=mainGROUP— default group name (usesservers-<GROUP>.txtif present).SSH_USER— default SSH username used for bare hostnames.MAX_JOBS— maximum parallel SSH jobs (concurrency).SSH_TIMEOUT— seconds forConnectTimeoutin SSH options.UI_LANG— UI language:en,fr,de,es,pt,it,ru,uk,pl,ja,ko,zh(aliases such aspt-brorzh-cnare also accepted).DRY_RUN— iftrue, no SSH commands will be executed (useful for testing).SCAN_OUTPUT_JSON—true/false; controls whetheraction_statuswrites JSON file.SCAN_OUTPUT_DIR— directory to write scan files (if not set, defaults to./logs/scans).SCAN_OUTPUT_FILE— exact file path for scan output (if set, overridesSCAN_OUTPUT_DIR).SCAN_REPORT—true/false; enables the Markdown scan report underlogs/reports/.SCAN_REPORT_DIR/SCAN_REPORT_FILE— override the default report destination.SCAN_INTERACTIVE_RETRY— iftrueand TTY present, when a host reports auth failure during scan, the user is prompted to retry interactively for that host.DISABLE_UPDATE_CHECK— set totrueto skip the automatic git update prompt (defaults tofalse).UPDATE_CHECK_INTERVAL— seconds between update checks (default86400).UPDATE_REMOTE/UPDATE_BRANCH— git remote/branch used to check for updates (defaultsorigin/main).
Examples:
# show status (ping + attempt uptime)
./palpatine --group prod --action status
# run a command across the fleet
./palpatine --action run --cmd "df -h"
# reboot all servers (use --dry-run to test)
./palpatine --action reboot --dry-run
# open focus on server #2 from servers.txt
./palpatine --focus 2
# add or remove entries from the active servers list
./palpatine --group prod --add-server "admin@web-04"
./palpatine --remove-server "legacy-host"Launch ./palpatine without args. The main menu offers:
- Scan systems (ping + uptime)
- Execute an order (enter a command to run in parallel)
- Reboot the fleet
- Shutdown the fleet
- Focus on a server for per-host actions
- Open the plugin bay (if plugins are installed)
- Add or remove entries from the active servers list (removal accepts either the hostname or its menu number)
Choose a server and you'll have options:
uptime(runsuptime -p)- run a custom command
- reboot / shutdown
- open an interactive SSH shell
Drop Bash scripts into lib/plugins/ and call register_plugin "id" "Label" handler_fn. Palpatine loads them automatically at startup and surfaces them under the Plugins menu item. Each plugin receives access to the shared helpers (run_ssh_cmd, draw_header, translations via L, etc.), so you can build custom workflows without touching the core menu.
The repository ships with two examples:
backup.sh— run fleet-widetarbackups of/etcor/var/www.monitoring.sh— stream uptime, disk, and memory stats across the fleet.
Document your plugin labels with L 'plugin.<name>.label' if you want multilingual support.
Playbooks chain multiple fleet actions without manual prompts. Create a plain-text file (for example playbooks/sample.playbook) with one directive per line:
# comments are ignored
note: Starting nightly scan
scan
run: df -h
sleep: 5
note: Finished
Supported directives:
scan— run the fleet scan workflow.run: <command>— execute a command across all servers (the playbook fails if any host reports an error or is unreachable).reboot/shutdown— trigger fleet reboots or shutdowns without interactive confirmation (pair withDRY_RUN=truefor rehearsals).sleep: <seconds>— wait between steps.note: <message>— log a message via the standardempirehelper.
Execute with ./palpatine --playbook path/to/file. Playbooks are mutually exclusive with --action, --focus, or server list edits so you can run them unattended.
When Palpatine is run from a git checkout it periodically checks the configured remote/branch (default origin/main) for new commits. If a newer revision is found, it offers to pull the update (git pull --ff-only). Set DISABLE_UPDATE_CHECK=true to skip this behaviour or adjust UPDATE_CHECK_INTERVAL for more/less frequent checks.
When SCAN_OUTPUT_JSON=true, scans are written to a file named:
${SCAN_OUTPUT_FILE}(if set), otherwise${SCAN_OUTPUT_DIR}/scan-YYYYMMDD_HHMMSS.json(where the defaultSCAN_OUTPUT_DIRis./logs/scans).
Output is a single JSON array — each element is an object per host:
[
{
"host": "root@192.168.1.10",
"ping": "ok",
"ssh": "ok",
"ssh_output": "up 2 days, 3:05",
"ssh_exit_code": 0,
"scanned_at": "2025-10-08T15:30:00Z"
}
]Field meanings:
host: the host string used (user@host or host).ping:"ok"or"failed".ssh: one of"ok","auth_failed","failed","failed_no_output","not_attempted", or"skipped".ssh_output: captured stdout/stderr fromuptime -p(truncated if long).ssh_exit_code: numeric exit status reported by the SSH command (nullif the command was skipped).scanned_at: ISO-8601 timestamp of the scan for that host.
You can easily pipe the file into jq for queries:
jq '.[] | {host, ping, ssh}' logs/scans/scan-20251008_153000.jsonWhen SCAN_REPORT=true, Palpatine also writes a Markdown report alongside the JSON output (default ./logs/reports/). The report contains a table summarising ping/SSH results per host and a final counter recap, making it easy to share scan results with teammates.
- Prefer SSH keys (ed25519 or RSA). Use
ssh-keygenandssh-copy-idto install your public key on servers. DRY_RUN=trueis useful to test the script without making changes.- Palpatine uses
ssh -o BatchMode=yesby default for non-interactive actions — this avoids blocking when running in automation/cron. - If
SCAN_INTERACTIVE_RETRY=true, Palpatine may prompt for passwords interactively only if run in a TTY — it will never store passwords. - File permissions: keep
~/.sshpermissions strict (700for.ssh,600forauthorized_keys). - Avoid running Palpatine as
rooton your workstation — prefer a normal user with SSH keys.
-
You attempted SSH without a key available on server.
-
Quick checks:
ssh -vvv user@host— inspect auth methods attempted.- Ensure
~/.ssh/id_ed25519(orid_rsa) exists. - Use
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@host.
- This is usually
set -ebehavior interacting with failing commands. - Palpatine wraps scan loops with
set +e/set -eto avoid that; if you modified files and see early exit, ensure those guards exist. - Run
bash -n lib/actions.sh(and other lib files) to syntax-check script files.
-
Often caused by:
- a truncated file (transfer interrupted),
- missing
fi,done, or}in a function, - CRLF (Windows) line endings.
-
Fixes:
- Ensure the file is complete —
wc -l lib/actions.sh. - Convert line endings:
dos2unix lib/actions.shorsed -i 's/\r$//' lib/actions.sh. - Run
bash -n lib/actions.shto pinpoint syntax errors.
- Ensure the file is complete —
localmust appear only inside functions. Check you haven't usedlocalat top-level.
Palpatine aims to be simple and auditable. If you want to contribute:
- Fork the repo.
- Add a clear commit message and provide tests or manual test steps (run
./tests/run.sh). - Open a PR with explanation and rationale.
Suggested improvements:
- additional built-in plugins (backups, observability, remediation)
- alternate visual themes (light mode, high contrast)
- optional output formats: CSV, JSONL, Prometheus metrics
- richer interactive UI (fzf integration or
dialog)
Automated checks live in tests/. Run everything locally with:
./tests/run.shThe script exercises the plugin registry/loader and ensures bundled plugins register correctly. Pair it with bash -n lib/*.sh lib/plugins/*.sh for quick syntax validation.
- All code is intentionally plain Bash (POSIX-ish with Bashisms).
- Keep comments and variable names English for wide collaboration.
- Use
bash -nfor syntax checks andshellcheckwhen possible. - Keep
set -euo pipefailat top-level but wrap tolerant sections withset +e/set -e.
# ~/.palpatine.conf - Palpatine configuration
GROUP="prod"
SSH_USER="deploy"
MAX_JOBS=8
SSH_TIMEOUT=5
UI_LANG="en"
DRY_RUN=false
SCAN_OUTPUT_JSON=true
SCAN_OUTPUT_DIR="$HOME/palpatine_scans"
SCAN_OUTPUT_FILE=""
SCAN_INTERACTIVE_RETRY=true