# KAOS Networks Pi Runbook — Sat 11 July 2026
### TekCom Sankofa Life Workshop · Leimert Park · IIAB + Medical Content + Odoo + Keycloak

**Hardware:** Raspberry Pi 5 (16GB), 256GB+ microSD or NVMe
**Convention reminders:** YAML → GitHub → Portainer. No secrets in repos. Standalone Portainer endpoint (NOT swarm worker). odoo.conf file mount, never env-var passwords. postgres:18 uses `/var/lib/postgresql` mount path.

**Suggested repo:** `github.com/calteknet/kaos-pi` with `/stacks/odoo/` and `/stacks/keycloak/` directories. Commit the compose files below before touching Portainer.

---

## PART 1 — Medical ZIMs (START TONIGHT — multi-GB downloads)

If flashing from the 1TB golden image, skip base install and just add content. Otherwise IIAB 8.x first: `curl iiab.io/install.txt | sudo bash`

ZIM filenames include dates and change; always list current names first rather than trusting old URLs:

```bash
# On the Pi (or any machine — you can download to a USB and copy over)
mkdir -p ~/zims && cd ~/zims

# List current medical ZIMs available from Kiwix
curl -s https://download.kiwix.org/zim/other/ | grep -oP 'href="\K[^"]*' | grep -iE 'medline|hesperian' 
curl -s https://download.kiwix.org/zim/zimit/ | grep -oP 'href="\K[^"]*' | grep -iE 'who|health'

# Download (adjust filenames to what the listings above show as newest)
wget -c https://download.kiwix.org/zim/other/medlineplus_en_all_$(date +%Y)-*.zim   # or copy exact name from listing
wget -c https://download.kiwix.org/zim/other/hesperian_health_guides_en_*.zim       # Where There Is No Doctor family

# Optional heavyweights if space/time allow:
# wikipedia_en_medicine (WikiMed — the single best offline medical reference, ~2GB)
curl -s https://download.kiwix.org/zim/wikipedia/ | grep medicine
wget -c https://download.kiwix.org/zim/wikipedia/wikipedia_en_medicine_maxi_*.zim
```

Install into IIAB's Kiwix library:

```bash
sudo cp ~/zims/*.zim /library/zims/content/
sudo /opt/iiab/iiab/scripts/iiab-make-kiwix-lib   # rebuilds library.xml
# Verify at http://box.lan/kiwix (or the Pi's IP :3000)
```

Alternative: IIAB Admin Console → Install Content → ZIM files handles download + library rebuild in one step if the Pi has good upstream tonight.

**Hesperian PDFs fallback:** if the Hesperian ZIM listing comes up empty, grab the PDF library from hesperian.org/books-and-resources (free downloads) into `/library/www/html/local_content/hesperian/` — IIAB serves it as local content.

---

## PART 2 — Odoo 19 CE stack (kaos-o19)

`stacks/odoo/docker-compose.yml`:

```yaml
services:
  kaos-pg:
    image: postgres:18
    restart: unless-stopped
    environment:
      POSTGRES_USER: odoo
      POSTGRES_PASSWORD_FILE: /run/secrets/pg_pass
      POSTGRES_DB: postgres
    volumes:
      - kaos-pg-data:/var/lib/postgresql        # postgres:18 path convention
      - ./pg_pass.txt:/run/secrets/pg_pass:ro   # file NOT committed — .gitignore it
    networks: [kaos-net]

  kaos-o19:
    image: odoo:19
    restart: unless-stopped
    depends_on: [kaos-pg]
    ports:
      - "8069:8069"
      - "8072:8072"
    volumes:
      - kaos-o19-data:/var/lib/odoo
      - ./odoo.conf:/etc/odoo/odoo.conf:ro      # passwords live here, not in env
      - kaos-o19-addons:/mnt/extra-addons
    networks: [kaos-net]

volumes:
  kaos-pg-data:
  kaos-o19-data:
  kaos-o19-addons:

networks:
  kaos-net:
    driver: bridge
```

`stacks/odoo/odoo.conf` (commit a `odoo.conf.example` with placeholders; real file is .gitignored):

```ini
[options]
admin_passwd = CHANGE_ME_MASTER
db_host = kaos-pg
db_port = 5432
db_user = odoo
db_password = CHANGE_ME_MATCHES_PG_PASS
dbfilter = ^kaos.*$
proxy_mode = False
workers = 2
limit_memory_hard = 1677721600
limit_memory_soft = 1342177280
```

`.gitignore` in the repo root:

```
pg_pass.txt
odoo.conf
*.env
```

