Update .env.example with detailed setup instructions, modify DATABASE_URL for Docker, and add new environment variables. Enhance package.json with Docker commands for easier deployment. Expand self-host hardening documentation to include Docker deployment on Ubuntu/VPS.
This commit is contained in:
parent
807722dc3e
commit
cc355b32d5
9 changed files with 369 additions and 8 deletions
15
.dockerignore
Normal file
15
.dockerignore
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
node_modules
|
||||
**/node_modules
|
||||
**/dist
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
.git
|
||||
.cursor
|
||||
*.db
|
||||
*.db-journal
|
||||
apps/api/src/generated
|
||||
coverage
|
||||
docs
|
||||
**/*.md
|
||||
!README.md
|
||||
29
.env.example
29
.env.example
|
|
@ -1,11 +1,24 @@
|
|||
DATABASE_URL="file:./prisma/dev.db"
|
||||
ENCRYPTION_KEY="<64-char-hex>"
|
||||
# Copy to .env on your server: cp .env.example .env
|
||||
# Generate secrets:
|
||||
# openssl rand -hex 32 # ENCRYPTION_KEY (64 hex chars — NEVER change after first deploy)
|
||||
# openssl rand -hex 24 # API_KEY
|
||||
|
||||
# Free AI via OpenRouter — sign up at https://openrouter.ai/keys
|
||||
AI_API_KEY="sk-or-v1-..."
|
||||
# Persistent SQLite (Docker Compose sets this automatically)
|
||||
DATABASE_URL="file:/data/warmbox.db"
|
||||
|
||||
# Required — encrypts mailbox passwords in the database
|
||||
ENCRYPTION_KEY=""
|
||||
|
||||
# Required when exposed to the internet — dashboard login uses this
|
||||
API_KEY=""
|
||||
|
||||
# Public URL users and OAuth providers see (no trailing slash)
|
||||
PUBLIC_BASE_URL="https://warmbox.yourdomain.com"
|
||||
|
||||
PORT=3000
|
||||
NODE_ENV=production
|
||||
|
||||
# AI content generation (OpenRouter recommended)
|
||||
AI_API_KEY=""
|
||||
AI_BASE_URL="https://openrouter.ai/api/v1"
|
||||
AI_MODEL="openrouter/free"
|
||||
|
||||
API_KEY=""
|
||||
PORT=3000
|
||||
NODE_ENV=development
|
||||
|
|
|
|||
48
Dockerfile
Normal file
48
Dockerfile
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# Warmbox — single-container production image (API + dashboard + cron jobs)
|
||||
FROM node:22-bookworm-slim AS base
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends openssl python3 make g++ \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
FROM base AS deps
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
COPY apps/api/package.json apps/api/
|
||||
COPY apps/web/package.json apps/web/
|
||||
COPY packages/shared/package.json packages/shared/
|
||||
RUN npm ci
|
||||
|
||||
FROM deps AS build
|
||||
COPY . .
|
||||
ENV DATABASE_URL=file:/tmp/build.db
|
||||
RUN npx prisma generate
|
||||
RUN npm run build
|
||||
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN useradd --create-home --uid 10001 warmbox
|
||||
|
||||
COPY --from=build /app/package.json /app/package-lock.json ./
|
||||
COPY --from=build /app/node_modules ./node_modules
|
||||
COPY --from=build /app/apps/api/dist ./apps/api/dist
|
||||
COPY --from=build /app/apps/api/package.json ./apps/api/package.json
|
||||
COPY --from=build /app/apps/web/dist ./apps/web/dist
|
||||
COPY --from=build /app/packages/shared/dist ./packages/shared/dist
|
||||
COPY --from=build /app/packages/shared/package.json ./packages/shared/package.json
|
||||
COPY --from=build /app/prisma ./prisma
|
||||
COPY --from=build /app/prisma.config.ts ./prisma.config.ts
|
||||
COPY scripts/docker-entrypoint.sh /entrypoint.sh
|
||||
|
||||
RUN chmod +x /entrypoint.sh \
|
||||
&& mkdir -p /data \
|
||||
&& chown -R warmbox:warmbox /app /data
|
||||
|
||||
USER warmbox
|
||||
EXPOSE 3000
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
|
||||
CMD node -e "fetch('http://127.0.0.1:3000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
7
deploy/Caddyfile
Normal file
7
deploy/Caddyfile
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Replace warmbox.yourdomain.com with your hostname.
|
||||
# Caddy obtains Let's Encrypt certificates automatically.
|
||||
|
||||
warmbox.yourdomain.com {
|
||||
encode gzip
|
||||
reverse_proxy warmbox:3000
|
||||
}
|
||||
58
docker-compose.yml
Normal file
58
docker-compose.yml
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
services:
|
||||
warmbox:
|
||||
build: .
|
||||
image: warmbox:local
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
environment:
|
||||
DATABASE_URL: file:/data/warmbox.db
|
||||
NODE_ENV: production
|
||||
volumes:
|
||||
- warmbox-data:/data
|
||||
ports:
|
||||
# Bind to localhost only when using the Caddy profile (recommended).
|
||||
# For LAN testing without Caddy, change to "3000:3000".
|
||||
- "127.0.0.1:3000:3000"
|
||||
healthcheck:
|
||||
test: ['CMD', 'node', '-e', "fetch('http://127.0.0.1:3000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
# Optional HTTPS reverse proxy — requires a public DNS name pointing at this host.
|
||||
# Start with: docker compose --profile caddy up -d
|
||||
caddy:
|
||||
profiles: [caddy]
|
||||
image: caddy:2-alpine
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
warmbox:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- '80:80'
|
||||
- '443:443'
|
||||
volumes:
|
||||
- ./deploy/Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- caddy-data:/data
|
||||
- caddy-config:/config
|
||||
|
||||
# Optional Cloudflare Tunnel — HTTPS without opening router ports (great for home servers).
|
||||
# 1. Create a tunnel in Cloudflare Zero Trust → copy token
|
||||
# 2. Set CLOUDFLARE_TUNNEL_TOKEN in .env
|
||||
# 3. docker compose --profile cloudflare up -d
|
||||
cloudflared:
|
||||
profiles: [cloudflare]
|
||||
image: cloudflare/cloudflared:latest
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
warmbox:
|
||||
condition: service_healthy
|
||||
command: tunnel run
|
||||
environment:
|
||||
TUNNEL_TOKEN: ${CLOUDFLARE_TUNNEL_TOKEN}
|
||||
|
||||
volumes:
|
||||
warmbox-data:
|
||||
caddy-data:
|
||||
caddy-config:
|
||||
202
docs/deploy-ubuntu-docker.md
Normal file
202
docs/deploy-ubuntu-docker.md
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
# Deploy Warmbox on Ubuntu (Docker)
|
||||
|
||||
Warmbox is a **long-running worker** (cron dispatcher, rescuer, SMTP/IMAP). It needs:
|
||||
|
||||
- A persistent database (SQLite volume)
|
||||
- Outbound SMTP/IMAP from the server
|
||||
- A public HTTPS URL for the dashboard and OAuth callbacks
|
||||
|
||||
**Recommended for your case:** Ubuntu home server + Docker + **Cloudflare Tunnel** (free HTTPS, no router port forwarding).
|
||||
|
||||
**Not suitable:** Netlify, GitHub Pages, Vercel static — no background jobs, no SQLite, no mail sockets.
|
||||
|
||||
---
|
||||
|
||||
## Quick comparison
|
||||
|
||||
| Option | Best for | Pros | Cons |
|
||||
|--------|----------|------|------|
|
||||
| **Ubuntu + Docker + Cloudflare Tunnel** | Home server (your preference) | Free, persistent, full control | You maintain the box |
|
||||
| Ubuntu + Docker + Caddy | Home server with public IP + domain | Simple TLS | Open ports 80/443 on router |
|
||||
| Hetzner / DigitalOcean VPS + same Docker | No home server | Always on, static IP | ~$5–6/mo |
|
||||
| Railway / Fly.io | Managed container | Easy deploy | Cost, less control for 30-day test |
|
||||
|
||||
---
|
||||
|
||||
## 1. Prepare the Ubuntu server
|
||||
|
||||
```bash
|
||||
# SSH into your home server
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
sudo apt install -y git docker.io docker-compose-v2
|
||||
sudo usermod -aG docker $USER
|
||||
# log out and back in so docker group applies
|
||||
```
|
||||
|
||||
Clone the repo:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/YOUR_ORG/warmbox.git
|
||||
cd warmbox
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Configure environment
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
nano .env
|
||||
```
|
||||
|
||||
Set these **before first start**:
|
||||
|
||||
```bash
|
||||
# Generate once — keep forever (changing it breaks encrypted mailbox passwords)
|
||||
openssl rand -hex 32 # → ENCRYPTION_KEY
|
||||
|
||||
# Dashboard login password
|
||||
openssl rand -hex 24 # → API_KEY
|
||||
|
||||
PUBLIC_BASE_URL=https://warmbox.yourdomain.com # your real public URL
|
||||
AI_API_KEY=sk-or-v1-... # OpenRouter or compatible
|
||||
```
|
||||
|
||||
**Migrating from local:** copy your existing `prisma/dev.db` to the server **and** use the **same** `ENCRYPTION_KEY` from your local `.env`. Otherwise mailbox credentials cannot be decrypted.
|
||||
|
||||
---
|
||||
|
||||
## 3. Choose how the world reaches your server
|
||||
|
||||
### Option A — Cloudflare Tunnel (recommended for home)
|
||||
|
||||
No port forwarding. Works behind CGNAT.
|
||||
|
||||
1. [Cloudflare Zero Trust](https://one.dash.cloudflare.com/) → **Networks** → **Tunnels** → Create tunnel
|
||||
2. Name it `warmbox`, install connector via **Docker** — copy the token
|
||||
3. In the tunnel **Public Hostname** tab, route `warmbox.yourdomain.com` → `http://warmbox:3000` (Docker network)
|
||||
If cloudflared runs in the same compose file, use hostname `warmbox` and port `3000`.
|
||||
4. Add to `.env`:
|
||||
|
||||
```bash
|
||||
CLOUDFLARE_TUNNEL_TOKEN=eyJh...
|
||||
PUBLIC_BASE_URL=https://warmbox.yourdomain.com
|
||||
```
|
||||
|
||||
5. Start:
|
||||
|
||||
```bash
|
||||
docker compose --profile cloudflare up -d --build
|
||||
```
|
||||
|
||||
### Option B — Caddy on a public IP
|
||||
|
||||
1. Point DNS `warmbox.yourdomain.com` → your home public IP
|
||||
2. Forward router ports **80** and **443** to the Ubuntu server
|
||||
3. Edit `deploy/Caddyfile` with your domain
|
||||
4. Start:
|
||||
|
||||
```bash
|
||||
docker compose --profile caddy up -d --build
|
||||
```
|
||||
|
||||
### Option C — LAN / direct port (testing only)
|
||||
|
||||
```bash
|
||||
# In docker-compose.yml change ports to "3000:3000"
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Use `http://SERVER_LAN_IP:3000` — not suitable for OAuth or remote access.
|
||||
|
||||
---
|
||||
|
||||
## 4. First login
|
||||
|
||||
1. Open `https://warmbox.yourdomain.com`
|
||||
2. Login with your `API_KEY`
|
||||
3. Leave **Base URL** blank (dashboard and API are same origin in production)
|
||||
|
||||
---
|
||||
|
||||
## 5. Update OAuth redirect URIs
|
||||
|
||||
Replace `localhost` with your public URL in:
|
||||
|
||||
| Integration | Redirect URI |
|
||||
|-------------|----------------|
|
||||
| Google Postmaster | `https://warmbox.yourdomain.com/api/integrations/google/callback` |
|
||||
| Microsoft Outlook | `https://warmbox.yourdomain.com/api/integrations/microsoft/callback` |
|
||||
|
||||
Re-connect integrations in **Settings** after deploy.
|
||||
|
||||
---
|
||||
|
||||
## 6. Migrate local data (optional)
|
||||
|
||||
If you already configured mailboxes and campaigns locally:
|
||||
|
||||
```bash
|
||||
# On your Mac — stop local dev server first
|
||||
scp prisma/dev.db user@home-server:/tmp/warmbox.db
|
||||
|
||||
# On Ubuntu — before first start OR with stack stopped:
|
||||
docker compose down
|
||||
docker volume create warmbox_warmbox-data 2>/dev/null || true
|
||||
docker run --rm -v warmbox_warmbox-data:/data -v /tmp:/backup alpine \
|
||||
cp /backup/warmbox.db /data/warmbox.db
|
||||
docker compose --profile cloudflare up -d
|
||||
```
|
||||
|
||||
Copy the **same** `ENCRYPTION_KEY` and `API_KEY` (or set a new API_KEY and use that to log in).
|
||||
|
||||
---
|
||||
|
||||
## 7. Operations
|
||||
|
||||
```bash
|
||||
# Logs
|
||||
docker compose logs -f warmbox
|
||||
|
||||
# Restart after code pull
|
||||
git pull
|
||||
docker compose --profile cloudflare up -d --build
|
||||
|
||||
# Backup database (run daily via cron)
|
||||
docker compose exec warmbox sh -c 'cp /data/warmbox.db /data/warmbox-$(date +%F).db'
|
||||
|
||||
# Health check
|
||||
curl -s https://warmbox.yourdomain.com/health
|
||||
```
|
||||
|
||||
### 30-day warmup checklist
|
||||
|
||||
- [ ] Container `restart: unless-stopped` (default in compose)
|
||||
- [ ] `warmbox-data` volume backed up
|
||||
- [ ] `ENCRYPTION_KEY` saved in your password manager
|
||||
- [ ] Campaign started and status **active**
|
||||
- [ ] Settings → Jobs shows dispatcher/rescuer running (or check logs every few hours day 1)
|
||||
- [ ] OAuth redirect URIs updated if using Postmaster / Microsoft
|
||||
|
||||
---
|
||||
|
||||
## 8. Security notes (single-tenant MVP)
|
||||
|
||||
- **Always set `API_KEY`** when exposed to the internet.
|
||||
- Do not commit `.env` to git.
|
||||
- Restrict dashboard access further with Cloudflare Access (optional) or VPN if desired.
|
||||
- Mailbox app passwords live encrypted in SQLite — protect the volume and `ENCRYPTION_KEY`.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Fix |
|
||||
|---------|-----|
|
||||
| Login fails | Check `API_KEY` in `.env` matches what you enter; restart container after `.env` change |
|
||||
| OAuth redirect error | `PUBLIC_BASE_URL` must match browser URL; update provider redirect URI |
|
||||
| Jobs not sending | `docker compose logs warmbox` — verify cron lines; confirm SMTP/IMAP from server IP isn't blocked |
|
||||
| `ENCRYPTION_KEY` error on decrypt | Wrong key vs database — restore original key or re-add mailboxes |
|
||||
| Container unhealthy | `docker compose logs warmbox` — migration or port conflict |
|
||||
|
||||
See also [self-host-hardening.md](./self-host-hardening.md) for operator workflows.
|
||||
|
|
@ -21,6 +21,10 @@ npm run dev # API :3000, dashboard :5173
|
|||
npm run build && npm run start
|
||||
```
|
||||
|
||||
### Run on Ubuntu / VPS (Docker)
|
||||
|
||||
For a **30-day live test** without keeping your laptop on, see **[deploy-ubuntu-docker.md](./deploy-ubuntu-docker.md)** (Docker Compose + Cloudflare Tunnel or Caddy).
|
||||
|
||||
## Operator workflows (no curl required)
|
||||
|
||||
1. **Mailboxes** — add, edit credentials, test connection, DNS check
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
"dev": "concurrently -n api,web -c blue,green \"npm run dev -w @warmbox/api\" \"npm run dev -w @warmbox/web\"",
|
||||
"build": "npm run build -w @warmbox/shared && npm run build -w @warmbox/api && npm run build -w @warmbox/web",
|
||||
"start": "npm run start -w @warmbox/api",
|
||||
"docker:up": "docker compose up -d --build",
|
||||
"docker:logs": "docker compose logs -f warmbox",
|
||||
"db:migrate": "prisma migrate dev",
|
||||
"db:generate": "prisma generate",
|
||||
"db:push": "prisma db push",
|
||||
|
|
|
|||
12
scripts/docker-entrypoint.sh
Executable file
12
scripts/docker-entrypoint.sh
Executable file
|
|
@ -0,0 +1,12 @@
|
|||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
mkdir -p /data
|
||||
|
||||
cd /app
|
||||
|
||||
echo "Running database migrations..."
|
||||
npx prisma migrate deploy
|
||||
|
||||
echo "Starting Warmbox API..."
|
||||
exec node apps/api/dist/index.js
|
||||
Loading…
Reference in a new issue