mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-06-30 05:27:07 +00:00
Compare commits
12 Commits
8996847745
...
7fafdd9d7c
| Author | SHA1 | Date | |
|---|---|---|---|
| 7fafdd9d7c | |||
| 8e3b62efc3 | |||
| 92c5b1f90f | |||
| c39743726a | |||
| 5c72d869a4 | |||
| a313816edc | |||
| 9c6e11d54d | |||
| 3770586a31 | |||
| bd4ef55a98 | |||
| b59d053c54 | |||
| 2494b55a18 | |||
|
|
839bab94a8 |
135
.claude/WIP.md
135
.claude/WIP.md
|
|
@ -1,5 +1,117 @@
|
|||
# WIP — Work In Progress
|
||||
*Mis à jour : 2026-05-14*
|
||||
*Mis à jour : 2026-05-15*
|
||||
|
||||
---
|
||||
|
||||
## 🔄 2026-05-15: Mail stack Phase 1 — LXC consolidation + source catch-up (Issue [#136](https://github.com/CyberMind-FR/secubox-deb/issues/136), PR [#141](https://github.com/CyberMind-FR/secubox-deb/pull/141) OPEN)
|
||||
|
||||
### Objective
|
||||
|
||||
Catch the in-repo source up to the test board's `/data/lxc/mail` + `/data/volumes/mail` + `10.100.0.10/24` reality (repo still references `/srv/lxc`, `/srv/mail`, `192.168.255.30`, separate `mail_container`/`webmail_container`). Phase 1 collapses the dual-container layout into a single mail LXC, deprecates legacy `secubox-mail-lxc` / `secubox-webmail` / `secubox-webmail-lxc` companion packages with `Breaks/Replaces`, and ships HAProxy mail-TCP snippets pointed at the new IP.
|
||||
|
||||
### Status (worktree `136-mail-stack-phase-1-source-catch-up-legac`, branch `feature/136-mail-stack-phase-1-source-catch-up-legac`)
|
||||
|
||||
- Phase 0 spec rev. 2 (`docs/superpowers/specs/2026-05-15-mail-stack-architecture-design.md`) + Phase 1 plan + rollback recipe committed to master
|
||||
- mailctl/mailser feature commits landed (latest `bade94f1`)
|
||||
- Versions bumped to 2.2.0 with `Breaks/Replaces` markers on the three legacy packages
|
||||
- HAProxy mail-TCP snippet targets new `10.100.0.10`
|
||||
- Test coverage shipped: 62-route endpoint-presence pytest + end-to-end acceptance smoke
|
||||
- **PR [#141](https://github.com/CyberMind-FR/secubox-deb/pull/141) opened** today — awaiting review + live deploy/cutover on test board
|
||||
|
||||
---
|
||||
|
||||
## 🔄 2026-05-15: remote-ui converged dashboard (Issue [#135](https://github.com/CyberMind-FR/secubox-deb/issues/135), PR [#140](https://github.com/CyberMind-FR/secubox-deb/pull/140) OPEN)
|
||||
|
||||
### Status
|
||||
|
||||
- PR [#137](https://github.com/CyberMind-FR/secubox-deb/pull/137) (initial converge round/ + square/ dashboards into `secubox_common` + pointer input on Pi 4B/400) **squash-merged `839bab94`**
|
||||
- **PR [#140](https://github.com/CyberMind-FR/secubox-deb/pull/140) OPEN** on `feature/135-converge-round-square-dashboards-into-re` — head `89968477`, 35 commits ahead of `origin/master`, net +35 / −2086 lines
|
||||
- Spec + plan landed on master (`b5e44e72`, `78316556`)
|
||||
|
||||
### Fixups added in this session (2026-05-15)
|
||||
|
||||
- `387fabb4` `fixup(common): pod_size=48 to match deployed icon sizes` — modules pods were rendering as letter placeholders on Pi 4B because `paint_pod_cluster(pod_size=40)` missed the deployed icon sizes (22/48/96/128); bumped to 48, radius 70→78 for clearance from the central button.
|
||||
- `f4acd5a9` `fixup(square): ship remote-ui/common/assets/icons` — the square build copied `common/python/` but skipped `common/assets/`; without the icons under `/var/www/common/assets/icons/`, `secubox_common.icons.load_module_icon` would fall back to first-letter placeholders.
|
||||
- `89968477` `fixup(square): install secubox-otg-gadget.sh to /usr/local/sbin` — the square `secubox-otg-gadget.service` ExecStarts that path; without the composer script the gadget never composed (zero USB enumeration events on the MOCHAbin, xHCI setup timeouts).
|
||||
|
||||
### Hardware bench (2026-05-15)
|
||||
|
||||
- **Pi 4B + 7" DSI (square):** converged dashboard renders ✓, icons ✓, all 4 right-panel tabs work (alerts/console/module_detail/mode_controls), USB-C OTG composes to MOCHAbin after the gadget-composer fixup landed.
|
||||
- **Pi Zero W + HyperPixel 2.1 (round):** boots clean, `fallback_manager.py` OFFLINE radar renders correctly on the existing image.
|
||||
|
||||
---
|
||||
|
||||
## 🔄 2026-05-15: Port `radar_concentric` into `secubox_common` (Issue [#138](https://github.com/CyberMind-FR/secubox-deb/issues/138), PR [#142](https://github.com/CyberMind-FR/secubox-deb/pull/142) OPEN)
|
||||
|
||||
### Status
|
||||
|
||||
- Issue opened today; PR [#142](https://github.com/CyberMind-FR/secubox-deb/pull/142) opened the same day on `feature/138-port-radar-concentric-into-secubox-commo` with title "Port radar_concentric into secubox_common + phase-aware dashboards (closes #138)"
|
||||
- New `secubox_common.painters.radar_concentric` module (phase-aware); module→arc-angle decoupled from list order via `DEFAULT_NAME_TO_ANGLE`
|
||||
- `RoundDashboard.layout(metrics, phase=0.0)` + `SquareDashboard.layout(metrics, phase=0.0)` — backward-compatible (phase=0 = still frame)
|
||||
- Square kiosk `__main__` drives `phase = (time.monotonic() * 12.0 / 60.0) % 1.0` at 12 RPM (matches deployed `fallback_manager._sweep_speed`)
|
||||
- 118 / 118 tests green (36 secubox_common incl. 8 new + 78 square kiosk + 4 round)
|
||||
- **Hardware bench (Pi 4B + 7" DSI, 2026-05-15):** rotating radar ✓ · icons ✓ · right panel ✓ — user-confirmed
|
||||
- `fallback_manager.py` migration deferred (visual-palette decision needed; follow-up)
|
||||
|
||||
---
|
||||
|
||||
## 🔄 2026-05-15: Round image cleanup — dead ifupdown + secubox sudo + OTG comment (Issue [#139](https://github.com/CyberMind-FR/secubox-deb/issues/139), PR [#143](https://github.com/CyberMind-FR/secubox-deb/pull/143) OPEN)
|
||||
|
||||
### Original misdiagnosis → rescoped
|
||||
|
||||
Initial report claimed "OTG networking dead": `/etc/network/interfaces.d/usb0` was a static stanza for ifupdown, but `ifupdown` was missing → `usb0` stayed DOWN. Wrong conclusion. Live-system probe via ACM serial showed the actual binding is on `usb1` (10.55.0.2/30), set programmatically by `secubox-otg-gadget.sh`. The dead ifupdown stanza never did anything; OTG was always working.
|
||||
|
||||
### Rescoped fix
|
||||
|
||||
- Drop dead `/etc/network/interfaces.d/usb0` (file + the inline heredoc in `build-eye-remote-image.sh` that recreated it)
|
||||
- Add `secubox` user to `sudo` group (so ACM serial recovery is possible — previously the only-path-in had no path-to-fix)
|
||||
- Rewrite the misleading `usb1 = ECM` comment in `secubox-otg-gadget.sh` — RNDIS+ECM share host_addr so host reaches 10.55.0.2 via either function
|
||||
|
||||
### Status
|
||||
|
||||
- Issue opened 2026-05-15 10:27; PR [#143](https://github.com/CyberMind-FR/secubox-deb/pull/143) opened 2 minutes later on `fix/139-round-image-usb0-otg-networking-dead-ifu`
|
||||
- Initial misdiagnosis annotated as a comment on issue #139 — diagnostic confusion came from the dead stanza being visible
|
||||
- **Hardware bench (Pi Zero W 1st gen, HyperPixel 2.1 Round, 2026-05-15):** all 3 fixes verified live:
|
||||
- `/etc/network/interfaces.d/` directory absent ✓
|
||||
- `secubox` in `sudo` group (`27(sudo)`) ✓
|
||||
- Gadget composer comment rewritten ✓
|
||||
- Bonus: ping `10.55.0.2` from host 3/3 received at 0.3 ms, SSH port 22 OPEN, both `usb0` + `usb1` UP @ 10.55.0.2/30 on the Pi
|
||||
- Mid-bench: caught a pre-existing Pi Zero W `dwc2` kernel panic under host xHCI reset hammering — unrelated to #143 (image was good, dwc2 driver instability on ARMv6 under USB stress); deferred to a separate investigation
|
||||
|
||||
---
|
||||
|
||||
## 🔄 2026-05-15: CMSD SPDX header rollout (Issue [#81](https://github.com/CyberMind-FR/secubox-deb/issues/81))
|
||||
|
||||
### Status
|
||||
|
||||
Worktree `secubox-deb-license-wt` on branch `feature/license-phase-b-full` at `aa1f7481` ("enroll all in-scope files via `**` allowlist (Phase B + C)"). Phase A + B + C work all on the branch but no PR opened yet.
|
||||
|
||||
---
|
||||
|
||||
## 🧹 2026-05-15: worktree + local-state housekeeping
|
||||
|
||||
### Cleaned 2026-05-15
|
||||
|
||||
- `secubox-deb-worktrees/127-add-remote-ui-square-variant-for-pi-4b-7` (Phase 1, PR #130 merged as `7c37415f`) — removed
|
||||
- `secubox-deb-worktrees/127-phase2-square-variant` (Phase 2, PR #131 closed/superseded) — removed
|
||||
- `secubox-deb-worktrees/127-phase3-python-kiosk` (Phase 3, PR #132 merged as `dee8bf8b`) — force-removed (had two stray untracked Signal Desktop apt-key files unrelated to the project, safe to discard)
|
||||
|
||||
All three feature branches deleted locally. `agent-worktree.sh clean` resolves by issue number which collides for multi-worktree issues like #127; used direct `git worktree remove` + `git branch -D`.
|
||||
|
||||
### Local master state
|
||||
|
||||
- `master` synced with `origin/master` at `a313816e` (pushed `839bab94..a313816e`, 6 commits). Was 6 ahead / 1 behind earlier; rebased then pushed.
|
||||
- Untracked: `.claude/settings.json` (pre-existing, intentional)
|
||||
|
||||
### Worktrees still active
|
||||
|
||||
| Worktree | Branch | Backing PR |
|
||||
|---|---|---|
|
||||
| `135-converge-round-square-dashboards-into-re` | `feature/135-…` | #140 OPEN |
|
||||
| `136-mail-stack-phase-1-source-catch-up-legac` | `feature/136-…` | #141 OPEN |
|
||||
| `138-port-radar-concentric-into-secubox-commo` | `feature/138-…` | #142 OPEN |
|
||||
| `139-round-image-usb0-otg-networking-dead-ifu` | `fix/139-…` | #143 OPEN |
|
||||
| `secubox-deb-license-wt` | `feature/license-phase-b-full` | none (SPDX rollout #81) |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -20,11 +132,24 @@ Replace Phase 2's Chromium+PySide6 dual-window stack with a single-process Pytho
|
|||
- Phase 2 PR #131 closed (superseded by Phase 3)
|
||||
- **Squash-merged 2026-05-14 as `dee8bf8b`** on master (after Phase 1 `7c37415f`)
|
||||
|
||||
### Followups
|
||||
### Hardware gates — 3 of 4 closed (2026-05-15)
|
||||
|
||||
- **Task 24 (Pi 400 manual sanity) — IN PROGRESS** this session: building `secubox-eye-square_0.2.0_arm64.img.xz` locally for flash to uSD.
|
||||
- **Task 23 (Pi 4B manual bench)** — still pending hardware (build + flash + boot + kiosk visible + OTG link to MOCHAbin).
|
||||
- Issue #127 stays open until both Pi 4B and Pi 400 benches pass.
|
||||
| Task | Hardware | Status |
|
||||
|------|----------|--------|
|
||||
| **Task 23** — Pi 4B square/ manual bench | Pi 4B + official 7" DSI 800×480 | ✅ kiosk renders correctly post-#134 fixes |
|
||||
| **Task 24** — Pi 400 square/ sanity | Pi 400 + HDMI 1920×1080 | ✅ same image, kiosk center-padded into letterbox (PR #134 second commit) |
|
||||
| **Task 19** — Pi Zero W round/ manual bench | Pi Zero W + HyperPixel 2.1 | ✅ booted from CI-built `secubox-eye-remote-2.2.1.img.xz`, rainbow ring dashboard clean post-`common/` |
|
||||
| Task 18 — round/ `diffoscope` regression gate | n/a (automated) | ⏳ still blocked on `hyperpixel2r.dtbo` prerequisite |
|
||||
|
||||
### Bug haul from the bench (fixed in PR [#134](https://github.com/CyberMind-FR/secubox-deb/pull/134), merged `a3a918ed`)
|
||||
|
||||
1. `/run/secubox` not recreated at boot (tmpfs wipe) → added `tmpfiles.d/secubox-eye-square.conf`
|
||||
2. `fonts-dejavu-core` missing from chroot apt-install → added
|
||||
3. `draw.text()` calls relied on Pillow legacy bitmap default (no Unicode) → `theme.DEFAULT_FONT` + `font=` kwarg on all 25 call sites
|
||||
4. `framebuffer.py` hardcoded 32bpp BGRA but `vc4drmfb` is 16bpp RGB565 → numpy RGB565 packer + `bits_per_pixel` auto-detect
|
||||
5. (followup) `framebuffer.py` hardcoded 800×480 → `virtual_size` auto-detect + center-pad for HDMI
|
||||
|
||||
Issue #127 closure now gated only on Task 18. Bug #139 (round image OTG networking) and enhancement #138 (radar to common) were surfaced from the same bench session — see today's PRs above.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
1618
docs/superpowers/plans/2026-05-15-mail-phase1-lxc-consolidation.md
Normal file
1618
docs/superpowers/plans/2026-05-15-mail-phase1-lxc-consolidation.md
Normal file
File diff suppressed because it is too large
Load Diff
46
docs/superpowers/runs/2026-05-15-mail-phase1-rollback.md
Normal file
46
docs/superpowers/runs/2026-05-15-mail-phase1-rollback.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Mail Phase 1 — Rollback recipe
|
||||
|
||||
Backups produced 2026-05-15 07:41 on test board 192.168.1.200 by
|
||||
`docs/superpowers/plans/2026-05-15-mail-phase1-lxc-consolidation.md` Task 0.
|
||||
|
||||
## What's in `/srv/backups/mail-phase1/`
|
||||
|
||||
| File | Size | Contents |
|
||||
|---|---|---|
|
||||
| `data-volumes-mail-2026-05-15-0741.tar.gz` | 48K | Entire `/data/volumes/mail/` tree — vmail dirs for `secubox.in/{gk2,bat,bourdon,lemurien,ragondin}`, Postfix lookup tables, ACME certs |
|
||||
| `lxc-mail-config-2026-05-15-0741.tar.gz` | 4.0K | `/data/lxc/mail/config` (the LXC's unprivileged-veth network config) |
|
||||
| `mail-toml-2026-05-15-0741.bak` | 0.4K | Original `/etc/secubox/mail.toml` (still has legacy keys) |
|
||||
| `pkglist-2026-05-15-0741.txt` | 0.7K | `dpkg -l` output for `secubox-mail*` + `secubox-webmail*` pre-deploy |
|
||||
|
||||
## Rollback procedure
|
||||
|
||||
If Phase 1 deploy breaks the mail stack on the board:
|
||||
|
||||
```bash
|
||||
ssh root@192.168.1.200 'set -euo pipefail
|
||||
lxc-stop -n mail 2>/dev/null || true
|
||||
|
||||
# Restore /data/volumes/mail (vmail + config + ssl)
|
||||
rm -rf /data/volumes/mail
|
||||
tar -xzf /srv/backups/mail-phase1/data-volumes-mail-2026-05-15-0741.tar.gz -C /
|
||||
|
||||
# Restore LXC config
|
||||
tar -xzf /srv/backups/mail-phase1/lxc-mail-config-2026-05-15-0741.tar.gz -C /
|
||||
|
||||
# Restore toml + downgrade packages
|
||||
cp /srv/backups/mail-phase1/mail-toml-2026-05-15-0741.bak /etc/secubox/mail.toml
|
||||
apt install --allow-downgrades -y \
|
||||
secubox-mail=2.1.0-1~bookworm1 \
|
||||
secubox-mail-lxc=1.1.0-1~bookworm1 \
|
||||
secubox-webmail=1.0.0-1~bookworm1 \
|
||||
secubox-webmail-lxc=1.1.0-1~bookworm1
|
||||
|
||||
systemctl restart secubox-mail nginx haproxy'
|
||||
```
|
||||
|
||||
## Priority guarantees
|
||||
|
||||
- The data tarball preserves the 5 live `secubox.in` mailboxes (`gk2`, `bat`,
|
||||
`bourdon`, `lemurien`, `ragondin`) and the ACME certs from Feb 2026.
|
||||
- Per spec rev. 2 invariant **I13**, this data MUST NOT be lost. If anything
|
||||
goes wrong, restoring this tarball is the first and most important step.
|
||||
|
|
@ -0,0 +1,316 @@
|
|||
# Mail Stack Architecture — Phase 0 Design (rev. 2)
|
||||
|
||||
**Date:** 2026-05-15 (rev. 2 — reconciled with board reality)
|
||||
**Status:** Approved direction; revised invariants pending user re-confirmation
|
||||
**Author:** Gérald Kerma <devel@cybermind.fr>
|
||||
**Scope:** Architecture-only. Each implementation phase below gets its own spec → plan → PR cycle.
|
||||
|
||||
> **Revision note (rev. 2, 2026-05-15):** Initial draft assumed a `/srv/lxc/` + `192.168.255.x` + `lxc.net.0.type = none` greenfield layout. Live board inspection on 2026-05-15 showed the actual single-`mail` LXC has already been hand-built on the test board with a modern unprivileged-veth layout under `/data/lxc/` + `/data/volumes/`. Invariants have been corrected; Phase 1 is reduced from "consolidate two LXCs" to "catch the repo source up to where the board already is, then deprecate the legacy package frame".
|
||||
|
||||
---
|
||||
|
||||
## 1. Goal
|
||||
|
||||
Deliver a "full-featured" multi-domain mail + collaboration stack inside a single LXC container, integrated with the existing SecuBox-Deb identity (`secubox-users`), DNS (`secubox-dns`) and storage (`secubox-nextcloud`) services.
|
||||
|
||||
**Definition of full-featured for this project:**
|
||||
- Standards-compliant SMTP/IMAP with TLS, SPF, DKIM, DMARC, ARC
|
||||
- Per-user features: ManageSieve filters, quotas, vacation, app passwords
|
||||
- Multi-domain virtual hosting with per-domain DKIM keys
|
||||
- Roundcube webmail with PGP (Enigma), 2FA, ManageSieve UI, CardDAV/CalDAV bridged to Nextcloud
|
||||
- Mailing lists (mlmmj) and shared mailboxes
|
||||
- imapsync-based migration from OpenWrt SecuBox or any external IMAP
|
||||
- End-user self-service portal (password, vacation, aliases, app-passwords, sieve)
|
||||
- Observability + outbound abuse policies
|
||||
|
||||
## 2. Non-goals
|
||||
|
||||
- ActiveSync / EAS protocol (Z-Push, grommunio) — not in scope this round
|
||||
- JMAP (Cyrus pivot) — not in scope
|
||||
- Mail archival / legal hold — not in scope
|
||||
- Independent CardDAV/CalDAV server inside the mail LXC — delegated to `secubox-nextcloud`
|
||||
|
||||
## 3. Locked invariants
|
||||
|
||||
Each phase below MUST respect these. Changing one requires a new Phase 0 revision.
|
||||
|
||||
| # | Invariant |
|
||||
|---|---|
|
||||
| **I1** | Exactly ONE LXC named `mail` at `/data/lxc/mail` (symlinked from `/var/lib/lxc/mail`). No separate `mailserver` or `roundcube` LXCs. |
|
||||
| **I2** | LXC is **unprivileged** (`lxc.idmap = u 0 100000 65536`), veth on bridge `br-lxc`, IPv4 `10.100.0.10/24` with gateway `10.100.0.1`. AppArmor + `debian.common.conf` includes. |
|
||||
| **I3** | Antispam stack is **Rspamd** (single daemon: greylisting + spam scoring + DKIM sign+verify + SPF + DMARC + ARC). ClamAV remains as separate AV milter. SpamAssassin, Postgrey, OpenDKIM, opendmarc removed in Phase 2. |
|
||||
| **I4** | CardDAV + CalDAV are **not** served from the mail LXC. Roundcube plugins point at `https://nextcloud.gk2.secubox.in/remote.php/dav/`. |
|
||||
| **I5** | Mail accounts are provisioned **by** `secubox-users`. The mail stack is a downstream consumer. Local Dovecot is the materialized projection. |
|
||||
| **I6** | Outbound delivery is **direct on port 25**. No smarthost relay. |
|
||||
| **I7** | Multi-domain virtual users. Mailbox path: `/data/volumes/mail/vmail/<domain>/<user>/Maildir/`. Per-domain DKIM key (or Rspamd selector after Phase 2). |
|
||||
| **I8** | Existing data is migrated from OpenWrt SecuBox via **imapsync** (Phase 7). |
|
||||
| **I9** | Webmail = Roundcube. SOGo / Cyrus / grommunio rejected. |
|
||||
| **I10** | All container daemons listen on the LXC IP (`10.100.0.10`). Exposed to LAN/WAN via host's HAProxy (SMTP/IMAPS TCP pass-through) + nginx (admin + webmail HTTPS). |
|
||||
| **I11** | Configuration source of truth: `/etc/secubox/mail.toml` on the host. Rendered into the LXC by `mailctl`. No editing config inside the LXC. |
|
||||
| **I12** | **Persistent data lives on the host under `/data/volumes/mail/{vmail,config,ssl}`** and is bind-mounted into the LXC. Destroying the LXC rootfs MUST be safe — no production data lives in the rootfs. |
|
||||
| **I13** | **Existing mail data on the test board MUST be preserved.** As of 2026-05-15 the board hosts the `secubox.in` domain with five live mailboxes (`gk2`, `bat`, `bourdon`, `lemurien`, `ragondin`) under `/data/volumes/mail/vmail/secubox.in/`. Any upgrade path that touches the data directory MUST refuse to proceed if it cannot guarantee preservation. |
|
||||
|
||||
## 4. Current state (test board 192.168.1.200, surveyed 2026-05-15)
|
||||
|
||||
| Element | Reality |
|
||||
|---|---|
|
||||
| LXCs on board | `gitea`, `mail`, `matrix`, `mitmproxy`, `nextcloud`, `streamlit` |
|
||||
| `mail` LXC location | `/data/lxc/mail/` (symlinked from `/var/lib/lxc/mail`) |
|
||||
| `mail` LXC state | STOPPED (last touched 2026-05-08) |
|
||||
| `mail` LXC networking | unprivileged, veth `br-lxc`, `10.100.0.10/24`, gw `10.100.0.1` |
|
||||
| `mail` LXC bind mounts | `/data/volumes/mail/vmail` → `var/vmail`, `/data/volumes/mail/config` → `etc/mail-config`, `/data/volumes/mail/ssl` → `etc/ssl/mail` |
|
||||
| Inside-LXC software | Postfix, Dovecot (core+imapd+lmtpd+pop3d), Apache2+mod_php, nginx, OpenDKIM, SpamAssassin, Roundcube (core+plugins+classic+larry skins, mysql backend), php-net-sieve |
|
||||
| **NOT yet inside LXC** | Postgrey, ClamAV (planned by spec rev. 1 — never installed; rev. 2 drops Postgrey entirely and defers ClamAV to Phase 2) |
|
||||
| Persistent data | `/data/volumes/mail/vmail/{secubox.in/{gk2,bat,bourdon,lemurien,ragondin},gk2}`, `/data/volumes/mail/config/{main.cf,master.cf,vmailbox,virtual,vdomains,users,aliases,...}`, `/data/volumes/mail/ssl/{fullchain.pem,privkey.pem}` (Feb 2026 ACME issue) |
|
||||
| Host packages | `secubox-mail 2.1.0-1`, `secubox-mail-lxc 1.1.0-1`, `secubox-webmail 1.0.0-1`, `secubox-webmail-lxc 1.1.0-1` |
|
||||
| Host service | `secubox-mail.service` is `active` (FastAPI listens, but mail LXC isn't running) |
|
||||
| Postfix `main.cf` (in `/data/volumes/mail/config/`) | hostname `mail.secubox.in`, virtual mailbox domains via `/etc/postfix/vdomains`, SASL via Dovecot, TLS via `/etc/ssl/mail/`, Maildir layout |
|
||||
| Roundcube webserver | Apache2 + libapache2-mod-php8.2 (BOTH nginx and apache2 packages installed inside LXC; only one needed) |
|
||||
| Repo source layout (this tree) | Out of date: `mailctl` still references `/srv/lxc`, `/srv/mail`, `mail_container = "mailserver"`, `webmail_container = "roundcube"`, `192.168.255.30`. The single `mail` LXC was hand-built outside the repo. |
|
||||
| Host `mail.toml` | Out of date: still has `mail_container`, `webmail_container`, `mail_ip = "192.168.255.30"`, `webmail_ip = "192.168.255.31"` |
|
||||
|
||||
## 5. Target architecture
|
||||
|
||||
### 5.1 LXC layout (canonical)
|
||||
|
||||
```
|
||||
/var/lib/lxc/mail -> /data/lxc/mail (symlink, host-side)
|
||||
/data/lxc/mail/
|
||||
config # LXC config (unprivileged, veth, br-lxc, 10.100.0.10/24)
|
||||
rootfs/ # Debian bookworm arm64
|
||||
etc/postfix/ # rendered by mailctl (read-only at runtime)
|
||||
etc/dovecot/
|
||||
etc/rspamd/ # Phase 2+
|
||||
etc/clamav/ # Phase 2+
|
||||
etc/apache2/ # Phase 1 keeps Apache; Phase 5 may revisit
|
||||
etc/roundcube/
|
||||
etc/mlmmj/ # Phase 6+
|
||||
opt/start-mail.sh # init script run by lxc.init.cmd
|
||||
|
||||
/data/volumes/mail/ # Persistent data (bind-mounted into LXC)
|
||||
vmail/ # Maildirs
|
||||
secubox.in/<user>/Maildir/
|
||||
<future-domain>/<user>/Maildir/
|
||||
config/ # Postfix/Dovecot lookup tables, owned by host
|
||||
main.cf, master.cf
|
||||
users, vmailbox, virtual, valias, vdomains, aliases
|
||||
*.lmdb (rebuilt by postmap)
|
||||
ssl/ # ACME-issued certs (host renews, container reads)
|
||||
fullchain.pem, privkey.pem
|
||||
dkim/ # per-domain keys (Phase 2 owned by Rspamd)
|
||||
rspamd/ # Phase 2 — bayes corpus, history
|
||||
clamav/ # Phase 2 — virus signature DB
|
||||
sieve/ # Phase 4 — per-user sieve scripts
|
||||
mlmmj/ # Phase 6 — mailing list spools
|
||||
roundcube/ # Phase 5 — user data, logs, plugin state
|
||||
```
|
||||
|
||||
### 5.2 Network and ports
|
||||
|
||||
LXC IP: `10.100.0.10` (br-lxc). Gateway: `10.100.0.1` (host bridge).
|
||||
|
||||
| Listener | Port | Protocol | Exposed how |
|
||||
|---|---|---|---|
|
||||
| Postfix smtpd | 25 | SMTP | HAProxy TCP pass-through, WAN |
|
||||
| Postfix submission | 587 | SMTP+STARTTLS+SASL | HAProxy TCP pass-through, WAN |
|
||||
| Postfix submissions | 465 | SMTPS+SASL | HAProxy TCP pass-through, WAN |
|
||||
| Dovecot imap | 143 | IMAP+STARTTLS | LAN only |
|
||||
| Dovecot imaps | 993 | IMAPS | HAProxy TCP pass-through, WAN |
|
||||
| Dovecot ManageSieve | 4190 | sieve+STARTTLS | HAProxy TCP pass-through, WAN |
|
||||
| Rspamd controller | 11334 | HTTP | Behind host nginx admin auth (Phase 2+) |
|
||||
| Rspamd worker | 11332 | milter | Localhost-in-LXC only (Phase 2+) |
|
||||
| ClamAV milter | 8894 | milter | Localhost-in-LXC only (Phase 2+) |
|
||||
| Roundcube HTTP (Apache or nginx) | 80 / 443 | HTTP | Behind host nginx on `webmail.<domain>` |
|
||||
|
||||
Host nginx publishes:
|
||||
- `https://mail-admin.gk2.secubox.in/` → FastAPI on UNIX socket `/run/secubox/mail.sock`
|
||||
- `https://webmail.gk2.secubox.in/` → `http://10.100.0.10:80/` (Roundcube)
|
||||
- `https://mail.gk2.secubox.in/.well-known/autoconfig/...` → FastAPI autoconfig
|
||||
- `https://rspamd.gk2.secubox.in/` → `http://10.100.0.10:11334/` (Phase 2+, admin-auth gated)
|
||||
|
||||
### 5.3 Daemon inventory (end of Phase 8)
|
||||
|
||||
Inside `mail` LXC:
|
||||
|
||||
| Daemon | Source | Role | Phase added |
|
||||
|---|---|---|---|
|
||||
| Postfix | Debian | MTA | already on board |
|
||||
| Dovecot | Debian | IMAP + LMTP + ManageSieve + SASL auth | already on board |
|
||||
| Apache2 + mod_php | Debian | Roundcube webserver | already on board (Phase 5 may migrate to nginx+php-fpm) |
|
||||
| Roundcube | Debian | Webmail (with classic/larry skins, plugins) | already on board |
|
||||
| Rspamd | Debian | Greylist + spam + DKIM + SPF + DMARC + ARC + ratelimit | Phase 2 |
|
||||
| ClamAV (clamd + clamav-milter) | Debian | Virus scan | Phase 2 |
|
||||
| mlmmj | Debian | Mailing lists | Phase 6 |
|
||||
| acme.sh | upstream | TLS cert renewal | host-side, already wired |
|
||||
| imapsync | upstream | One-shot per migration job | Phase 7 |
|
||||
|
||||
**Daemons removed by Phase 2:** SpamAssassin, OpenDKIM. (Postgrey was planned by rev. 1 but never installed; dropped from scope.)
|
||||
|
||||
### 5.4 Identity / provisioning flow (Phase 3)
|
||||
|
||||
```
|
||||
secubox-users API ──"user.created"──▶ mail provisioning webhook
|
||||
│
|
||||
▼
|
||||
mailctl provision <user@domain>
|
||||
│
|
||||
├──▶ /data/volumes/mail/vmail/<domain>/<user>/Maildir (mkdir + perms)
|
||||
├──▶ append /data/volumes/mail/config/users (Dovecot passwd-file, SHA512-CRYPT)
|
||||
├──▶ append /data/volumes/mail/config/vmailbox (Postfix virtual_mailbox_maps)
|
||||
└──▶ postmap if needed; notify Rspamd
|
||||
```
|
||||
|
||||
Password sync: `secubox-users` POSTs `/internal/password` over UNIX socket on every change. No password ever leaves the host except as the SHA512-CRYPT hash already stored in Dovecot's `users` file.
|
||||
|
||||
### 5.5 DNS records owned by the mail stack
|
||||
|
||||
For each managed domain, `mailctl dns-records <domain>` emits records `secubox-dns` must publish:
|
||||
|
||||
```
|
||||
mail.<domain> A <public IP>
|
||||
<domain> MX 10 mail.<domain>.
|
||||
<domain> TXT "v=spf1 mx -all"
|
||||
default._domainkey.<domain> TXT "v=DKIM1; k=rsa; p=<pubkey>"
|
||||
_dmarc.<domain> TXT "v=DMARC1; p=quarantine; rua=mailto:postmaster@<domain>; ruf=mailto:postmaster@<domain>; adkim=s; aspf=s"
|
||||
_imaps._tcp.<domain> SRV "0 1 993 mail.<domain>."
|
||||
_submission._tcp.<domain> SRV "0 1 587 mail.<domain>."
|
||||
autoconfig.<domain> CNAME mail.<domain>.
|
||||
autodiscover.<domain> CNAME mail.<domain>.
|
||||
```
|
||||
|
||||
Phase 3 wires this to `secubox-dns` via API.
|
||||
|
||||
### 5.6 `mail.toml` schema (target — end of Phase 3)
|
||||
|
||||
```toml
|
||||
[mail]
|
||||
enabled = true
|
||||
hostname = "mail.gk2.secubox.in"
|
||||
container = "mail"
|
||||
lxc_path = "/var/lib/lxc" # symlink to /data/lxc on this board
|
||||
data_path = "/data/volumes/mail"
|
||||
lxc_ip = "10.100.0.10"
|
||||
lxc_bridge = "br-lxc"
|
||||
lxc_gateway = "10.100.0.1"
|
||||
|
||||
[[mail.domain]]
|
||||
name = "secubox.in"
|
||||
primary = true
|
||||
dkim_selector = "default"
|
||||
dmarc_policy = "quarantine"
|
||||
catchall = ""
|
||||
|
||||
[mail.tls]
|
||||
provider = "acme"
|
||||
acme_email = "postmaster@secubox.in"
|
||||
|
||||
[mail.rspamd] # Phase 2+
|
||||
greylist = true
|
||||
bayes_autolearn = true
|
||||
ratelimit_outbound = "100/h/user"
|
||||
|
||||
[mail.identity] # Phase 3+
|
||||
source = "secubox-users"
|
||||
provisioning_url = "http://127.0.0.1:8093/api/v1/users"
|
||||
|
||||
[mail.dav] # Phase 5+
|
||||
provider = "secubox-nextcloud"
|
||||
url = "https://nextcloud.gk2.secubox.in/remote.php/dav/"
|
||||
|
||||
[mail.webmail] # Phase 5+
|
||||
enabled = true
|
||||
url = "https://webmail.gk2.secubox.in/"
|
||||
plugins = ["managesieve", "carddav", "calendar", "enigma", "twofactor"]
|
||||
|
||||
[mail.lists] # Phase 6+
|
||||
enabled = false
|
||||
default_domain = "lists.gk2.secubox.in"
|
||||
```
|
||||
|
||||
## 6. Phase plan (revised)
|
||||
|
||||
Phase 1 is now substantially smaller: most of the architectural bones are already on the board; the repo source just doesn't reflect them yet.
|
||||
|
||||
| # | Phase | Effort | Critical-path? |
|
||||
|---|---|---|---|
|
||||
| **0** | Architecture spec (this doc, rev. 2) | done | — |
|
||||
| **1** | **Reconcile source ↔ board, deprecate legacy packages, lock the data contract** | 2–3 days | yes |
|
||||
| **2** | Rspamd migration (drops SA + OpenDKIM, adds ClamAV) | 1 wk | yes |
|
||||
| **3** | Multi-domain + `secubox-users` provisioning hook | 1.5 wk | yes |
|
||||
| **4** | ManageSieve + quotas + vacation | 1 wk | yes |
|
||||
| **5** | Roundcube polish + Nextcloud DAV bridge + (optional) Apache→nginx+php-fpm | 1 wk | no |
|
||||
| **6** | mlmmj mailing lists + shared mailboxes | 1 wk | no |
|
||||
| **7** | imapsync migration tooling | 1 wk | no |
|
||||
| **8** | Self-service portal + observability + outbound abuse policies | 1.5 wk | no |
|
||||
|
||||
**Total:** ~8 weeks. Phase 5–8 can interleave once Phase 3 is in.
|
||||
|
||||
### Phase 1 — revised goal: "source-catch-up + legacy package cleanup"
|
||||
|
||||
**Deliverables**
|
||||
- Repo source updated to canonical paths/IP: `/var/lib/lxc/mail`, `/data/volumes/mail`, `10.100.0.10`, unprivileged veth br-lxc. (`mailctl`, `mailserverctl`, `roundcubectl`, `api/main.py`.)
|
||||
- `mail.toml` schema: single `container`, `lxc_ip`, `lxc_bridge`, `lxc_gateway`, `data_path`. Drop `mail_container`/`webmail_container`/`mail_ip`/`webmail_ip`/`webmail_port`.
|
||||
- `lib/install.sh` + `lib/lxc.sh` extracted from `mailserverctl` for re-use.
|
||||
- `mailctl migrate-config` rewrites a legacy `mail.toml` in place. Idempotent.
|
||||
- `mail-migrate-to-single-lxc.sh` becomes a defensive **scanner** that detects old `mailserver`/`roundcube` LXC directories (none expected on this board) and old toml keys, and applies safe migration. **Refuses to touch `/data/volumes/mail/` if data is present** (per I13).
|
||||
- Legacy `secubox-mail-lxc`, `secubox-webmail-lxc`, `secubox-webmail` packages → transitional metadata-only `2.2.0` packages that just `Depends: secubox-mail (>= 2.2)`.
|
||||
- `secubox-mail` bumps to `2.2.0` (one minor higher than current `2.1.0`) with `Breaks:`/`Replaces:` against the transitional packages.
|
||||
- Host nginx vhost: `mail-admin.<base>` → FastAPI socket; `webmail.<base>` → `http://10.100.0.10:80/`. Replaces both `packages/secubox-mail/nginx/mail.conf` and `packages/secubox-webmail/nginx/webmail.conf` with one `common/nginx/modules.d/mail.conf`.
|
||||
- HAProxy SMTP/submission/IMAPS/sieve backends targeting `10.100.0.10`.
|
||||
- API `main.py` updated to read new keys; all 62 endpoints respond non-5xx (presence test).
|
||||
- Acceptance: from clean checkout + deploy, `mailctl status` correctly reports the existing `mail` LXC; `mailctl start` brings it up; existing 5 `secubox.in` users can IMAP login; Roundcube responds via host proxy.
|
||||
|
||||
**Explicitly out of Phase 1:**
|
||||
- Installing Postgrey / ClamAV inside the LXC — Phase 2 handles ClamAV; Postgrey is dropped entirely.
|
||||
- Multi-domain refactor — Phase 3.
|
||||
- Apache → nginx+php-fpm migration — Phase 5 if desired.
|
||||
- Roundcube CardDAV/CalDAV plugin wiring — Phase 5.
|
||||
|
||||
## 7. Deprecations and breaking changes
|
||||
|
||||
| Item | Phase | Migration |
|
||||
|---|---|---|
|
||||
| `secubox-mail-lxc` package | 1 | Transitional 2.2.0 stub depending on `secubox-mail (>= 2.2)`. Removed entirely in 3.0. |
|
||||
| `secubox-webmail-lxc` package | 1 | Same |
|
||||
| `secubox-webmail` package | 1 | Same — its API surface folded into `secubox-mail` API. |
|
||||
| `mail_container`, `webmail_container`, `mail_ip`, `webmail_ip`, `webmail_port` in `mail.toml` | 1 | `mailctl migrate-config` rewrites to single `container`/`lxc_ip` + comments the old keys for one release. |
|
||||
| `/srv/lxc/`, `/srv/mail/` paths in source | 1 | Replaced by `/var/lib/lxc/` and `/data/volumes/mail/` everywhere. |
|
||||
| `192.168.255.30/31` IP literals in source | 1 | Replaced by `10.100.0.10` (and `lxc_ip` lookup from toml). |
|
||||
| OpenDKIM (`/dkim/*` API) | 2 | Rspamd DKIM module; old endpoints proxy for one minor version, removed in 3.0. |
|
||||
| SpamAssassin (`/spam/*`) | 2 | Rspamd spam scoring; same pattern. |
|
||||
| Postgrey (`/grey/*`) | 2 | Rspamd greylist module; the `/grey/*` endpoints were stubbed but Postgrey was never installed — endpoints return informative deprecation responses. |
|
||||
| `domain` scalar in `mail.toml` | 3 | Migrated to `[[mail.domain]]` array. |
|
||||
|
||||
## 8. GitHub issue plan
|
||||
|
||||
| # | Title | Label | Phase |
|
||||
|---|---|---|---|
|
||||
| TBD | Mail stack: Phase 1 — source-catch-up + legacy package cleanup | `migration,wip` | 1 |
|
||||
| TBD | Mail stack: Phase 2 — Rspamd migration | `migration,security` | 2 |
|
||||
| TBD | Mail stack: Phase 3 — multi-domain + secubox-users integration | `migration,api` | 3 |
|
||||
| TBD | Mail stack: Phase 4 — ManageSieve + quotas + vacation | `api,frontend` | 4 |
|
||||
| TBD | Mail stack: Phase 5 — Roundcube polish + Nextcloud DAV bridge | `frontend` | 5 |
|
||||
| TBD | Mail stack: Phase 6 — mailing lists + shared mailboxes | `api,frontend` | 6 |
|
||||
| TBD | Mail stack: Phase 7 — imapsync migration tooling | `migration` | 7 |
|
||||
| TBD | Mail stack: Phase 8 — self-service portal + metrics + abuse | `frontend,infra` | 8 |
|
||||
|
||||
Issues filed at start of each phase.
|
||||
|
||||
## 9. Open questions (deferred to per-phase specs)
|
||||
|
||||
- **Roundcube webserver (Phase 5):** Keep Apache+mod_php (current) or migrate to nginx+php-fpm? Decided in Phase 5 spec. Phase 1 does **not** touch this.
|
||||
- **PGP key escrow (Phase 5/8):** read-only after import vs. user-managed in self-service portal?
|
||||
- **HAProxy SMTP cert handling:** TCP pass-through (current direction) vs. terminate at HAProxy with shared cert. Phase 1 stays pass-through; revisit only if cert renewal proves painful.
|
||||
- **Mailing list tool (Phase 6):** mlmmj vs. Mailman 3?
|
||||
|
||||
## 10. ANSSI / CSPN posture
|
||||
|
||||
- **Privilege separation:** every daemon under its own user. LXC unprivileged adds a second layer (root inside LXC = uid 100000 outside).
|
||||
- **Audit logging:** all admin actions (provision, delete, password reset, sieve edit) appended to `/data/volumes/mail/audit.log` and to `secubox-users` audit stream.
|
||||
- **Double-buffer config:** `mailctl` writes Postfix/Dovecot/Rspamd config under `/data/volumes/mail/config/shadow/`, validates with `postfix check` / `doveconf -n`, atomic-swap to `active/`. Keeps R1..R4.
|
||||
- **AppArmor profiles:** one per daemon, shipped by `secubox-mail` debian/, enforced via `postinst`.
|
||||
- **Secrets:** Dovecot SHA512-CRYPT only; DKIM private keys 0600 owned by `_rspamd` (post-Phase-2); ACME private keys 0600 owned by root. Nothing leaves the host.
|
||||
|
||||
---
|
||||
|
||||
**End of Phase 0 spec rev. 2.** Next: revised Phase 1 plan, then user re-confirmation, then execution.
|
||||
Loading…
Reference in New Issue
Block a user