Files
pic/Makefile
T
2026-05-09 08:05:38 -04:00

339 lines
13 KiB
Makefile

# Personal Internet Cell - Makefile
# Provides easy commands for managing the cell
.PHONY: help start stop restart status logs clean setup check-deps init-peers \
update reinstall uninstall install \
build build-api build-webui \
start-core start-dns start-api start-wg start-webui \
backup restore \
test test-all test-unit test-coverage test-api test-cli \
test-phase1 test-phase2 test-phase3 test-phase4 test-all-phases \
test-integration test-integration-readonly \
test-e2e-deps test-e2e-api test-e2e-ui test-e2e-wg test-e2e \
reset-test-admin-pass \
show-admin-password reset-admin-password \
show-routes add-peer list-peers
# Detect docker compose command (v2 plugin preferred, fallback to v1 standalone)
DC := $(shell docker compose version >/dev/null 2>&1 && echo "docker compose" || echo "docker-compose")
# Default target
help:
@echo "Personal Internet Cell - Management Commands"
@echo ""
@echo "First install:"
@echo " check-deps - Install all required system packages (python3, docker, etc.)"
@echo " setup - Generate keys, write configs, create data dirs"
@echo " Env vars: CELL_NAME=mycell CELL_DOMAIN=cell VPN_ADDRESS=10.0.0.1/24 WG_PORT=51820"
@echo " init-peers - Reset peer list to empty"
@echo ""
@echo "Lifecycle:"
@echo " start - Start all services"
@echo " stop - Stop all services"
@echo " restart - Restart all services"
@echo " status - Show container status + API health"
@echo " logs - Follow logs from all services"
@echo " logs-<svc> - Follow logs for one service (e.g. make logs-api)"
@echo " shell-<svc> - Open shell in a container (e.g. make shell-api)"
@echo ""
@echo "Updates & reinstall:"
@echo " update - git pull + rebuild + restart (deploy latest code)"
@echo " reinstall - Full wipe and fresh install from current git checkout"
@echo " uninstall - Stop + remove containers; prompts whether to also delete data"
@echo ""
@echo "Build:"
@echo " build - Rebuild API image"
@echo " build-api - Rebuild API image (no cache)"
@echo " build-webui - Rebuild Web UI image (no cache)"
@echo ""
@echo "Individual services:"
@echo " start-dns - Start DNS only"
@echo " start-api - Start API only"
@echo " start-wg - Start WireGuard only"
@echo ""
@echo "Maintenance:"
@echo " backup - Backup config + data to backups/"
@echo " restore - List available backups"
@echo " clean - Remove containers and volumes (keeps config/data dirs)"
@echo " show-admin-password - Print the admin password (reads setup file or prompts to reset)"
@echo " reset-admin-password - Generate a new random admin password and print it"
@echo ""
@echo "Tests:"
@echo " test - Run all tests"
@echo " test-coverage - Run tests with HTML coverage report"
@echo " test-integration - Full integration tests (needs running stack)"
@echo " test-integration-readonly - Read-only integration tests (safe to run anytime)"
@echo ""
@echo "Peers:"
@echo " list-peers - List configured WireGuard peers"
@echo " show-routes - Show WireGuard routing table"
# ── Dependencies & setup ──────────────────────────────────────────────────────
check-deps:
@sudo sh scripts/check_deps.sh
setup: check-deps
@echo "Setting up Personal Internet Cell..."
@sudo chown -R $$(id -u):$$(id -g) config/ data/ 2>/dev/null || true
CELL_NAME=$(or $(CELL_NAME),mycell) \
CELL_DOMAIN=$(or $(CELL_DOMAIN),cell) \
VPN_ADDRESS=$(or $(VPN_ADDRESS),10.0.0.1/24) \
WG_PORT=$(or $(WG_PORT),51820) \
WG_PRIVATE_KEY="$(WG_PRIVATE_KEY)" \
WG_PUBLIC_KEY="$(WG_PUBLIC_KEY)" \
python3 scripts/setup_cell.py
init-peers:
@echo "Initializing peer configuration..."
@echo '[]' > data/api/peers.json
@echo "Peer configuration initialized."
# ── Lifecycle ─────────────────────────────────────────────────────────────────
start:
@echo "Starting Personal Internet Cell..."
PUID=$$(id -u) PGID=$$(id -g) $(DC) --profile full up -d --build
@echo "Services started. Check status with 'make status'"
stop:
@echo "Stopping Personal Internet Cell..."
PUID=$$(id -u) PGID=$$(id -g) $(DC) --profile full down
@echo "Services stopped."
restart:
@echo "Restarting Personal Internet Cell..."
PUID=$$(id -u) PGID=$$(id -g) $(DC) restart
@echo "Services restarted."
status:
@echo "Personal Internet Cell Status:"
@echo "================================"
$(DC) ps
@echo ""
@echo "API Status:"
@curl -s http://localhost:3000/health || echo "API not responding"
logs:
$(DC) logs -f
logs-%:
$(DC) logs -f $*
shell-%:
docker exec -it cell-$* /bin/bash 2>/dev/null || docker exec -it cell-$* /bin/sh
# ── Updates & reinstall ───────────────────────────────────────────────────────
update:
@echo "Pulling latest code..."
@git stash --include-untracked --quiet 2>/dev/null || true
git pull
@git stash pop --quiet 2>/dev/null || true
@if [ ! -f config/mail/mailserver.env ]; then \
echo "Config missing — running setup first..."; \
$(MAKE) setup; \
fi
@echo "Rebuilding and restarting services..."
PUID=$$(id -u) PGID=$$(id -g) $(DC) --profile full up -d --build
@echo "Update complete. Run 'make status' to verify."
reinstall:
@echo "Reinstalling Personal Internet Cell from scratch..."
PUID=$$(id -u) PGID=$$(id -g) $(DC) --profile full down -v 2>/dev/null || true
@sudo rm -rf config/ data/
@$(MAKE) setup
@$(MAKE) start
@echo "Reinstall complete."
install:
@if [ -f /opt/pic/.installed ] && [ "$(FORCE)" != "1" ]; then \
echo "Already installed. Run 'make update' to update, or 'make install FORCE=1' to reinstall."; \
exit 0; \
fi
@echo "Running setup..."
@$(MAKE) setup
@echo "Installing systemd unit..."
@sudo cp scripts/pic.service /etc/systemd/system/pic.service
@-sudo systemctl daemon-reload && sudo systemctl enable pic
@sudo mkdir -p /opt/pic
@sudo touch /opt/pic/.installed
@echo "Installation complete. Run 'make start-core' to start core services."
uninstall:
@echo ""
@echo "This will stop and remove all containers."
@echo ""
@printf "Also delete config/ and data/? This cannot be undone. [y/N/cancel]: "; \
read ans; \
case "$$ans" in \
y|Y) \
echo "Stopping containers and removing images..."; \
PUID=$$(id -u) PGID=$$(id -g) $(DC) --profile full down -v --rmi all 2>/dev/null || true; \
echo "Deleting config/ and data/..."; \
sudo rm -rf config/ data/; \
echo "Uninstall complete. Git repo and scripts remain."; \
;; \
n|N|"") \
echo "Stopping and removing containers (keeping images and data)..."; \
PUID=$$(id -u) PGID=$$(id -g) $(DC) --profile full down 2>/dev/null || true; \
echo "Done. Images, config/ and data/ are untouched. Run 'make start' to bring it back up."; \
;; \
*) \
echo "Cancelled."; \
;; \
esac
@-sudo systemctl disable pic 2>/dev/null || true
@-sudo rm -f /etc/systemd/system/pic.service
@-sudo rm -f /opt/pic/.installed
@echo "Note: Data volumes were not deleted. To remove all data, manually delete config/ and data/."
# ── Build ─────────────────────────────────────────────────────────────────────
build:
@echo "Building API service..."
$(DC) build api
build-api:
@echo "Rebuilding API (no cache)..."
$(DC) build --no-cache api
$(DC) up -d api
build-webui:
@echo "Rebuilding Web UI (no cache)..."
$(DC) build --no-cache webui
$(DC) up -d webui
# ── Individual services ───────────────────────────────────────────────────────
start-core:
@echo "Starting core services (caddy, dns, wireguard, api, webui)..."
PUID=$$(id -u) PGID=$$(id -g) $(DC) --profile core up -d --build
@echo "Core services started. Run 'make start' to also bring up optional services."
start-dns:
$(DC) --profile core up -d dns
start-api:
$(DC) --profile core up -d api
start-wg:
$(DC) --profile core up -d wireguard
start-webui:
$(DC) --profile core up -d webui
# ── Maintenance ───────────────────────────────────────────────────────────────
clean:
@echo "Removing containers and volumes..."
$(DC) down -v
docker system prune -f
@echo "Done. config/ and data/ are untouched."
backup:
@echo "Creating backup..."
@mkdir -p backups
@sudo tar -czf backups/cell-backup-$(shell date +%Y%m%d-%H%M%S).tar.gz \
config/ data/ docker-compose.yml Makefile README.md
@sudo chown $$(id -u):$$(id -g) backups/cell-backup-*.tar.gz
@echo "Backup created in backups/."
restore:
@echo "Available backups:"
@ls -lh backups/cell-backup-*.tar.gz 2>/dev/null || echo "No backups found."
@echo ""
@echo "To restore: tar -xzf backups/cell-backup-YYYYMMDD-HHMMSS.tar.gz"
# ── Tests ─────────────────────────────────────────────────────────────────────
test:
@echo "Running unit tests..."
python3 -m pytest tests/ --ignore=tests/e2e --ignore=tests/integration -q
test-all: test test-integration test-e2e-api test-e2e-ui
@echo "All test suites complete."
test-unit:
python3 -m pytest tests/ --ignore=tests/e2e --ignore=tests/integration -q
test-coverage:
python3 -m pytest tests/ --ignore=tests/e2e --ignore=tests/integration \
--cov=api --cov-report=html --cov-report=term-missing --cov-fail-under=70 -v
test-integration:
@echo "Running full integration tests (requires running PIC stack)..."
PIC_HOST=$${PIC_HOST:-localhost} python3 -m pytest tests/integration/ -v
test-webui:
@echo "Running webui unit tests (requires node; builds a disposable container)..."
docker run --rm -v "$(PWD)/webui:/app" -w /app node:18-alpine \
sh -c "npm install --silent && npm test"
test-integration-readonly:
@echo "Running read-only integration tests (no peer creation)..."
PIC_HOST=$${PIC_HOST:-localhost} python3 -m pytest tests/integration/test_live_api.py tests/integration/test_webui.py -v
test-api:
python3 -m pytest tests/test_api_endpoints.py -v
test-cli:
python3 -m pytest tests/test_cli_tool.py -v
# ── E2E tests ─────────────────────────────────────────────────────────────────
# Run `make test-e2e-deps` once to install dependencies, then use the other targets.
# Admin password is read from data/api/.test_admin_pass (written by reset-test-admin-pass).
# Override: make test-e2e-api PIC_ADMIN_PASS=mypassword
test-e2e-deps:
sudo pip3 install --break-system-packages -r tests/e2e/requirements.txt
sudo python3 -m playwright install --with-deps chromium
test-e2e-api:
@PIC_HOST=$${PIC_HOST:-localhost} python3 -m pytest tests/e2e/api -v
test-e2e-ui:
@PIC_HOST=$${PIC_HOST:-localhost} python3 -m pytest tests/e2e/ui -v
test-e2e-wg:
@PIC_HOST=$${PIC_HOST:-localhost} sudo -E python3 -m pytest tests/e2e/wg -v -p no:xdist
test-e2e: test-e2e-api test-e2e-ui test-e2e-wg
reset-test-admin-pass:
ifndef PIC_TEST_ADMIN_PASS
$(error Usage: make reset-test-admin-pass PIC_TEST_ADMIN_PASS=newpassword)
endif
python3 scripts/reset_admin_password.py "$(PIC_TEST_ADMIN_PASS)"
# ── Admin password management ──────────────────────────────────────────────────
show-admin-password:
@sudo python3 scripts/reset_admin_password.py --show
reset-admin-password:
@docker exec cell-api python3 /app/scripts/reset_admin_password.py --generate
# ── Network / peers ───────────────────────────────────────────────────────────
show-routes:
@docker exec cell-wireguard wg show 2>/dev/null || echo "WireGuard not running"
list-peers:
@curl -s http://localhost:3000/api/peers | python3 -m json.tool || echo "API not responding"
add-peer:
@if [ -n "$(PEER_NAME)" ] && [ -n "$(PEER_IP)" ] && [ -n "$(PEER_KEY)" ]; then \
curl -X POST http://localhost:3000/api/peers \
-H "Content-Type: application/json" \
-d '{"name":"$(PEER_NAME)","ip":"$(PEER_IP)","public_key":"$(PEER_KEY)"}'; \
else \
echo "Usage: make add-peer PEER_NAME=name PEER_IP=10.0.0.x PEER_KEY=<pubkey>"; \
fi
# ── Dev ───────────────────────────────────────────────────────────────────────
dev:
$(DC) -f docker-compose.yml -f docker-compose.dev.yml up -d