mirror of
https://github.com/apidoorman/doorman.git
synced 2026-02-09 02:29:42 -06:00
New demo mode updates
This commit is contained in:
79
Dockerfile.demo
Normal file
79
Dockerfile.demo
Normal file
@@ -0,0 +1,79 @@
|
||||
# Demo image: Python backend (Doorman) + Next.js web client
|
||||
# All demo environment is baked into the image (no .env required).
|
||||
|
||||
FROM python:3.11-slim AS base
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PIP_NO_CACHE_DIR=1
|
||||
|
||||
# Install Node.js + npm and useful tools
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
nodejs npm curl ca-certificates git \
|
||||
&& npm i -g npm@^10 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Backend dependencies first for better layer caching
|
||||
COPY backend-services/requirements.txt /app/backend-services/requirements.txt
|
||||
RUN python -m pip install --upgrade pip \
|
||||
&& pip install -r /app/backend-services/requirements.txt
|
||||
|
||||
# Prepare web client dependencies separately for better caching
|
||||
WORKDIR /app/web-client
|
||||
COPY web-client/package*.json ./
|
||||
RUN npm ci --include=dev
|
||||
|
||||
# Copy backend source only (avoid copying entire repo)
|
||||
WORKDIR /app
|
||||
COPY backend-services /app/backend-services
|
||||
|
||||
# Copy web client sources (excluding node_modules via .dockerignore)
|
||||
WORKDIR /app/web-client
|
||||
COPY web-client/ .
|
||||
|
||||
# Build-time args for frontend env (baked into Next.js bundle)
|
||||
# For demo, leave gateway URL empty so client uses same-origin and Next.js rewrites proxy to the API.
|
||||
ARG NEXT_PUBLIC_PROTECTED_USERS=demo@doorman.dev
|
||||
ARG NEXT_PUBLIC_GATEWAY_URL=
|
||||
|
||||
# Build Next.js (production) with baked public env
|
||||
RUN echo "export NEXT_PUBLIC_PROTECTED_USERS=${NEXT_PUBLIC_PROTECTED_USERS}" > /tmp/build-env.sh && \
|
||||
echo "export NEXT_PUBLIC_GATEWAY_URL=${NEXT_PUBLIC_GATEWAY_URL}" >> /tmp/build-env.sh && \
|
||||
echo "export NODE_ENV=production" >> /tmp/build-env.sh && \
|
||||
echo "export NEXT_TELEMETRY_DISABLED=1" >> /tmp/build-env.sh && \
|
||||
echo "export DEMO_MODE=true" >> /tmp/build-env.sh && \
|
||||
. /tmp/build-env.sh && \
|
||||
npm run build && \
|
||||
npm prune --omit=dev
|
||||
|
||||
# Runtime configuration baked for demo
|
||||
WORKDIR /app
|
||||
|
||||
# Demo defaults (no external .env needed)
|
||||
ENV ENV=development \
|
||||
MEM_OR_EXTERNAL=MEM \
|
||||
THREADS=1 \
|
||||
HTTPS_ONLY=false \
|
||||
DOORMAN_ADMIN_EMAIL=demo@doorman.dev \
|
||||
DOORMAN_ADMIN_PASSWORD=DemoPassword123! \
|
||||
JWT_SECRET_KEY=demo-secret-change-me-please-32-bytes-min \
|
||||
DEMO_SEED=false \
|
||||
DEMO_MODE=true \
|
||||
ENABLE_STARLETTE_CORS=true \
|
||||
ALLOWED_ORIGINS=http://localhost:3000 \
|
||||
ALLOW_METHODS="GET,POST,PUT,DELETE,OPTIONS,PATCH,HEAD" \
|
||||
ALLOW_HEADERS="*" \
|
||||
ALLOW_CREDENTIALS=true \
|
||||
PORT=3001 \
|
||||
WEB_PORT=3000
|
||||
|
||||
# Add entrypoint
|
||||
COPY docker/entrypoint.sh /app/docker/entrypoint.sh
|
||||
RUN chmod +x /app/docker/entrypoint.sh
|
||||
|
||||
EXPOSE 3001 3000
|
||||
|
||||
CMD ["/app/docker/entrypoint.sh"]
|
||||
15
README.md
15
README.md
@@ -42,22 +42,31 @@ When ready:
|
||||
- Web UI: `http://localhost:3000`
|
||||
- Gateway API: `http://localhost:3001`
|
||||
|
||||
### One‑Command Demo (in‑memory + auto‑seed)
|
||||
### One‑Command Demo (in‑memory)
|
||||
|
||||
Spin up a preconfigured demo (auto‑cleans on exit) without editing `.env`:
|
||||
|
||||
```bash
|
||||
# First time (build the demo image to include frontend proxy config)
|
||||
docker compose -f docker-compose.yml -f docker-compose.demo.yml up --build
|
||||
|
||||
# Next runs (no rebuild needed)
|
||||
docker compose -f docker-compose.yml -f docker-compose.demo.yml up
|
||||
```
|
||||
|
||||
Defaults:
|
||||
Defaults (demo‑only):
|
||||
- Admin: `demo@doorman.dev` / `DemoPassword123!`
|
||||
- Web UI: `http://localhost:3000`
|
||||
- API: `http://localhost:3001`
|
||||
- Mode: in‑memory (no Redis/Mongo), demo data auto‑seeded in‑process on start
|
||||
- Mode: in‑memory (no Redis/Mongo); no seed data created
|
||||
- Isolation: uses separate image tag (`doorman-demo:latest`) and project name to avoid overwriting any existing `doorman`
|
||||
- Cleanup: containers, volumes, and networks are removed automatically when you stop (Ctrl+C)
|
||||
|
||||
Notes:
|
||||
- Demo serves the API through the frontend (same‑origin proxy) so browser cookies work without cross‑port CORS issues. The first run with `--build` bakes this Next.js proxy configuration.
|
||||
- Demo runs fully in‑container: it does not mount the host `generated/` directory and disables memory auto‑save to avoid cross‑contamination with a non‑demo instance.
|
||||
- If you ran an older demo before this change and see demo data in a non‑demo run, stop the container and delete host memory dumps: `rm -rf generated/memory_dump*`.
|
||||
|
||||
## Frontend Gateway Configuration
|
||||
|
||||
The web client needs to know the backend gateway URL. Set `NEXT_PUBLIC_GATEWAY_URL` in the root `.env` file:
|
||||
|
||||
@@ -154,11 +154,17 @@ class Database:
|
||||
# for tests that adjust the env and call initialize_collections again.
|
||||
try:
|
||||
env_pwd = os.getenv('DOORMAN_ADMIN_PASSWORD')
|
||||
env_email = os.getenv('DOORMAN_ADMIN_EMAIL')
|
||||
if env_pwd:
|
||||
users.update_one(
|
||||
{'username': 'admin'},
|
||||
{'$set': {'password': password_util.hash_password(env_pwd)}},
|
||||
)
|
||||
if env_email:
|
||||
users.update_one(
|
||||
{'username': 'admin'},
|
||||
{'$set': {'email': env_email}},
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@@ -189,6 +189,12 @@ class AsyncDatabase:
|
||||
raise RuntimeError(
|
||||
'Admin user missing password and DOORMAN_ADMIN_PASSWORD not set'
|
||||
)
|
||||
# Ensure admin email matches env if provided
|
||||
env_email = os.getenv('DOORMAN_ADMIN_EMAIL')
|
||||
if adm and env_email and adm.get('email') != env_email:
|
||||
await self.db.users.update_one(
|
||||
{'username': 'admin'}, {'$set': {'email': env_email}}
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@@ -1,33 +1,25 @@
|
||||
name: doorman-demo
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
doorman:
|
||||
# Reuse the same build but inject demo-friendly frontend URL
|
||||
# Build from demo Dockerfile with baked env
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
NEXT_PUBLIC_GATEWAY_URL: http://localhost:3001
|
||||
NEXT_PUBLIC_PROTECTED_USERS: demo@doorman.dev
|
||||
dockerfile: Dockerfile.demo
|
||||
image: doorman-demo:latest
|
||||
container_name: null
|
||||
# Provide critical runtime env so it works even without a fresh build
|
||||
environment:
|
||||
# App mode
|
||||
ENV: development
|
||||
MEM_OR_EXTERNAL: MEM
|
||||
THREADS: 1
|
||||
HTTPS_ONLY: "false"
|
||||
|
||||
# Admin bootstrap
|
||||
DOORMAN_ADMIN_EMAIL: demo@doorman.dev
|
||||
DOORMAN_ADMIN_PASSWORD: DemoPassword123!
|
||||
JWT_SECRET_KEY: demo-secret-change-me-please-32-bytes-min
|
||||
|
||||
# Demo seeding
|
||||
DEMO_SEED: "true"
|
||||
|
||||
# Ports (compose defaults already match these)
|
||||
# Keep demo memory isolated; avoid writing/reading host dumps
|
||||
MEM_AUTO_SAVE_ENABLED: "false"
|
||||
MEM_DUMP_PATH: "/tmp/demo_memory_dump.bin"
|
||||
ENABLE_STARLETTE_CORS: "true"
|
||||
ALLOWED_ORIGINS: http://localhost:3000
|
||||
ALLOW_METHODS: GET,POST,PUT,DELETE,OPTIONS,PATCH,HEAD
|
||||
ALLOW_HEADERS: "*"
|
||||
ALLOW_CREDENTIALS: "true"
|
||||
PORT: 3001
|
||||
WEB_PORT: 3000
|
||||
|
||||
@@ -39,6 +31,7 @@ services:
|
||||
# Ensure it doesn't restart and doesn't persist bind mounts from base compose
|
||||
restart: "no"
|
||||
volumes: []
|
||||
container_name: doorman-demo
|
||||
|
||||
demo-cleanup:
|
||||
image: docker:27.3.1-cli
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
# 1. Set MEM_OR_EXTERNAL=REDIS in .env
|
||||
# 2. Run: docker compose --profile production up
|
||||
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
doorman:
|
||||
build:
|
||||
|
||||
@@ -67,6 +67,27 @@ load_env_files
|
||||
|
||||
trap graceful_stop SIGTERM SIGINT
|
||||
|
||||
# Start backend (Doorman) in the foreground so logs go to container stdout
|
||||
echo "[entrypoint] Starting Doorman backend..."
|
||||
(
|
||||
cd /app/backend-services
|
||||
# Ensure required directories
|
||||
mkdir -p proto generated logs
|
||||
python doorman.py run
|
||||
) &
|
||||
BACK_PID=$!
|
||||
|
||||
# Start web client (Next.js)
|
||||
echo "[entrypoint] Starting web client..."
|
||||
(
|
||||
cd /app/web-client
|
||||
# Start Next.js on WEB_PORT, bind to 0.0.0.0 for container networking
|
||||
PORT="${WEB_PORT:-3000}" npm run start -- -H 0.0.0.0 -p "${WEB_PORT:-3000}"
|
||||
) &
|
||||
WEB_PID=$!
|
||||
|
||||
echo "[entrypoint] Services launched. Backend PID=$BACK_PID Web PID=$WEB_PID"
|
||||
|
||||
# Optional: Demo seeding (in-memory, for quick start demos)
|
||||
if [ "${DEMO_SEED:-false}" = "true" ]; then
|
||||
(
|
||||
@@ -95,27 +116,6 @@ if [ "${DEMO_SEED:-false}" = "true" ]; then
|
||||
) &
|
||||
fi
|
||||
|
||||
# Start backend (Doorman) in the foreground so logs go to container stdout
|
||||
echo "[entrypoint] Starting Doorman backend..."
|
||||
(
|
||||
cd /app/backend-services
|
||||
# Ensure required directories
|
||||
mkdir -p proto generated logs
|
||||
python doorman.py run
|
||||
) &
|
||||
BACK_PID=$!
|
||||
|
||||
# Start web client (Next.js)
|
||||
echo "[entrypoint] Starting web client..."
|
||||
(
|
||||
cd /app/web-client
|
||||
# Start Next.js on WEB_PORT, bind to 0.0.0.0 for container networking
|
||||
PORT="${WEB_PORT:-3000}" npm run start -- -H 0.0.0.0 -p "${WEB_PORT:-3000}"
|
||||
) &
|
||||
WEB_PID=$!
|
||||
|
||||
echo "[entrypoint] Services launched. Backend PID=$BACK_PID Web PID=$WEB_PID"
|
||||
|
||||
# Wait on either process to exit, then stop gracefully
|
||||
wait -n || true
|
||||
# Wait on either main service to exit, then stop gracefully
|
||||
wait -n "$BACK_PID" "$WEB_PID" || true
|
||||
graceful_stop
|
||||
|
||||
19
web-client/next.config.js
Normal file
19
web-client/next.config.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const isDemo = process.env.DEMO_MODE === 'true'
|
||||
|
||||
const nextConfig = {
|
||||
// Keep production builds resilient (regular and demo)
|
||||
eslint: { ignoreDuringBuilds: true },
|
||||
typescript: { ignoreBuildErrors: true },
|
||||
async rewrites() {
|
||||
if (!isDemo) return []
|
||||
// Demo: proxy API to backend on 3001, keep browser same-origin (3000)
|
||||
const target = process.env.GATEWAY_INTERNAL_URL || 'http://localhost:3001'
|
||||
return [
|
||||
{ source: '/platform/:path*', destination: `${target}/platform/:path*` },
|
||||
{ source: '/api/:path*', destination: `${target}/api/:path*` },
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
@@ -1,6 +1,8 @@
|
||||
// Gateway URL from environment - required for API calls
|
||||
// Set NEXT_PUBLIC_GATEWAY_URL in root .env file (loaded via dotenv-cli for dev, build arg for Docker)
|
||||
const GATEWAY_URL_RAW = process.env.NEXT_PUBLIC_GATEWAY_URL || ''
|
||||
const DEMO = process.env.DEMO_MODE === 'true'
|
||||
// In demo, force same-origin by ignoring NEXT_PUBLIC_GATEWAY_URL
|
||||
const GATEWAY_URL_RAW = DEMO ? '' : (process.env.NEXT_PUBLIC_GATEWAY_URL || '')
|
||||
|
||||
let _cachedUrl: string | null = null
|
||||
|
||||
|
||||
Reference in New Issue
Block a user