Skip to content

Z2 ↔ Sandpit Sync & Migration Strategy

Owner: Jimmy Neutron (Captain) + Trunks (architecture)
Date: 2026-06-04
Status: Executable plan — ready for Phase 1 kickoff


Executive Summary

Goal: Move mail-poller + Heim classifier from Mac (local) → Z2 (networked), with vault + data split cleanly.

Architecture:

┌──────────────────────────────────┐
│ iCloud (Obsidian vault)          │
│ hinata-v2 (canonical)         │
└──────────────┬───────────────────┘
               │ (git clone/pull)

┌──────────────────────────────────┐
│ Z2 (Proxmox VE 9.2)              │
│ ├─ hinata-v2/ (vault clone)   │
│ ├─ Container: hinata-poller      │
│ └─ Container: hinata-ml (Heim)   │
└──────────────┬───────────────────┘
               │ (NFS mount)

┌──────────────────────────────────┐
│ Sandpit (~/hinata/resources)     │
│ ├─ email-poller/ (47K emails)    │
│ ├─ data/mail/ (imap state)       │
│ └─ (cold + backup storage)       │
└──────────────────────────────────┘

Key principles:

  1. Vault source: iCloud (Obsidian) → authoritative
  2. Vault on Z2: Working clone (git pull daily)
  3. Data source: Sandpit (NFS mount, read-write for poller, read-only for classifier)
  4. Infrastructure code: hinata-z2 GitHub repo (docker/, scripts/ only — NOT vault, NOT data)

Phase 1 — Prepare (NOW → 1 week)

1.1 Credential Audit + Sandpit Export

  • [ ] Confirm mail_imap_credential.json has ALL 4 accounts:

  • [ ] Export imap-state-*.json from Mac → project folder:

    • z2-sandpit-sync/state-export-2026-06-04.json
    • Validate: last UID + modseq per account match current server state
    • Test: dry-run mail-poller against state → should fetch 0 new emails
  • [ ] Generate archive manifest (size, file count, date range):

    • Total: ~47K emails
    • Per account breakdown
    • Oldest—newest dates
    • Corrupted files detected (if any)

1.2 Z2 GitHub Repo Restructure

Old structure (contaminated): vault copy + Sandpit data duplication + credentials in git

New structure (clean):

hinata-z2/ (GitHub)
├── docker/
│   ├── compose.yml              # LXC service definitions
│   └── Dockerfile.mail-poller   # mail-poller container image
├── scripts/
│   ├── proxmox-install.sh       # BIOS → Proxmox VE 9.2 flow
│   ├── mount-sandpit-nfs.sh     # NFS mount setup
│   ├── mail-poller.py           # (copy from vault, updated for Z2 paths)
│   └── email-classifier.py      # Heim layers 1–4 (stub, Phase 3 build)
├── docs/
│   └── z2-setup-guide.md        # Step-by-step for Michael
├── .gitignore                   # STRICT: no data/, resources/, creds
└── README.md                    # Infrastructure pointer

NEVER in repo:
  - Sandpit data (email archives, embeddings, models)
  - Credentials, tokens, settings
  - Vault copy (federation/, supreme-court/, the-government/)

Action:

  • [ ] Delete old hinata-z2 GitHub repo
  • [ ] Create fresh repo with clean structure (ready locally: /tmp/hinata-z2-clean/)
  • [ ] Push to GitHub

1.3 Sandpit NFS Export Configuration (Mac)

Mail-poller + Heim will read from Sandpit via NFS. Requires:

  • [ ] Confirm Mac has nfsd enabled (check System Settings → Sharing → File Sharing)
  • [ ] Add NFS export to /etc/exports:
    /Users/nnamdi/Sandpit/hinata/resources -alldirs -mapall=nnamdi:staff 192.168.1.0/24
    (allows Z2 subnet to mount)
  • [ ] Restart nfsd: sudo nfsd restart
  • [ ] Test from Z2 (ping from Proxmox): showmount -e [mac-ip]

Phase 2 — Install & Sync (Z2 hardware online → 2 weeks)

2.1 Z2 Hardware Pre-flight

  • [ ] Verify RAM: expected 16GB (factory sticker says 8GB — CRITICAL MISMATCH)
  • [ ] BIOS update + VT-x/VT-d enabled
  • [ ] MAC address noted for static DHCP

2.2 Proxmox VE Install

  • [ ] Boot Z2 from USB (ISO at ~/Sandpit/hinata/installers/proxmox-ve_9.2-1.iso)
  • [ ] Install to NVMe, static IP .50, Tailscale join
  • [ ] Verify: Web UI at https://jimmy-z2.${TAILNET}.ts.net:8006