**dbfilter note:** `^kaos.*$` — one Pi, one org family. Create db `kaos-main` on first boot. This avoids the DB_FILTER drift bug from the shec.us migrations.

Deploy: Portainer (Pi registered as **standalone endpoint** via Edge Agent) → Stacks → Add from repository → point at `calteknet/kaos-pi`, compose path `stacks/odoo/docker-compose.yml`. Upload `odoo.conf` + `pg_pass.txt` to the stack directory on the Pi first (`scp` them — they're not in git).

---

## PART 3 — Keycloak stack (offline-capable SSO)

`stacks/keycloak/docker-compose.yml`:

```yaml
services:
  kc-pg:
    image: postgres:18
    restart: unless-stopped
    environment:
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD_FILE: /run/secrets/kc_pg_pass
      POSTGRES_DB: keycloak
    volumes:
      - kc-pg-data:/var/lib/postgresql
      - ./kc_pg_pass.txt:/run/secrets/kc_pg_pass:ro
    networks: [kc-net]

  keycloak:
    image: quay.io/keycloak/keycloak:26.0
    restart: unless-stopped
    depends_on: [kc-pg]
    command: start --http-enabled=true --hostname-strict=false --proxy-headers=xforwarded
    environment:
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://kc-pg:5432/keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD_FILE: /run/secrets/kc_pg_pass
      KC_BOOTSTRAP_ADMIN_USERNAME: admin
      KC_BOOTSTRAP_ADMIN_PASSWORD_FILE: /run/secrets/kc_admin_pass
    volumes:
      - ./kc_pg_pass.txt:/run/secrets/kc_pg_pass:ro
      - ./kc_admin_pass.txt:/run/secrets/kc_admin_pass:ro
      - kc-data:/opt/keycloak/data
    ports:
      - "8080:8080"
    networks: [kc-net]

volumes:
  kc-pg-data:
  kc-data:

networks:
  kc-net:
    driver: bridge
```

Add `kc_pg_pass.txt` and `kc_admin_pass.txt` to `.gitignore` too.

**Note:** separate postgres per stack — never two stacks against one postgres volume (the corruption rule). Two small postgres containers on a 16GB Pi 5 is fine.

### First-boot realm setup (10 min)
1. `http://<pi>:8080` → admin console → create realm **`ctn`**
2. Clients → Create: `odoo` (OpenID Connect, confidential, redirect `http://<pi>:8069/auth_oauth/signin`)
3. In Odoo: Settings → Integrations → OAuth Authentication → add provider with the `ctn` realm's discovery URL (`http://<pi>:8080/realms/ctn/.well-known/openid-configuration`)
4. Later, same pattern for Elgg (OIDC plugin) and Portainer (OAuth settings)

### On/offline sync
```bash
# After Saturday signups — export the realm (users + clients)
docker exec -it <keycloak-container> /opt/keycloak/bin/kc.sh export \
  --dir /opt/keycloak/data/export --realm ctn --users realm_file
docker cp <keycloak-container>:/opt/keycloak/data/export ./ctn-realm-11jul.json
# Monday: import into the swarm's Keycloak with kc.sh import
```

---

## PART 4 — Saturday topology & checklist

```
TrueConnect 5G hotspot (Kay) ──> Pi eth0/wlan1 (WAN, when available)
                                   │
                            IIAB Pi 5 (box.lan AP mode)
                                   │  wlan0 hotspot: "KAOS-Classroom"
        ┌──────────┬───────────┬───┴────────┬─────────────┐
     Kiwix :3000  Kolibri   Odoo :8069  Keycloak :8080  Photobooth
     (medical)                                            (separate box)
```

**Friday night:**
- [ ] ZIMs downloaded and library rebuilt — verify WikiMed/MedlinePlus/Hesperian load at box.lan/kiwix
- [ ] Both stacks green in Portainer; Odoo reachable at :8069, db `kaos-main` created
- [ ] Keycloak `ctn` realm created, Odoo OAuth login tested end-to-end
- [ ] Odoo: Events module configured (you enabled full components — build a "TekCom Sankofa 11 Jul" event so signups happen live Saturday)
- [ ] Test full offline mode: unplug WAN, confirm Kiwix + Odoo + Keycloak login all still work

**Pack list:** Pi + PSU, microSD backup of golden image, photobooth rig (Canon 750D + DS40 + Debian box), ethernet cable for the 5G hotspot, power strip.

**The demo script for Dr. Wong & Dr. Batie:** connect to KAOS-Classroom WiFi → no internet needed → open box.lan → medical library, event signup in Odoo, one login for everything. *That's* the distribution model: what runs in Leimert Park Saturday runs in Ghana, Sierra Leone, and the STEM54 van unchanged.
