# Personal Internet Cell (PIC) PIC is a self-hosted digital infrastructure platform. It manages DNS, DHCP, NTP, WireGuard VPN, email, calendar/contacts (CalDAV), file storage (WebDAV), a reverse proxy, and a certificate authority — all controlled from a single REST API and React web UI. No manual config file editing is required for normal operations. --- ## Architecture ``` Browser └── React SPA (cell-webui :8081) └── Flask REST API (cell-api :3000, bound to 127.0.0.1) └── Docker SDK / config files ├── cell-caddy :80/:443 reverse proxy ├── cell-dns :53 CoreDNS ├── cell-dhcp :67/udp dnsmasq ├── cell-ntp :123/udp chrony ├── cell-wireguard :51820/udp WireGuard VPN ├── cell-mail :25/:587/:993 Postfix + Dovecot ├── cell-radicale 127.0.0.1:5232 CalDAV/CardDAV ├── cell-webdav 127.0.0.1:8080 WebDAV ├── cell-rainloop :8888 webmail (RainLoop) ├── cell-filegator :8082 file manager UI └── cell-webui :8081 React UI (Nginx) ``` All containers run on a custom Docker bridge network (`cell-network`, default `172.20.0.0/16`). Static IPs per container are set in `docker-compose.yml` and overridden via `.env`. The Flask API (`api/app.py`, ~2800 lines) contains all REST endpoints, runs a background health-monitoring thread, and manages the entire lifecycle of generated config artefacts: `Caddyfile`, `Corefile`, `wg0.conf`, and `cell_config.json` (the single source of truth at `config/api/cell_config.json`). The React frontend (`webui/`) is built with Vite + Tailwind CSS. All API calls go through `src/services/api.js` (Axios). Pages: Dashboard, Peers, Network Services, WireGuard, Email, Calendar, Files, Routing, Vault, Containers, Cell Network, Logs, Settings. --- ## Requirements - Linux host with the WireGuard kernel module loaded - Docker Engine and Docker Compose (v2 plugin or v1 standalone) - Python 3.10+ (for `make setup` and local dev only; not needed at runtime) - 2 GB+ RAM, 10 GB+ disk - Ports available: 53, 67/udp, 80, 443, 51820/udp, 25, 587, 993 --- ## Quick Start See [QUICKSTART.md](QUICKSTART.md) for step-by-step setup. --- ## Configuration Runtime configuration is controlled by `.env` in the project root. Copy `.env.example` to `.env` before first run. | Variable | Default | Description | |---|---|---| | `CELL_NETWORK` | `172.20.0.0/16` | Docker bridge subnet for all containers | | `CADDY_IP` through `FILEGATOR_IP` | `172.20.0.2`–`.13` | Static IP for each container | | `DNS_PORT` | `53` | DNS (UDP+TCP) | | `DHCP_PORT` | `67` | DHCP (UDP) | | `NTP_PORT` | `123` | NTP (UDP) | | `WG_PORT` | `51820` | WireGuard listen port (UDP) | | `API_PORT` | `3000` | Flask API (bound to `127.0.0.1`) | | `WEBUI_PORT` | `8081` | React UI | | `MAIL_SMTP_PORT` | `25` | SMTP | | `MAIL_SUBMISSION_PORT` | `587` | SMTP submission | | `MAIL_IMAP_PORT` | `993` | IMAP | | `RADICALE_PORT` | `5232` | CalDAV (bound to `127.0.0.1`) | | `WEBDAV_PORT` | `8080` | WebDAV (bound to `127.0.0.1`) | | `RAINLOOP_PORT` | `8888` | Webmail | | `FILEGATOR_PORT` | `8082` | File manager UI | | `WEBDAV_USER` | `admin` | WebDAV basic-auth username | | `WEBDAV_PASS` | _(required)_ | WebDAV basic-auth password — must be set before `make start` | | `FLASK_DEBUG` | _(unset)_ | Set to `1` to enable Flask debug mode; do not use in production | | `PUID` / `PGID` | current user | UID/GID passed to the WireGuard container | Cell identity (cell name, domain, VPN IP range) is configured via `make setup` or the Settings → Identity page in the UI after startup. The VPN IP range must be an RFC-1918 CIDR (`10.0.0.0/8`, `172.16.0.0/12`, or `192.168.0.0/16`); the API and UI both enforce this. --- ## Security Notes **Ports exposed to the network:** - `80` / `443` — Caddy (HTTP/HTTPS reverse proxy) - `51820/udp` — WireGuard - `25` / `587` / `993` — Mail (SMTP, submission, IMAP) - `53` — DNS (UDP + TCP) - `67/udp` — DHCP - `8081` — Web UI - `8888` — Webmail (RainLoop) - `8082` — File manager (Filegator) **Ports bound to `127.0.0.1` only** (not directly reachable from the network): - `3000` — Flask API - `5232` — Radicale (CalDAV) - `8080` — WebDAV The API has no authentication layer. It relies on `is_local_request()` to restrict sensitive endpoints (containers, vault) to requests originating from loopback or the cell's Docker network. The Docker socket is mounted into `cell-api`; treat access to port 3000 as equivalent to root access on the host. For internet-facing deployments, place the host behind a firewall or VPN and restrict access to the API and UI ports. --- ## Development ```bash # Start the full stack (builds api and webui images) make start # Rebuild a single image after code changes make build-api make build-webui # Run Flask API locally without Docker (port 3000) pip install -r api/requirements.txt python api/app.py # Run React UI dev server locally (port 5173, proxies /api to :3000) cd webui && npm install && npm run dev # Follow all container logs make logs # Follow logs for one service (e.g. api, dns, caddy, wireguard, mail) make logs-api # Open a shell inside a container make shell-api ``` --- ## Testing ```bash make test # run the full pytest suite make test-coverage # run with coverage; HTML report in htmlcov/ ``` Tests live in `tests/` (34 files, 642 test functions). Coverage includes: - All service managers (network, WireGuard, email, calendar, file, routing, vault, container) - API endpoint tests for each service area - Config manager (CRUD, validation, backup/restore) - IP utilities and Caddyfile generation - Peer registry and WireGuard peer lifecycle - Service bus pub/sub - Firewall manager - Pending-restart logic Integration tests (`tests/integration/`) require a running PIC stack: ```bash make test-integration # full suite (creates peers) make test-integration-readonly # read-only checks, safe to run anytime ``` --- ## Management Commands ```bash make setup # generate WireGuard keys, write configs, create data dirs make start # docker compose up -d --build make stop # docker compose down make restart # docker compose restart make status # container status + API health check make logs # follow all service logs make logs- # follow logs for one service make shell- # shell inside a container make update # git pull + rebuild + restart make reinstall # full wipe of config/ and data/, then setup + start make uninstall # stop containers; prompts whether to also delete config/ and data/ make backup # tar config/ + data/ into backups/ make restore # list available backups make list-peers # show WireGuard peers via API make show-routes # wg show inside the wireguard container make add-peer PEER_NAME=foo PEER_IP=10.0.0.5 PEER_KEY= ``` --- ## License MIT — see [LICENSE](LICENSE).