2.3 Itachi Credentials Sync (special case — plaintext only)

ONE-TIME: Sync ONLY credential .json files from Itachi to Z2

bash
# On Mac: sync ONLY *.json credential files (NOT docs/doctrine)
rsync -av --include='*.json' --exclude='*.html' --exclude='*.md' \
  /Users/nnamdi/hinata-v2/federation/ \
  hinata-poller@hinata-poller.ts.net:/etc/hinata/credentials/
  • [ ] Credential .json files synced to Z2 at /etc/hinata/credentials/ (0600 perms)
  • [ ] TEMPORARY: Plaintext credentials on Z2 until Vaultwarden (840058) live
  • [ ] NOT synced: Doctrine files (no-rotate-credentials.html, itachi-credential-storage.html)
  • [ ] Once Vaultwarden deployed: credentials rotated → Vaultwarden vault, plaintext .json files deleted from Z2
  • [ ] Mail-poller reads from /etc/hinata/credentials/mail_imap_credential.json (and other creds as needed)

2.4 LXC Container Setup

hinata-poller (mail-poller service):

  • [ ] Create Debian 12 LXC
  • [ ] Install Python 3.11, openssh-server, git
  • [ ] Copy mail-poller.py → /opt/hinata/mail-poller.py
  • [ ] Mount Sandpit NFS at /mnt/archive (read-write)
  • [ ] Copy credentials to /etc/hinata/mail_imap_cred.json (0600 perms)
  • [ ] Test: python3 mail-poller.py --account michael.asolo1@gmail.com --dry-run
    • Should fetch 0 new (state matches server)
  • [ ] First live run: archive new emails to /mnt/archive/email-poller/2026-06/
  • [ ] Verify no duplicates

hinata-ml (Heim classifier, scaffolding):

  • [ ] Create Debian 12 LXC
  • [ ] Install Python 3.11 + pip3 (PyTorch, BGE, BERTopic, spaCy, FastAPI)
  • [ ] Mount Sandpit NFS at /mnt/archive (read-only)
  • [ ] Create /data/ml local storage (models, FAISS index)
  • [ ] Start FastAPI mock at 0.0.0.0:8000 → 200 OK (placeholder for Phase 3)

2.5 State Sync from Mac

  • [ ] On Z2 poller: mount /mnt/archive (verify Sandpit visible)
  • [ ] SCP imap-state-*.json from Mac ~/Sandpit/hinata/data/mail/ → Z2 /mnt/archive/data/mail/
  • [ ] Run: mail-poller.py --account michael.asolo1@gmail.com --dry-run
    • Should detect 0 new emails (UIDvalidity + modseq match)
  • [ ] If matched: state sync successful

2.6 Cron Integration (Pi → Z2)

Remove Mac cron:

bash
launchctl unload ~/Library/LaunchAgents/com.hinata.mail-poller.plist

Add Pi cron (calls Z2):

bash
# Pi crontab
0 6,12,20,21 * * * ssh hinata-poller@hinata-poller.ts.net "/opt/hinata/mail-poller.py" >> /var/log/hinata-poller.log 2>&1

Verify:

  • [ ] Cron executes successfully
  • [ ] New emails appear in /mnt/archive/email-poller/2026-06/
  • [ ] 7-day smoke test: no missed emails, no duplicates

2.7 GitHub Repo Alignment

  • [ ] Z2 repo (GitHub) reflects the clean infrastructure structure
  • [ ] docker-compose.yml in repo matches running containers
  • [ ] All scripts are version-controlled (but config/paths are environment-specific)
  • [ ] .gitignore is airtight (no data leaks)

Phase 3 — Z2 HDD Installation + Data Replication

When: After Phase 2 smoke test passes (Z2 poller stable, Heim scaffolding live)

Deliverables:

  • [ ] Order + install HDD on Z2 (recommend: 4TB SSD for durability, or 8TB HDD for capacity)
  • [ ] Format + mount at /mnt/data (primary storage, not Sandpit NFS)
  • [ ] Replicate Sandpit data to Z2 HDD:
    bash
    rsync -av --delete /mnt/archive/ /mnt/data/
    # email-poller/, data/mail/, resources/ → Z2 HDD
    # One-time sync; ~30 minutes (4.2GB)
  • [ ] Verify: File count + checksums match between Sandpit + Z2 HDD
  • [ ] Update containers to read from /mnt/data (Z2 HDD) instead of /mnt/archive (Sandpit NFS)
  • [ ] Test 7 days: poller + classifier running against Z2 HDD (not NFS)
  • [ ] Delete vault clone from Z2:
    bash
    rm -rf /home/hinata/vault
    # Vault lives on iCloud/GitHub; Z2 no longer needs a local copy
  • [ ] Update Z2 cron: daily git pull removed (vault no longer synced locally)

