A tiny FastAPI-based web UI to start, stop, restart, and monitor multiple app/sites on a Linux box (Raspberry Pi friendly). It prefers tmux sessions for process management and falls back to a background (PID) mode when tmux isn't available. Includes a live log viewer powered by Server-Sent Events (SSE) with a polling fallback.
- π₯οΈ Web dashboard to add/remove sites and run common actions
- π§° Works with tmux or a portable background runner (PID file)
- π Live logs: streams tail output via SSE, with copy/pause
- π Optional autostart and autorestart watchdog
- π Simple Basic or Bearer token auth (via config or env)
- βοΈ Single JSON config (
config.json) with hot reload
# 1) Clone and enter
git clone https://Github.com/WilleLX1/PiSiteManager.git
cd PiSiteManager
# 2) (Recommended) Python venv
python3 -m venv .venv
source .venv/bin/activate
# 3) Install deps
pip install -r requirements.txt
# 4) Optional: set auth via env file (see "Authentication" below)
# Example: echo 'PSM_USERNAME=admin\nPSM_PASSWORD=changeme' > auth.env
export $(grep -v '^#' auth.env | xargs) || true
# 5) Run
uvicorn manager:app --host 0.0.0.0 --port 8088 --workers 1Open: http://<your-pi-ip>:8088/
Tip: If you already used it once, just run
run.sh:sudo chmod +x run.sh ./run.sh
PiSite Manager stores configuration in config.json (created on first run).
Environment variables can override auth at runtime.
namemust be unique (no spaces or slashes).cwdmust exist on disk.cmdis executed insidecwd.logis appended to by the process (we pipe stdout/stderr to it).autostart/autorestartare honored by an internal watchdog.
PSM_BASE_DIRβ whereconfig.jsonis stored (default: current working dir)PSM_PID_DIRβ PID files directory for background mode (default:/tmp/pisite_pids)PSM_USERNAMEβ overridesauth.usernamePSM_PASSWORDβ overridesauth.passwordPSM_TOKENβ sets/overrides Bearer token
Changes via env vars apply on next start or after Reload config (see API).
If tmux is installed, each site runs in its own named session (<site.name>).
Start/stop/restart map directly to tmux session lifecycle.
If tmux is missing, a background process is launched with:
- unbuffered output (
stdbufif available, plusPYTHONUNBUFFERED=1) - stdout/stderr tee'd into the site log
- PID tracked in
PSM_PID_DIR/<name>.pid - polite group termination on stop/restart
You can mix both: if tmux becomes available later, new starts will use tmux.
- Page: Logs β
/logs/<name> - Shows last lines and then streams updates via SSE.
- If SSE cannot connect, it falls back to short-interval polling.
- Buttons: Copy, Pause/Resume, **Start/Stop/Restart`.
Note: Log file path is
cwd/logand is created/appended automatically.
- Dashboard
/lists all sites with status (running/stopped) and mode (tmux/background) - Add Site form (name/cwd/cmd/port/log + autostart/autorestart)
- Actions per row: Start, Stop, Restart, Logs, Delete
- Reload config button re-reads
config.json
Status is refreshed every ~3s.
Two options, both enabled if configured:
Set auth.username and auth.password (or set PSM_USERNAME/PSM_PASSWORD).
Example curl:
curl -u admin:changeme http://localhost:8088/api/statusSet auth.token or PSM_TOKEN:
curl -H "Authorization: Bearer MY_SUPER_TOKEN" http://localhost:8088/api/statusIf no credentials or token are set at all, the server allows unauthenticated access.
Base path under http://<host>:8088
| Method | Path | Description | ||
|---|---|---|---|---|
| GET | / |
Dashboard (HTML) | ||
| GET | /logs/{name} |
Logs viewer (HTML) | ||
| GET | /api/status |
JSON status for all sites | ||
| GET | /api/logs/{name} |
Plaintext tail (query: lines=200) |
||
| POST | /api/reload |
Reload config.json |
||
| POST | /api/sites |
Add site (form fields below) | ||
| POST | /api/sites/delete |
Delete site (form: name) |
||
| POST | /action |
Control site (form: name, `op=start |
stop | restart`) |
| GET | /stream/{name} |
SSE stream of new log lines |
name(str, required, unique, no spaces/slashes)cwd(str, required, must exist)cmd(str, required)port(int, optional)log(str, defaultactivity.log)autostart(true|false, defaultfalse)autorestart(true|false, defaultfalse)start_after_add(true|false, defaultfalse)
Example:
curl -u admin:changeme -X POST http://localhost:8088/api/sites \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "name=MySite" \
--data-urlencode "cwd=/home/pi/myapp" \
--data-urlencode "cmd=/usr/bin/python3 app.py" \
--data-urlencode "port=5000" \
--data-urlencode "log=activity.log" \
--data-urlencode "autostart=true" \
--data-urlencode "autorestart=true" \
--data-urlencode "start_after_add=true"Create /etc/systemd/system/pisite-manager.service:
[Unit]
Description=PiSite Manager
After=network-online.target
[Service]
Type=simple
WorkingDirectory=/home/pi/PiSiteManager
Environment=PSM_USERNAME=admin
Environment=PSM_PASSWORD=changeme
# Environment=PSM_TOKEN=MY_SUPER_TOKEN
ExecStart=/home/pi/PiSiteManager/.venv/bin/uvicorn manager:app --host 0.0.0.0 --port 8088 --workers 1
Restart=on-failure
User=pi
Group=pi
[Install]
WantedBy=multi-user.targetThen:
sudo systemctl daemon-reload
sudo systemctl enable --now pisite-manager-
401 Unauthorized: Supply Basic or Bearer creds. Verify env vars or
authblock. -
"Session not found or tmux not available": Install tmux or use background mode (it will auto-fallback).
sudo apt-get update && sudo apt-get install -y tmux -
No logs: Ensure your command prints to stdout/stderr. PiSite pipes output into the configured log file.
-
SSE blocked: Some reverse proxies buffer SSE. Disable buffering for
/stream/*or rely on polling fallback.
- Use random, strong passwords/tokens in production.
- Expose the service behind a reverse proxy with HTTPS (Caddy/Nginx/Traefik).
- Limit network access to trusted subnets if possible (firewall).
MIT (or your preferred license). See LICENSE.
{ "sites": [ { "name": "MySite", "cwd": "/home/pi/myapp", "cmd": "/usr/bin/python3 app.py", "port": 5000, // optional, for display only "log": "activity.log", // relative to cwd "autostart": true, // optional "autorestart": false // optional } ], "auth": { "username": "admin", "password": "password", "token": null // optional Bearer token } }