1
Dev – Install Internals
Dmitrii Iurco edited this page 2026-06-11 15:39:28 -04:00

Status: Active | Owner: @roof | Applies to: main (2026-06) | Updated: 2026-06-11

Dev – Install Internals

This page describes what the installer does, the make targets, the systemd unit, and debugging options.


install.sh

The installer at https://install.pic.ngo is a Bash script (install.sh in the repository root). It is served from the DDNS VPS.

Flags

Flag Effect
--debug Verbose output to terminal; stdout/stderr are tee'd to both the log file and the terminal
--force Bypass the idempotency check (.installed file)
PIC_DIR=/path Override install directory (default /opt/pic)
PIC_DEBUG=1 Same as --debug, usable as an env var

The install log is always written to /var/log/pic-install.log (or /tmp/pic-install.log if /var/log is not writable).

Seven steps

Step What happens
1 Detect OS from /etc/os-release; select apt, dnf, or apk
2 Install Docker, git, make; install and start host chrony
3 Create pic system user; add to docker group
4 Clone repository to ${PIC_DIR} (or git pull if already cloned)
5 Run make install — generates keys and config, writes systemd unit; scan stdout for the admin password banner and relay it to the terminal
6 Run make start-core to bring up the six core containers
7 Enable and start the pic systemd unit; poll http://127.0.0.1:3000/health for up to 60 seconds

After step 7, the installer prints the browser URL for the setup wizard.

Idempotency

If ${PIC_DIR}/.installed exists and --force is not passed, the installer exits with a message to run make update instead.


make targets

All container operations go through make. The Makefile is at the project root.

Stack lifecycle

# Run on: the cell server host, from /opt/pic (or your repo root)
make start           # docker compose up -d --build (full profile)
make start-core      # bring up only the six core containers
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>      # e.g. make logs-api, make logs-wireguard
make shell-<svc>     # shell inside container, e.g. make shell-api

Build

# Run on: your development machine or the cell server host
make build-api       # rebuild cell-api image after code change
make build-webui     # rebuild cell-webui image after code change

First install and maintenance

# Run on: the cell server host
make install         # generate keys, config, systemd unit; write .installed
make update          # git pull + rebuild + restart
make reinstall       # full wipe of config/ and data/, then setup + start
make uninstall       # stop containers; prompt to delete config/ and data/

Operations

# Run on: the cell server host, from /opt/pic
make backup          # tar config/ + data/ into backups/cell-backup-<timestamp>.tar.gz
make restore         # list available backup archives
make list-peers      # show peers via API
make show-routes     # wg show inside cell-wireguard
make show-admin-password    # print current admin password
make reset-admin-password   # generate and set a new random admin password

Tests

# Run on: your development machine
make test                        # pytest unit suite
make test-coverage               # with coverage; htmlcov/
make test-api                    # API endpoint tests only
make test-integration            # full integration tests (running stack required)
make test-integration-readonly   # read-only integration tests
make test-e2e-deps               # install Playwright (run once)
make test-e2e-api                # API e2e tests
make test-e2e-ui                 # UI e2e tests

systemd unit

make install copies scripts/pic.service to /etc/systemd/system/pic.service and runs systemctl daemon-reload && systemctl enable pic. The unit starts and stops the PIC stack on boot and shutdown. The installer also calls systemctl start pic after make start-core succeeds.

make uninstall calls systemctl disable --now pic, removes the unit file, and runs systemctl daemon-reload.

On Alpine Linux (OpenRC), the installer skips systemd and uses OpenRC's rc-update instead.


Host NTP

The installer installs and enables chrony on the host (step 2). This is not the same as the cell-ntp container — it is the host's own time synchronisation, needed for:

  • ACME certificate issuance (Let's Encrypt rejects challenges with skewed clocks)
  • pic.ngo DDNS registration (the registration endpoint requires a valid TOTP derived from the host clock)

If the installer cannot start chrony, it prints a warning. Proceed only after verifying the clock is accurate (date).


Debugging an install

To see the full verbose output of every installer step:

# Run on: your Linux server, as root
curl -fsSL https://install.pic.ngo | PIC_DEBUG=1 sudo -E bash

Or with the flag:

# Run on: your Linux server, as root
curl -fsSL https://install.pic.ngo | sudo bash -s -- --debug

The complete log (including all subprocess output) is always at /var/log/pic-install.log regardless of debug mode.

To debug a failing step after the installer exits:

# Run on: your Linux server
sudo cat /var/log/pic-install.log

.env overrides

A .env file in the project root overrides container IPs and port assignments. A .env file is not required — all variables have defaults. Create one only if you need to change ports or move containers to different IPs.

Variable Default Description
CELL_NETWORK 172.20.0.0/16 Docker bridge subnet
DNS_PORT 53 DNS (UDP + TCP)
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 Host port for the web UI
FLASK_DEBUG (unset) Set to 1 for Flask debug mode; do not use in production
PUID / PGID current user UID/GID for the WireGuard container