New architecture (post-HDD):

iCloud (vault source)
  ↓ (no longer synced to Z2)

Z2 (containers only, no vault clone)
  ├─ /home/hinata/z2-infra/ (docker/, scripts/, config/)
  └─ /mnt/data/ (email-poller/, data/mail/, models, embeddings)

Sandpit (backup-only)
  └─ Mirrors /mnt/data/ (rsync backup, not primary)

Success criteria:

  • [ ] Z2 HDD populated + verified (file count, checksums)
  • [ ] Containers reading from Z2 HDD (not NFS)
  • [ ] 7-day stability test passed
  • [ ] Vault clone deleted from Z2
  • [ ] Sandpit demoted to backup-only status

Phase 4 — Heim Classifier (Z2 ML pipeline → 2 weeks)

3.1 Layer 1 — Structured Classifier

  • [ ] Build email-classifier.py Layer 1 (deterministic rules)
  • [ ] Test on existing corpus: >90% coverage
  • [ ] Output: JSON with

3.2 Layer 2 — BERTopic + Unstructured

  • [ ] Fit BERTopic on full 47K email corpus (one-time, overnight)
  • [ ] Topic → commander domain mapping (Meruem reviews)
  • [ ] Train LogReg classifier (embedding + topic_id → category)
  • [ ] Test on held-out set: >80% accuracy

3.3 Layer 3 — Recommendation Engine

  • [ ] Similarity scorer: email embedding similarity to acted-on emails
  • [ ] Persist as FAISS index + pickle on Z2 /data/ml/
  • [ ] Priority scoring: 1–5 per email

3.4 Layer 4 — FastAPI Endpoints (live)

  • [ ] GET /api/email/feed — latest classified emails
  • [ ] GET /api/email/leads — career + side-hustle inbound signals
  • [ ] GET /api/email/missed-opportunities — cold leads >14d
  • [ ] POST /api/email/{message_id}/action — log feedback

3.5 Backfill (optional)

  • [ ] Process all 47K existing emails through Layers 1–4
  • [ ] Store classifications to /mnt/archive/resources/ml/classifications/

Risk Register

RiskMitigation
NFS mount latency slows classificationMonitor via /proc/self/mountstats; alert if >100ms
Sandpit offline → Z2 poller stallsImplement circuit breaker; fall back to local queue
State cursor desync (Mac vs Z2)Dry-run all fetches before cutover; validate UIDs against server
Credential plaintext on Z2 until Vaultwarden (CT103, 192.168.1.250) ready0600 file perms, isolated LXC, Tailscale-only SSH
Z2 hardware RAM is 8GB (not 16GB)Reduce container limits, defer GPU, or upgrade RAM kit (~£35)

Success Criteria

Phase 1 complete:

  • [ ] All 4 IMAP credentials located + validated
  • [ ] State export matches server state
  • [ ] Archive manifest generated
  • [ ] Z2 GitHub repo clean + pushable

Phase 2 complete:

  • [ ] Mail-poller runs on Z2, fetches correctly via NFS
  • [ ] 7-day smoke test: no errors, no missing emails
  • [ ] Pi cron → Z2 working
  • [ ] Mac mail-poller disabled
  • [ ] Heim scaffolding live (FastAPI 200 OK)

Phase 3 complete (HDD + migration):

  • [ ] Z2 HDD installed + formatted at /mnt/data
  • [ ] Sandpit data replicated to Z2 HDD (verified)
  • [ ] Containers switched to read from /mnt/data (not NFS)
  • [ ] 7-day stability test on Z2 HDD passed
  • [ ] Vault clone deleted from Z2: rm -rf /home/hinata/vault
  • [ ] Sandpit demoted to backup-only (rsync pull from Z2 weekly)

Phase 4 complete:

  • [ ] Heim classifier live (Layers 1–4)
  • [ ] FastAPI endpoints tested
  • [ ] Optional: 47K emails backfilled + classified

Open Questions for Michael

  1. Outlook credentials: Where are the 3 Outlook accounts stored? (currently only Gmail in mail_imap_credential.json)
  2. RAM contingency: Z2 shows 8GB on sticker, listing claimed 16GB. Proceed with 8GB or upgrade?
  3. Backfill: Classify all 47K existing emails in Phase 3, or defer?
  4. Mail.Send: Investigate send capability (Phase 5) in parallel with Phase 1–3, or defer?