make install runs as root so all generated files (config/, data/) land as root:root. Added a chown pass in install.sh after make install completes, re-applying REPO_OWNER ownership. Also fixed the make setup chown to use SUDO_USER when invoked via sudo rather than always id -u (which is 0 when running as root). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Personal Internet Cell (PIC)
PIC is a self-hosted digital infrastructure platform. It packages DNS, DHCP, NTP, WireGuard VPN, email, calendar/contacts (CalDAV), file storage (WebDAV), a reverse proxy, a certificate authority, and optional third-party services — all managed through a single REST API and a 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)
└── Service managers + Docker SDK
├── cell-caddy :80/:443 Caddy reverse proxy (HTTPS/TLS)
├── 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 127.0.0.1:8888 Webmail (RainLoop)
├── cell-filegator 127.0.0.1:8082 File manager (Filegator)
└── cell-webui :8081 React UI (Nginx)
All containers run on a custom Docker bridge network (cell-network, default subnet 172.20.0.0/16). Static IPs per container are set in docker-compose.yml and can be overridden via .env.
The Flask API (api/app.py) contains REST endpoints and a background health-monitoring thread. Service managers are instantiated as singletons in api/managers.py. The single source of truth for runtime configuration is config/api/cell_config.json, managed by ConfigManager.
The React frontend (webui/) is built with Vite + Tailwind CSS. All API calls go through src/services/api.js (Axios).
Web UI pages: Dashboard, Peers, Network Services, WireGuard, Email, Calendar, Files, Routing, Vault, Containers, Cell Network, Connectivity, Service Store, Logs, Settings.
Features
- First-run wizard — browser-based setup at
/setup. On first start, all API requests redirect to/setup(HTTP 428) until the wizard is completed. Sets cell name, domain mode, timezone, admin password, and initial services. No manual.envediting required for identity. - Session-based auth — admin and peer roles. All
/api/*endpoints require an authenticated session after setup. CSRF protection on all state-changing requests. - WireGuard VPN — peer lifecycle management, automatic key generation, QR code config export, per-peer routing policy.
- Caddy HTTPS — automatic TLS via Let's Encrypt (DNS-01 or HTTP-01) or an internal CA, depending on domain mode.
- DDNS (pic.ngo) — registers a
<cell-name>.pic.ngosubdomain. Supported providers:pic_ngo,cloudflare,duckdns,noip,freedns. A background thread re-publishes the public IP every 5 minutes. - Service store — install/remove optional third-party services from the
pic-servicesindex atgit.pic.ngo. Manifests declare container images, Caddy routes, and iptables rules. - Extended connectivity — per-peer egress routing through alternate exits: WireGuard external, OpenVPN, or Tor. Configured via policy routing (fwmark + ip rule) in the WireGuard container.
- Cell-to-cell networking — WireGuard-based site-to-site links between PIC cells with service-level access control (calendar, files, mail, WebDAV) and a peer-sync protocol.
- Certificate authority —
vault_managerissues and revokes TLS certificates for internal services. - Network services — CoreDNS (
.cellTLD), dnsmasq DHCP, chrony NTP. - Email — Postfix + Dovecot via
docker-mailserver. - Calendar/contacts — Radicale CalDAV/CardDAV.
- File storage — WebDAV with per-user accounts; Filegator for browser-based file management.
- Container manager — start/stop/inspect containers, pull images, manage volumes via the Docker SDK.
- Firewall manager — iptables rule management (
firewall_manager.py). - Structured logging — JSON logs with rotation (5 MB / 5 backups per service), log search, and per-service verbosity control.
Requirements
- Linux host with the WireGuard kernel module loaded (
modprobe wireguardto verify) - Docker Engine and Docker Compose (v2 plugin or v1 standalone)
- Python 3.10+ (for
make setupand local development; 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 for step-by-step instructions.
The short version:
git clone gitea@192.168.31.50:roof/pic.git pic
cd pic
make start
# open http://<host-ip>:8081 — the setup wizard appears automatically
Configuration
Port assignments and container IPs are configured in .env in the project root. A .env file is not required for first start — all variables have defaults. Create one only if you need to change ports or container IPs.
| Variable | Default | Description |
|---|---|---|
CELL_NETWORK |
172.20.0.0/16 |
Docker bridge subnet |
CADDY_IP through FILEGATOR_IP |
172.20.0.2–.13 |
Static IP per 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 (127.0.0.1 only) |
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 (127.0.0.1 only) |
WEBDAV_PORT |
8080 |
WebDAV (127.0.0.1 only) |
RAINLOOP_PORT |
8888 |
Webmail |
FILEGATOR_PORT |
8082 |
File manager UI |
WEBDAV_USER |
admin |
WebDAV basic-auth username |
WEBDAV_PASS |
(unset) | WebDAV basic-auth password |
FLASK_DEBUG |
(unset) | Set to 1 for Flask debug mode; do not use in production |
PUID / PGID |
current user | UID/GID passed to the WireGuard container |
Cell identity (cell name, domain mode, timezone) is set through the first-run wizard on first start, or later through the Settings page in the UI.
Security
Ports exposed on all interfaces by default:
80/443— Caddy (HTTP/HTTPS reverse proxy)51820/udp— WireGuard25/587/993— mail53— DNS67/udp— DHCP8081— Web UI
Ports bound to 127.0.0.1 only:
3000— Flask API5232— Radicale (CalDAV)8080— WebDAV8888— Webmail8082— Filegator
The API uses session-based authentication (admin and peer roles). The Docker socket is mounted into cell-api; treat access to port 3000 as equivalent to root access on the host.
Before setup is complete, all /api/* requests except /api/setup/* and /health return HTTP 428 and a redirect to /setup.
CSRF protection (double-submit token in X-CSRF-Token header) applies to all POST, PUT, DELETE, and PATCH requests on /api/* once a user session exists, except /api/auth/* and /api/setup/*.
Cell-to-cell peer-sync endpoints (/api/cells/peer-sync/*) authenticate via source IP and WireGuard public key, not session cookies.
For internet-facing deployments, place the host behind a firewall and restrict access to the API and UI ports.
Development
# 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
make logs-api
# Open a shell inside a container
make shell-api
Testing
make test # run all unit tests (pytest, excludes e2e and integration)
make test-coverage # run with coverage; HTML report in htmlcov/
make test-api # run API endpoint tests only
Tests live in tests/. Integration tests require a running stack:
make test-integration # full suite (creates peers, modifies state)
make test-integration-readonly # read-only checks, safe to run anytime
End-to-end tests use Playwright:
make test-e2e-deps # install Playwright and dependencies (run once)
make test-e2e-api # API-level e2e tests
make test-e2e-ui # UI-level e2e tests
Management Commands
make start # docker compose up -d --build (full profile)
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-<svc> # follow logs for one service (e.g. make logs-api)
make shell-<svc> # shell inside a container (e.g. make shell-api)
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 show-admin-password # print current admin password
make reset-admin-password # generate and set a new random admin password
License
MIT — see LICENSE.