A command-line tool for managing one or more Raspberry Pis from macOS. Deploy websites, monitor system health, restart services, view logs, and SSH into your Pis — all from a single pi command with an interactive REPL.
- Multi-Pi support — manage multiple Raspberry Pis from one tool, switch between them with
use - Tailscale support — automatically connects via LAN at home or Tailscale VPN when away
- Numbered selection — pick Pis, projects, and services by number instead of typing names
- Interactive REPL — type
piand stay in a persistent shell with tab-completion and command history - One-shot CLI — run
pi statusorpi deploy my-sitedirectly from your terminal for scripting - Self-updating — run
pi updateto pull the latest version and reinstall - System monitoring — CPU, RAM, disk, temperature, uptime at a glance
- Service management — check status, start, stop, or restart individual services or all at once
- One-command upgrade — run
pi upgrade-pisto update all packages on every Pi and restart their services - Log viewing — tail Apache error logs, with optional real-time streaming
- One-command deploys — rsync to Pi, restart Apache, purge Cloudflare cache
- SSH — opens in a new Terminal.app window so the REPL stays running
- Setup wizard — generates SSH keys, copies them to the Pi, tests the connection
- Backwards-compatible — old single-Pi configs are automatically migrated
- macOS (uses Terminal.app for SSH windows)
- Python 3.10+
- pipx (recommended) or pip
- One or more Raspberry Pis with SSH enabled
# Install pipx if you don't have it
brew install pipx
# Clone the repo (use SSH if you have it set up)
git clone https://github.com/Werizu/Pi-WebHost-Manager.git
cd Pi-WebHost-Manager
# Install
pipx install .The pi command is now available globally in your terminal.
piOn first run a setup wizard walks you through:
- Pi name (e.g.
homepi,mediaserver) - IP address / hostname and username
- SSH key generation (stored safely in
~/.pi-manager/keys/, not in the project directory) - Copying the public key to your Pi
- Which services to monitor (defaults:
apache2,mariadb,cloudflared) - Tailscale IP (optional — for remote access via VPN)
- Option to add more Pis
- Cloudflare setup — same account for all Pis or separate tokens per Pi
- Deploy projects (local path, remote path, target Pi, Cloudflare zone ID per project)
The wizard tests the SSH connection for each Pi and tells you if something's wrong. Re-run anytime with setup (REPL) or pi setup (CLI).
Run pi with no arguments to enter the interactive shell:
$ pi
PiManager v0.3.3
2 Pis: homepi(192.168.1.100) · mediaserver(192.168.1.101)
Type help for commands, exit to quit
─────────────────────────────────────
pi > use # numbered Pi selection:
# 1) homepi (192.168.1.100)
# 2) mediaserver (192.168.1.101)
# 0) Cancel
# > 2
pi > status # shows all Pis
pi > deploy # numbered project selection
pi > restart # numbered service selection
pi > status --pi homepi # explicit Pi via flag
pi > list-pis
pi > update # self-update from git
pi > exit
Features:
- Tab completion for all commands
- Command history persisted in
~/.pi-manager/history(arrow keys to navigate) - Styled prompt powered by prompt_toolkit
Pass a command directly for scripting or quick use:
pi status # all Pis
pi status --pi homepi # specific Pi
pi deploy my-site # uses Pi from project config
pi deploy my-site --pi mediaserver # override target Pi
pi services --pi homepi
pi restart # numbered service selection
pi restart apache2 # restart a specific service
pi stop # numbered service selection
pi stop apache2 --pi homepi # stop a service
pi start # numbered service selection
pi start apache2 # start a service
pi deploy # numbered project selection
pi ping # check all Pis reachable
pi ping --pi homepi # check specific Pi
pi rename-pi homepi mainpi # rename a Pi everywhere
pi edit-pi # edit host/user/key interactively
pi add-service nginx # add to active Pi's service list
pi remove-service # numbered selection to remove
pi open my-site # open project URL in browser
pi cache-clear my-site # purge Cloudflare cache only
pi tailscale list # show LAN/Tailscale IPs and connection mode
pi tailscale set homepi 100.64.0.1 # set Tailscale IP for a Pi
pi tailscale remove homepi # remove Tailscale IP
pi upgrade-pis # upgrade packages on all Pis + restart services
pi upgrade-pis --pi homepi # upgrade a specific Pi only
pi shutdown-pis # shut down all Pis at once
pi reboot-pis # reboot all Pis at once
pi update # self-update from git
pi list-pis
pi add-pi| Command | Description |
|---|---|
status |
Show system status for all Pis (or --pi <name> for one) |
services |
Show service status for all Pis (or --pi <name> for one) |
logs |
Show last 30 lines of Apache error log |
logs -n 100 |
Show more lines |
logs --live |
Stream logs in real-time (Ctrl+C to stop) |
restart |
Numbered service selection, then restart |
restart <service> |
Restart a specific service |
restart all |
Restart all monitored services |
stop |
Numbered service selection, then stop |
stop <service> |
Stop a specific service |
start |
Numbered service selection, then start |
start <service> |
Start a specific service |
ping |
Check if all Pis are reachable via SSH (with response time) |
ssh |
Open SSH in a new Terminal.app window |
deploy |
Numbered project selection, then deploy |
deploy <name> |
Deploy a specific project (rsync + cache purge) |
list-pis |
List all configured Pis |
add-pi |
Add a new Pi interactively |
remove-pi <name> |
Remove a Pi |
rename-pi <old> <new> |
Rename a Pi (updates all references including projects) |
edit-pi |
Edit a Pi's host, user, SSH key path, or Tailscale IP interactively |
use |
Numbered Pi selection to set active Pi (persists to config) |
use <pi-name> |
Set active Pi by name (persists to config) |
add-service <name> |
Add a service to a Pi's monitored services list |
remove-service |
Numbered service selection, then remove from monitor list |
remove-service <name> |
Remove a specific service from monitor list |
tailscale list |
Show LAN/Tailscale IPs and current connection mode for all Pis |
tailscale set <pi> <ip> |
Set Tailscale IP for a Pi |
tailscale remove <pi> |
Remove Tailscale IP from a Pi |
config |
Show current configuration |
add-project |
Add a new deploy project (numbered Pi/folder selection) |
list-projects |
List all configured projects |
remove-project <name> |
Remove a project |
open |
Numbered project selection, then open URL in browser |
open <name> |
Open a specific project's URL in the default browser |
cache-clear |
Numbered project selection, then purge Cloudflare cache |
cache-clear <name> |
Purge Cloudflare cache for a specific project (no deploy) |
upgrade-pis |
Upgrade all packages on every Pi via apt-get, then restart their services |
upgrade-pis --pi <name> |
Upgrade packages on a specific Pi only |
shutdown |
Shut down the active Pi (asks for confirmation) |
reboot |
Reboot the active Pi (asks for confirmation) |
shutdown-pis |
Shut down all Pis at once (asks for confirmation) |
reboot-pis |
Reboot all Pis at once (asks for confirmation) |
setup |
Re-run the setup wizard |
update |
Update PiManager to the latest version from git |
uninstall |
Remove config and uninstall PiManager |
help |
Show all available commands |
exit / quit |
Exit the REPL |
All commands that target a specific Pi accept --pi <name> for scripting. Without it:
status,services, andpingshow all Pisuse,deploy,restart,stop, andstartwithout arguments show a numbered selection list- Other commands use the active Pi (set via
use) or the default Pi
All config and keys are stored in ~/.pi-manager/:
~/.pi-manager/
├── config.json # Main configuration
├── keys/ # SSH keys (generated during setup)
│ ├── id_rsa
│ └── id_rsa.pub
└── history # REPL command history
Example config.json:
{
"pis": {
"homepi": {
"host": "192.168.1.100",
"user": "pi",
"ssh_key_path": "~/.pi-manager/keys/id_rsa",
"services": ["apache2", "mariadb", "cloudflared"],
"tailscale_host": "100.64.0.1"
},
"mediaserver": {
"host": "192.168.1.101",
"user": "pi",
"ssh_key_path": "~/.pi-manager/keys/id_rsa",
"services": ["plex", "samba"],
"cloudflare_api_token": "separate-token-if-different-account",
"tailscale_host": "100.64.0.2"
}
},
"default_pi": "homepi",
"cloudflare_api_token": "global-token-for-all-pis",
"projects": {
"my-site": {
"local_path": "/Users/you/Sites/my-site/",
"remote_path": "/var/www/my-site/",
"pi": "homepi",
"cloudflare_zone_id": "zone-id-for-my-site",
"url": "https://my-site.example.com"
},
"blog": {
"local_path": "/Users/you/Sites/blog/",
"remote_path": "/var/www/blog/",
"pi": "homepi",
"cloudflare_zone_id": "different-zone-id-for-blog",
"url": "https://blog.example.com"
}
}
}cloudflare_api_token(global) — used for all Pis by defaultcloudflare_api_token(per-Pi, optional) — overrides the global token if a Pi uses a different Cloudflare accountcloudflare_zone_id— always per project, since one Pi can host multiple sites with different zonestailscale_host(per-Pi, optional) — Tailscale IP for remote access when not on the home networkurl(per-project, optional) — website URL used by theopencommand to launch in your browser
Migration: If you're upgrading from v0.1.0, your old single-Pi config is automatically migrated to the new format on first load. No manual changes needed.
You can edit the file directly or use pi setup / pi add-project / pi add-pi.
When you run deploy <name>, PiManager:
- Resolves the target Pi — from
--piflag, project config, active Pi, or default - Rsyncs the local project folder to the Pi (excludes
.git,.DS_Store,node_modules) - Restarts Apache on the Pi
- Purges Cloudflare cache (if a zone ID and API token are configured)
pi-manager/
├── pi_manager/
│ ├── __init__.py
│ ├── cli.py # Click entry point, routes to REPL or one-shot
│ ├── repl.py # Interactive REPL (prompt_toolkit + rich)
│ ├── config.py # Config loading, saving, setup wizard, multi-Pi helpers
│ ├── ssh.py # SSH connection, remote commands, Terminal.app integration
│ ├── monitor.py # System status, services, log viewing
│ ├── services.py # Service start, stop, restart, shutdown, reboot
│ └── deploy.py # Rsync deploy + Cloudflare cache purge
├── pyproject.toml
└── README.md
| Package | Purpose |
|---|---|
| click | CLI framework and one-shot command parsing |
| rich | Tables, panels, styled terminal output |
| paramiko | SSH connections and remote command execution |
| prompt_toolkit | Interactive REPL with history and tab-completion |
| requests | Cloudflare API calls |
Where is the config stored?
~/.pi-manager/config.json. Edit it directly or use pi setup / pi add-project.
Where are the SSH keys stored?
In ~/.pi-manager/keys/. This keeps them out of any project directory so they can't accidentally be committed to git.
Can I manage multiple Pis?
Yes. Run pi add-pi to add more Pis at any time, or add them during pi setup. Use pi list-pis to see all configured Pis. use <name> switches the active Pi and persists it as the default so it survives restarts.
How does Pi resolution work for commands?
- Explicit
--pi <name>always wins - For
deploy: falls back to the project's configuredpifield - In the REPL: falls back to the active Pi set via
use - Finally falls back to
default_pifrom the config
I upgraded from v0.1.0 — do I need to change my config?
No. PiManager automatically migrates old configs to the new multi-Pi format. Your existing Pi becomes pi (the default name).
How do I change my SSH key path?
Run pi edit-pi to change host, user, or SSH key path interactively. Or run pi setup to reconfigure from scratch, or edit the config file directly.
How do I get a Cloudflare API token? Go to the Cloudflare dashboard and create a token with Zone > Cache Purge > Purge permission. Add it during setup or edit the config file.
Can I deploy multiple websites?
Yes. Run add-project for each site (assign each to a Pi), then deploy <name> to deploy individually.
How do I monitor custom services?
Enter your services as a comma-separated list during setup, use pi add-service <name> / pi remove-service to manage them at any time, or edit the services array per Pi in the config file.
How does open know the URL for a project?
Add a url field to the project in ~/.pi-manager/config.json, e.g. "url": "https://my-site.example.com". The open command uses macOS open to launch it in your default browser.
Can I clear the Cloudflare cache without deploying?
Yes. Run pi cache-clear <project> (or just pi cache-clear for a numbered selection). This reuses the same Cloudflare purge logic as deploy but skips rsync and Apache restart.
SSH opens in a new window — can I use the current terminal instead?
In one-shot mode (pi ssh) SSH also opens in a new Terminal.app window. This is by design so the REPL stays usable. For an inline session, run ssh -i ~/.pi-manager/keys/id_rsa pi@your-pi-ip directly.
How does Tailscale support work?
PiManager automatically detects whether you're on your home network (192.168.178.x / Fritz!Box) or not. At home, it connects via the Pi's LAN IP. Away from home, it uses the Tailscale IP if configured. Each operation shows the connection method once (e.g. -> LAN (192.168.178.201) or -> Tailscale (100.64.0.1)). Set Tailscale IPs during setup, via pi edit-pi, or with pi tailscale set <pi> <ip>.
Connection issues?
- "Can't reach the Pi" — check that the Pi is powered on and the IP is correct
- "SSH key rejected" — run
pi setupto regenerate and recopy the key - "Connection timed out" — verify your network connection
The easiest way to update:
pi updateThis pulls the latest changes from git, reinstalls via pipx, and shows a changelog. Your config and SSH keys in ~/.pi-manager/ are never touched.
On first run, update asks for the path to your local git clone and remembers it.
Manual update (same thing, just by hand):
cd Pi_Manager
git pull
pipx install . --forceFrom the REPL:
pi > uninstall
Or manually:
pipx uninstall pi-manager
rm -rf ~/.pi-managerMIT