roof ac0c16c97b Fix session cookie name collision when running multiple PIC instances on localhost
Flask's default cookie name ('session') is shared across all ports on the same
hostname. When two PIC instances are accessed via localhost:portA and localhost:portB,
logging into one overwrites the other's session cookie, causing repeated logouts.

Derive a unique 8-hex suffix from each instance's persistent SECRET_KEY and set
SESSION_COOKIE_NAME = 'pic_sess_<suffix>'. This ensures each cell uses a distinct
cookie name, so sessions are fully isolated regardless of hostname.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 09:15:42 -04:00
2025-09-14 03:31:14 -05:00
2025-09-12 23:04:52 +03:00
2025-09-12 23:04:52 +03:00
2025-09-12 23:04:52 +03:00
2025-09-12 23:04:52 +03:00
2025-09-12 23:04:52 +03:00

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 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

# 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

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:

make test-integration             # full suite (creates peers)
make test-integration-readonly    # read-only checks, safe to run anytime

Management Commands

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-<svc>      # follow logs for one service
make shell-<svc>     # 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=<pubkey>

License

MIT — see LICENSE.

S
Description
No description provided
Readme 1.6 MiB
Languages
Python 79.6%
JavaScript 18.7%
Shell 0.9%
Makefile 0.6%