feat: add Twenty CRM /crm route + deploy stack for commons-hub lead funnel
Adds dedicated /crm sub-route to rNetwork module embedding Twenty CRM via ExternalAppShell iframe. Updates TWENTY_API_URL to use internal Docker networking (http://twenty-server:3000). Includes full Twenty CRM Docker stack (server, worker, postgres, redis) with Traefik routing for crm.rspace.online and deployment instructions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
80e42596b3
commit
07e53d6aa1
|
|
@ -0,0 +1,10 @@
|
|||
# Twenty CRM secrets
|
||||
# Generate these before first deploy:
|
||||
# APP_SECRET: openssl rand -hex 32
|
||||
# POSTGRES_PASSWORD: openssl rand -hex 16
|
||||
|
||||
POSTGRES_PASSWORD=changeme
|
||||
APP_SECRET=changeme
|
||||
|
||||
# Store these in Infisical (twenty-crm project) for production.
|
||||
# The .env file is only used for initial bootstrap / local dev.
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
# Twenty CRM Deployment — commons-hub Lead Funnel
|
||||
|
||||
## 1. Deploy Twenty CRM Stack on Netcup
|
||||
|
||||
```bash
|
||||
# SSH to server
|
||||
ssh netcup-full
|
||||
|
||||
# Create directory and copy files
|
||||
mkdir -p /opt/twenty-crm
|
||||
# (copy docker-compose.yml and .env from this directory)
|
||||
|
||||
# Generate secrets
|
||||
cd /opt/twenty-crm
|
||||
cat > .env <<EOF
|
||||
POSTGRES_PASSWORD=$(openssl rand -hex 16)
|
||||
APP_SECRET=$(openssl rand -hex 32)
|
||||
EOF
|
||||
|
||||
# Start the stack
|
||||
docker compose up -d
|
||||
|
||||
# Wait for healthy status (may take 1-2 minutes on first boot)
|
||||
docker compose ps
|
||||
docker logs twenty-server --tail 50
|
||||
```
|
||||
|
||||
## 2. Create Admin Account
|
||||
|
||||
Once Twenty is healthy at `https://crm.rspace.online`:
|
||||
|
||||
1. Open `https://crm.rspace.online` in browser
|
||||
2. Twenty will show the initial setup wizard
|
||||
3. Create admin account: **jeff / jeffemmett@gmail.com**
|
||||
4. Set workspace name: **commons-hub**
|
||||
|
||||
## 3. Generate API Token & Store in Infisical
|
||||
|
||||
1. In Twenty: Settings → Accounts → API Keys → Create API key
|
||||
2. Copy the token
|
||||
3. Store in Infisical:
|
||||
- Project: `rspace` (same project as rSpace secrets)
|
||||
- Secret name: `TWENTY_API_TOKEN`
|
||||
- Secret value: the API token from step 2
|
||||
4. Restart rSpace to pick up the new token:
|
||||
```bash
|
||||
cd /opt/rspace-online && docker compose restart rspace
|
||||
```
|
||||
|
||||
## 4. Configure Lead Funnel Pipeline
|
||||
|
||||
In Twenty CRM UI: Settings → Data model → Opportunity → Edit stages
|
||||
|
||||
### Pipeline Stages (7)
|
||||
|
||||
| # | Stage | Color |
|
||||
|---|-------|-------|
|
||||
| 1 | New Lead | Blue |
|
||||
| 2 | Contacted | Yellow |
|
||||
| 3 | Qualified | Orange |
|
||||
| 4 | Offer Sent | Purple |
|
||||
| 5 | Confirmed | Teal |
|
||||
| 6 | Won | Green |
|
||||
| 7 | Lost / Not Now | Red |
|
||||
|
||||
### Custom Fields
|
||||
|
||||
Add via Settings → Data model → [Object] → Add field:
|
||||
|
||||
**On Opportunity:**
|
||||
- Event Dates (preferred) — DATE
|
||||
- Event Dates (flexible range) — TEXT
|
||||
- Group Size — NUMBER
|
||||
- Needs: Accommodation — BOOLEAN
|
||||
- Needs: Catering — BOOLEAN
|
||||
- Needs: Rooms — BOOLEAN
|
||||
- Needs: AV — BOOLEAN
|
||||
- Next Action Date — DATE (required)
|
||||
- Follow-up Date — DATE
|
||||
- Lost Reason — TEXT
|
||||
|
||||
**On Company:**
|
||||
- Lead Source — SELECT (options: Website, Referral, Event, Cold Outreach, Partner, Other)
|
||||
- Last Touch Date — DATE
|
||||
|
||||
### Saved Views
|
||||
|
||||
Create via the Opportunities list → Save view:
|
||||
|
||||
1. **My Pipeline** — Group by: Stage, Filter: Assigned to = current user
|
||||
2. **Needs Follow-up** — Filter: Next Action Date <= today
|
||||
3. **Stale Leads** — Filter: Next Action Date is empty
|
||||
|
||||
## 5. Create commons-hub Space
|
||||
|
||||
This will be done via the rSpace community store or directly:
|
||||
|
||||
- Space slug: `commons-hub`
|
||||
- Visibility: `permissioned`
|
||||
- Owner: jeff (jeffemmett@gmail.com)
|
||||
- Enabled modules: `["rnetwork"]`
|
||||
|
||||
The CRM will then be accessible at: `commons-hub.rspace.online/rnetwork/crm`
|
||||
|
||||
## 6. Deploy rSpace Changes
|
||||
|
||||
```bash
|
||||
ssh netcup-full
|
||||
cd /opt/rspace-online
|
||||
git pull
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
- [ ] `docker compose ps` on Netcup — all Twenty containers healthy
|
||||
- [ ] `curl https://crm.rspace.online` — Twenty CRM loads
|
||||
- [ ] Navigate to `commons-hub.rspace.online/rnetwork/crm` — CRM embedded in rSpace shell
|
||||
- [ ] Log in as jeff — admin access confirmed
|
||||
- [ ] Open pipeline view — 7 stages visible
|
||||
- [ ] Create test opportunity — all custom fields present
|
||||
- [ ] Verify "Next Action Date" is required
|
||||
- [ ] Check rNetwork graph view still works (`/rnetwork` default view)
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
# Twenty CRM stack for commons-hub lead funnel
|
||||
# Deploy to /opt/twenty-crm/ on Netcup
|
||||
#
|
||||
# Prerequisites:
|
||||
# - rspace-online stack running (creates rspace-online_rspace-internal network)
|
||||
# - Traefik running on traefik-public network
|
||||
# - .env with INFISICAL_CLIENT_ID + INFISICAL_CLIENT_SECRET
|
||||
#
|
||||
# Secrets fetched from Infisical (twenty-crm project):
|
||||
# POSTGRES_PASSWORD, APP_SECRET, ADMIN_PASSWORD
|
||||
|
||||
services:
|
||||
twenty-server:
|
||||
image: twentycrm/twenty:latest
|
||||
container_name: twenty-server
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
twenty-db:
|
||||
condition: service_healthy
|
||||
twenty-redis:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
# ── Core ──
|
||||
- NODE_ENV=production
|
||||
- SERVER_URL=https://crm.rspace.online
|
||||
- FRONT_BASE_URL=https://crm.rspace.online
|
||||
- PORT=3000
|
||||
# ── Database ──
|
||||
- PG_DATABASE_URL=postgres://twenty:${POSTGRES_PASSWORD}@twenty-db:5432/twenty
|
||||
# ── Redis ──
|
||||
- REDIS_URL=redis://twenty-redis:6379
|
||||
# ── Auth ──
|
||||
- APP_SECRET=${APP_SECRET}
|
||||
- ACCESS_TOKEN_SECRET=${APP_SECRET}
|
||||
- LOGIN_TOKEN_SECRET=${APP_SECRET}
|
||||
- REFRESH_TOKEN_SECRET=${APP_SECRET}
|
||||
- FILE_TOKEN_SECRET=${APP_SECRET}
|
||||
# ── Storage ──
|
||||
- STORAGE_TYPE=local
|
||||
- STORAGE_LOCAL_PATH=.local-storage
|
||||
# ── Misc ──
|
||||
- SIGN_IN_PREFILLED=false
|
||||
- IS_BILLING_ENABLED=false
|
||||
- TELEMETRY_ENABLED=false
|
||||
volumes:
|
||||
- twenty-server-data:/app/.local-storage
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.twenty-crm.rule=Host(`crm.rspace.online`)"
|
||||
- "traefik.http.routers.twenty-crm.entrypoints=web"
|
||||
- "traefik.http.routers.twenty-crm.priority=130"
|
||||
- "traefik.http.services.twenty-crm.loadbalancer.server.port=3000"
|
||||
- "traefik.docker.network=traefik-public"
|
||||
networks:
|
||||
- traefik-public
|
||||
- rspace-internal
|
||||
- twenty-internal
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:3000/healthz"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
start_period: 60s
|
||||
|
||||
twenty-worker:
|
||||
image: twentycrm/twenty:latest
|
||||
container_name: twenty-worker
|
||||
restart: unless-stopped
|
||||
command: ["yarn", "worker:prod"]
|
||||
depends_on:
|
||||
twenty-db:
|
||||
condition: service_healthy
|
||||
twenty-redis:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- PG_DATABASE_URL=postgres://twenty:${POSTGRES_PASSWORD}@twenty-db:5432/twenty
|
||||
- REDIS_URL=redis://twenty-redis:6379
|
||||
- APP_SECRET=${APP_SECRET}
|
||||
- ACCESS_TOKEN_SECRET=${APP_SECRET}
|
||||
- LOGIN_TOKEN_SECRET=${APP_SECRET}
|
||||
- REFRESH_TOKEN_SECRET=${APP_SECRET}
|
||||
- FILE_TOKEN_SECRET=${APP_SECRET}
|
||||
- STORAGE_TYPE=local
|
||||
- STORAGE_LOCAL_PATH=.local-storage
|
||||
- SERVER_URL=https://crm.rspace.online
|
||||
- TELEMETRY_ENABLED=false
|
||||
volumes:
|
||||
- twenty-server-data:/app/.local-storage
|
||||
networks:
|
||||
- twenty-internal
|
||||
|
||||
twenty-db:
|
||||
image: postgres:16-alpine
|
||||
container_name: twenty-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_DB=twenty
|
||||
- POSTGRES_USER=twenty
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||
volumes:
|
||||
- twenty-pgdata:/var/lib/postgresql/data
|
||||
networks:
|
||||
- twenty-internal
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U twenty -d twenty"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
|
||||
twenty-redis:
|
||||
image: redis:7-alpine
|
||||
container_name: twenty-redis
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- twenty-redis-data:/data
|
||||
networks:
|
||||
- twenty-internal
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
twenty-server-data:
|
||||
twenty-pgdata:
|
||||
twenty-redis-data:
|
||||
|
||||
networks:
|
||||
traefik-public:
|
||||
external: true
|
||||
rspace-internal:
|
||||
name: rspace-online_rspace-internal
|
||||
external: true
|
||||
twenty-internal:
|
||||
|
|
@ -38,7 +38,7 @@ services:
|
|||
- IMAP_HOST=mail.rmail.online
|
||||
- IMAP_PORT=993
|
||||
- IMAP_TLS_REJECT_UNAUTHORIZED=false
|
||||
- TWENTY_API_URL=https://rnetwork.online
|
||||
- TWENTY_API_URL=http://twenty-server:3000
|
||||
- OLLAMA_URL=http://ollama:11434
|
||||
- INFISICAL_AI_CLIENT_ID=${INFISICAL_AI_CLIENT_ID}
|
||||
- INFISICAL_AI_CLIENT_SECRET=${INFISICAL_AI_CLIENT_SECRET}
|
||||
|
|
|
|||
|
|
@ -214,6 +214,20 @@ routes.get("/api/workspaces", (c) => {
|
|||
]);
|
||||
});
|
||||
|
||||
// ── CRM sub-route — dedicated iframe to Twenty CRM ──
|
||||
routes.get("/crm", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
return c.html(renderExternalAppShell({
|
||||
title: `${space} — CRM | rSpace`,
|
||||
moduleId: "rnetwork",
|
||||
spaceSlug: space,
|
||||
modules: getModuleInfoList(),
|
||||
appUrl: "https://crm.rspace.online",
|
||||
appName: "Twenty CRM",
|
||||
theme: "dark",
|
||||
}));
|
||||
});
|
||||
|
||||
// ── Page route ──
|
||||
routes.get("/", (c) => {
|
||||
const space = c.req.param("space") || "demo";
|
||||
|
|
|
|||
Loading…
Reference in New Issue