Deploying Company Atlas on MacLustr
Since 2026-09-14 production runs on the OVH server BHS128b (
ssh BHS128b, ubuntu@51.161.112.85, Ubuntu 24.04, 16 threads / 128 GB), outsidemld: code in~/apps/company-atlas(venv.venv= uv Python 3.12,uv pip install -e .), Postgres 17 native (db/rolecompanyatlas/companyatlas, pg_trgm + uuid-ossp, en_US.UTF-8), objects in~/company-atlas-data, PM2 under systemd (pm2-ubuntu) with~/apps/pm2.company-atlas.config.cjsgenerated from the mld manifest by~/apps/.manifests/gen-pm2.py(secrets, 0600). Tunnel route:mlt add www.company-atlas.co BHS128b:8360(BHS64 Caddy → wg1 10.67.0.62); firewallufw allow in on wg1 from 10.67.0.1 to any port 8360 proto tcp. Release on BHS128b:rsync -az --exclude .venv --exclude node_modules --exclude .git --exclude 'apps/web/.next' --exclude data . BHS128b:apps/company-atlas/thenssh BHS128b 'cd apps/company-atlas && ~/.local/bin/uv pip install --python .venv/bin/python -e . && DATABASE_URL=postgresql+asyncpg://companyatlas:companyatlas@127.0.0.1:5432/companyatlas CA_DATA_DIR=$HOME/company-atlas-data .venv/bin/catlas migrate && pnpm install --frozen-lockfile && (cd apps/web && API_URL=http://127.0.0.1:8361 NEXT_PUBLIC_SITE_URL=https://www.company-atlas.co pnpm build) && pm2 restart company-atlas-api company-atlas-scheduler company-atlas-web && pm2 save'. Known:company-atlas-schedulersegfaults now and then insidelexbor(selectolax native parser) during enrichment — PM2 restarts it (PYTHONFAULTHANDLER=1set in the PM2 env to capture the Python stack);/api/v1/indexand/api/v1/pulsereturn 500 (TypeError: float() argument … 'dict'inapi/aggregates.pyby_country) — pre-existing on M2U64 too. The M2U64 copy (code, Postgrescompanyatlas,~/company-atlas-data) is kept as a cold copy, retired frommld(--keep-dir).
Production formerly ran on M2U64 (Mac Studio M2 Ultra, 24 c / 64 GB) behind the MacLustr Tunnel (BHS64 Caddy → WireGuard wg1 10.67.0.12).
Everything goes through the gateway M1M32 and mld (~/Desktop/cluster-skill/mld).
Topology
Internet → DNS (A www.company-atlas.co → 51.161.112.61)
→ BHS64 Caddy (TLS Let's Encrypt) → WireGuard wg1 → M2U64:8360 (Next.js, PM2 company-atlas-web)
└─ rewrite /api/v1/* → 127.0.0.1:8361 (FastAPI, 2 uvicorn workers, PM2 company-atlas-api)
└─ PM2 company-atlas-scheduler (`catlas schedule`: due sensors + discovery queue + periodic tasks)
└─ Postgres 17 Homebrew (db companyatlas) · ~/company-atlas-data (objects zstd, logs, backups)
LLM enrichment: https://www.llm-api.io/v1 (MacLustr llm-api on M1M64, key "company-atlas") — optional, budgetedNode prerequisites (done 2026-09-12): node 25 + pnpm + pm2 (Homebrew), uv + Python 3.12 (~/.local/bin/uv), postgresql@17 running,
role/db companyatlas (password companyatlas, localhost only), extensions pg_trgm + uuid-ossp, ~/company-atlas-data.
No Redis is required.
Domains
| Host | State | Action |
|---|---|---|
www.company-atlas.co |
A → 51.161.112.61 (GoDaddy) — canonical today | route posed by mld deploy |
company-atlas.co |
no A record | add A @ → 51.161.112.61, then put company-atlas.co in tunnel.redirects |
www.company-atlas.com / company-atlas.com |
nameservers at Vercel (ns1.vercel-dns.com), A → Vercel IPs |
point A records (or NS) at the gateway; then add both to tunnel.redirects (308 → www.company-atlas.co) or make .com canonical: CA_SITE_URL, NEXT_PUBLIC_SITE_URL, tunnel.domain |
Caddy only issues certificates for hosts whose authoritative DNS points at the gateway — do not add a route before the DNS is in place.
Release procedure (from the laptop)
cd ~/Desktop/Projets/apps-web/company-atlas
.venv/bin/pytest -q && .venv/bin/ruff check src tests && pnpm -r typecheck # 1. tests + typecheck
deploy/render-manifest.sh --push # 2. manifest with CA_ADMIN_TOKEN + CA_LLM_API_KEY → M1M32:~/dispatch/apps/company-atlas.json
~/Desktop/cluster-skill/mld stage ~/Desktop/Projets/apps-web/company-atlas company-atlas # 3. laptop → gateway staging (sync_excludes)
~/Desktop/cluster-skill/mld deploy company-atlas --node M2U64 # 4. rsync → node, hooks (venv, migrate, seed, pnpm build), PM2, health, tunnel route, registry
ssh M2U64 'cd ~/apps/company-atlas && nohup bash deploy/first-run.sh > ~/company-atlas-data/logs/first-run.log 2>&1 &' # 5. first time: mass onboarding
curl -sI https://www.company-atlas.co | head -1 # 6. public checkmld deploy re-points https://www.company-atlas.co → M2U64:8360 on BHS64 and runs the public health check. mld heal (every 5 min on
M1M32) restarts anything missing — note it also undoes a manual pm2 stop: to pause a process, remove it from the manifest.
Operations
~/Desktop/cluster-skill/mld status | grep company-atlas
~/Desktop/cluster-skill/mld logs company-atlas
ssh M2U64 'cd ~/apps/company-atlas && export PATH=/opt/homebrew/opt/postgresql@17/bin:$PATH CA_LOG_JSON=0 DATABASE_URL=postgresql+asyncpg://companyatlas:companyatlas@127.0.0.1:5432/companyatlas CA_DATA_DIR=$HOME/company-atlas-data; .venv/bin/catlas status' # heartbeat, queue, failures
ssh M2U64 '… .venv/bin/catlas stats' # dataset counters
ssh M2U64 '… .venv/bin/catlas sensors --status failing --limit 50'
ssh M2U64 '… .venv/bin/catlas discover https://example.com --dry-run'
ssh M2U64 '… .venv/bin/catlas backup' # pg_dump → ~/company-atlas-data/backups (nightly 04:35 too)
scripts/backup-offnode.sh # second copy (dumps + objects) → M1M32:~/backups/company-atlas
curl -s https://www.company-atlas.co/api/v1/stats | python3 -m json.tool | headAdmin console: https://www.company-atlas.co/admin (token = deploy/.admin-token). Environment variables: .env.example.
Scaling
- More crawl throughput: raise
CA_FETCH_CONCURRENCY(scheduler env in the manifest) or run a secondcatlas schedule --no-onboardingon another node withDATABASE_URLpointing at M2U64 (Postgres must then listen on the LAN and the object store must be shared or per-node — observations reference object keys, so a per-node store needs a shared path; prefer scaling on the same node first). - LLM enrichment: any OpenAI-compatible server (
CA_LLM_BASE_URL); budgetCA_LLM_DAILY_BUDGET. - Storage: object store growth ≈ changed snapshots × ~15 kB (zstd). 373 GB free on M2U64 at launch.