Homelab Management is a small server + CLI stack for managing homelab machines and Tapo smart plugs on a local network. It provides a FastAPI REST API used by a Python CLI client and an optional Telegram bot for interactive control.
- Server (
server/): FastAPI application that exposes the REST API. - Telegram bot (
server/telegram_bot.py,server/bot/): Optional bot that talks to the same services as the API. - CLI client (
client/): Python command-line tool (lab) that calls the API. - Docker deployment (
docker/): Dockerfile and compose file for running the server locally.
- Manage Tapo smart plugs (add/remove/list, switch on/off, read power metrics where supported).
- Manage servers (inventory, ping checks, Wake-on-LAN, and remote shutdown over SSH).
- Show a consolidated status view, including power and energy reporting.
- Optional Telegram bot with a button-driven menu (restricted by a user ID allowlist).
- Docker Engine and Docker Compose (for running the server locally)
- Python 3.7+ and pip (for the CLI client)
- Network reachability from the server host to:
- Tapo devices on your LAN
- Your target servers (for ping/WOL and optional SSH shutdown)
The recommended way to run the server is via Docker Compose.
-
Clone the repository:
git clone <repository-url> cd homelab-cli
-
Configure environment variables:
cd docker cp .env.example .env ${EDITOR:-nano} .env
Minimum required values:
TAPO_USERNAMETAPO_PASSWORDAPI_KEY
Optional (Telegram bot):
TELEGRAM_BOT_TOKENTELEGRAM_USER_IDS
-
Start the service:
# Docker Compose v2 docker compose up -d --build # If your environment still uses the v1 plugin # docker-compose up -d --build
-
Verify it is running:
curl http://localhost:8000/health
-
View logs:
docker compose logs -f
The compose file uses network_mode: host so the container can reach LAN devices directly (smart plugs, WOL broadcast, SSH to servers). If you change this to bridge networking you will need to map ports and ensure the container can still reach your LAN.
The server stores its configuration in a JSON file mounted from docker/data/:
- Host path:
docker/data/config.json - Container path:
/app/data/config.json(viaCONFIG_PATH)
The client is a lightweight Python CLI called lab.
cd client
./install.shThe script:
- Installs runtime dependencies (currently
requests) - Copies
laband thehomelab_clientpackage to~/.local/bin/ - Optionally prompts for server URL and API key
Make sure ~/.local/bin is in your PATH.
pip3 install --user requests
mkdir -p ~/.local/bin
cp client/lab.py ~/.local/bin/lab
chmod +x ~/.local/bin/lab
# Ensure ~/.local/bin is in PATH (bash example)
# echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
# source ~/.bashrclab config set-server http://<server-ip>:8000
lab config set-key <api-key>
lab config testClient configuration is stored at ~/.config/homelab-client/config.json. You can also use:
export HOMELAB_SERVER_URL=http://<server-ip>:8000
export HOMELAB_API_KEY=<api-key>A typical workflow looks like this:
# Add a smart plug
lab plug add office-plug 192.168.1.50
# Add a server (MAC is required for Wake-on-LAN)
lab server add workstation workstation.local AA:BB:CC:DD:EE:FF office-plug
# List inventory
lab plug list
lab server list
# Power control (server — turns on plug, then WOL)
lab on workstation
lab off workstation
# Direct plug control (no server entry needed)
lab plug on office-plug
lab plug off office-plug
# Consolidated status (follow mode refreshes continuously)
lab status
lab status -f
# Optional: set electricity price per kWh so status includes cost estimates
lab set price 0.2721
lab get priceThe Telegram bot is optional. When enabled, it provides an interactive menu for viewing servers/plugs and running power actions.
-
Create a bot via
@BotFatherand copy the token. -
Get your Telegram numeric user ID (for example via
@userinfobot). -
Set the following in
docker/.env:TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz TELEGRAM_USER_IDS=123456789,987654321
-
Restart the container:
cd docker docker compose restart -
Open a chat with your bot and send
/start.
The bot is restricted to the comma-separated list of user IDs in TELEGRAM_USER_IDS. Requests from other users are rejected.
If the host machine's SSH key is linked to a different GitHub user, push from a disposable container with its own deploy key.
# 1. Generate a deploy key in the host home directory (not ~/.ssh)
ssh-keygen -t ed25519 -f ~/github_deploy_key -N ""
# 2. Add the public key to GitHub → Settings → SSH keys (with write access)
cat ~/github_deploy_key.pub
# 3. Push from an Alpine container using the deploy key
docker run --rm --network host \
-v "$(pwd)":/repo \
-v ~/github_deploy_key:/tmp/deploy_key:ro \
-w /repo \
alpine:latest sh -c '
apk add --no-cache git openssh-client > /dev/null 2>&1 &&
mkdir -p /root/.ssh &&
cp /tmp/deploy_key /root/.ssh/id_ed25519 &&
chmod 600 /root/.ssh/id_ed25519 &&
git config --global user.email "your@email.com" &&
git config --global user.name "Your Name" &&
git config --global --add safe.directory /repo &&
GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=no" git push origin master
'Why
--network host? GitHub SSH (port 22) may be unreachable from Docker's default bridge network on some setups.
- All API endpoints require an API key via the
X-API-Keyheader. - API docs are available when the server is running:
- Swagger UI:
http://<server-ip>:8000/docs - ReDoc:
http://<server-ip>:8000/redoc
- Swagger UI:
Remote shutdown uses SSH from inside the container. The compose file mounts the host SSH directory read-only and the container startup script copies keys into place.
Requirements:
- Your Docker host must have working SSH access to the target machines.
- Configure passwordless
sudo poweroffon the target machines if required. - Set
SSH_USERindocker/.envif the SSH username differs from the host user.
- Components — component interaction diagram
- Modules — client/server module dependency graphs
- Power Flows — power on/off sequence diagrams
- Server deployment:
docs/SERVER_DEPLOYMENT.md - Client installation:
docs/CLIENT_INSTALLATION.md - Architecture notes:
docs/ARCHITECTURE.md
See LICENSE.