Complete stage 7
This commit is contained in:
parent
3e7b89bcad
commit
1f88e291a9
9 changed files with 784 additions and 26 deletions
|
|
@ -32,3 +32,8 @@ OIDC_JWKS_URI=http://localhost:9000/application/o/kaboot/jwks/
|
|||
# OPTIONAL - CORS (Frontend origin for backend API)
|
||||
# ==============================================================================
|
||||
CORS_ORIGIN=http://localhost:5173
|
||||
|
||||
# ==============================================================================
|
||||
# OPTIONAL - Logging
|
||||
# ==============================================================================
|
||||
LOG_REQUESTS=false
|
||||
|
|
|
|||
20
Caddyfile.example
Normal file
20
Caddyfile.example
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# Kaboot Production Caddyfile
|
||||
# Copy this file to Caddyfile and update the domain names
|
||||
|
||||
kaboot.example.com {
|
||||
root * /srv/frontend
|
||||
file_server
|
||||
try_files {path} /index.html
|
||||
|
||||
handle /api/* {
|
||||
reverse_proxy kaboot-backend:3001
|
||||
}
|
||||
|
||||
handle /health {
|
||||
reverse_proxy kaboot-backend:3001
|
||||
}
|
||||
}
|
||||
|
||||
auth.example.com {
|
||||
reverse_proxy authentik-server:9000
|
||||
}
|
||||
|
|
@ -299,21 +299,21 @@ Add user accounts via Authentik (OIDC) and persist quizzes to SQLite database. U
|
|||
## Phase 7: Documentation & Deployment
|
||||
|
||||
### 7.1 Documentation
|
||||
- [ ] Update main `README.md`:
|
||||
- [ ] Add Docker Compose setup instructions
|
||||
- [ ] Document environment variables
|
||||
- [ ] Add Authentik configuration steps
|
||||
- [ ] Create `docs/AUTHENTIK_SETUP.md` (detailed IdP setup)
|
||||
- [ ] Create `docs/API.md` (backend API documentation)
|
||||
- [x] Update main `README.md`:
|
||||
- [x] Add Docker Compose setup instructions
|
||||
- [x] Document environment variables
|
||||
- [x] Add Authentik configuration steps
|
||||
- [x] Create `docs/AUTHENTIK_SETUP.md` (detailed IdP setup)
|
||||
- [x] Create `docs/API.md` (backend API documentation)
|
||||
|
||||
### 7.2 Production Considerations
|
||||
- [ ] Document production deployment:
|
||||
- [ ] HTTPS setup (reverse proxy)
|
||||
- [ ] Update redirect URIs for production domain
|
||||
- [ ] Database backup strategy
|
||||
- [ ] Authentik email configuration
|
||||
- [ ] Add health check endpoints
|
||||
- [ ] Add logging configuration
|
||||
- [x] Document production deployment:
|
||||
- [x] HTTPS setup (reverse proxy)
|
||||
- [x] Update redirect URIs for production domain
|
||||
- [x] Database backup strategy
|
||||
- [x] Authentik email configuration
|
||||
- [x] Add health check endpoints
|
||||
- [x] Add logging configuration
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -399,7 +399,7 @@ kaboot/
|
|||
|
||||
## Progress Tracking
|
||||
|
||||
**Last Updated**: 2026-01-13
|
||||
**Last Updated**: 2026-01-14
|
||||
|
||||
| Phase | Status | Notes |
|
||||
|-------|--------|-------|
|
||||
|
|
@ -409,4 +409,4 @@ kaboot/
|
|||
| Phase 4 | **COMPLETE** | useQuizLibrary hook, QuizLibrary modal, Landing integration |
|
||||
| Phase 5 | **COMPLETE** | SaveQuizPrompt modal, QuizCreator save checkbox, save integration |
|
||||
| Phase 6 | **COMPLETE** | Toast notifications, loading states, error handling, edge cases |
|
||||
| Phase 7 | Not Started | |
|
||||
| Phase 7 | **COMPLETE** | README, API docs, PRODUCTION docs, health checks, logging |
|
||||
|
|
|
|||
124
README.md
124
README.md
|
|
@ -2,19 +2,123 @@
|
|||
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
|
||||
</div>
|
||||
|
||||
# Run and deploy your AI Studio app
|
||||
# Kaboot
|
||||
|
||||
This contains everything you need to run your app locally.
|
||||
Kaboot is an AI-powered quiz party game inspired by Kahoot. It leverages the Google Gemini API to instantly generate engaging quizzes on any topic, allowing users to host and join multiplayer games with ease.
|
||||
|
||||
View your app in AI Studio: https://ai.studio/apps/drive/1N0ITrr45ZWdQvXMQNxOULCmJBQyaiWH8
|
||||
## Features
|
||||
|
||||
## Run Locally
|
||||
- **AI Quiz Generation**: Create full quizzes in seconds by simply providing a topic or prompt using Google Gemini.
|
||||
- **Real-time Multiplayer**: Host games and have players join via game pins using Peer-to-Peer technology.
|
||||
- **Single-player Arcade Mode**: Play against AI bots to sharpen your skills.
|
||||
- **Secure Authentication**: Integrated with Authentik for robust OIDC-based user management.
|
||||
- **Quiz Library**: Save, manage, and reuse your AI-generated quizzes.
|
||||
- **Dynamic UI**: A premium, responsive interface built with React, Framer Motion, and Lucide.
|
||||
|
||||
**Prerequisites:** Node.js
|
||||
## Architecture
|
||||
|
||||
Kaboot is built with a modern decoupled architecture:
|
||||
|
||||
1. Install dependencies:
|
||||
`npm install`
|
||||
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
|
||||
3. Run the app:
|
||||
`npm run dev`
|
||||
- **Frontend**: React (Vite) with PeerJS for real-time communication.
|
||||
- **Backend**: Node.js Express server managing the quiz database and session state.
|
||||
- **Database**: SQLite (Better-SQLite3) for lightweight, reliable data storage.
|
||||
- **Identity Provider**: Authentik (running in Docker) providing OIDC authentication.
|
||||
- **Infrastructure**: Redis and PostgreSQL (supporting Authentik).
|
||||
- **AI Engine**: Google Gemini API for intelligent content generation.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you begin, ensure you have the following installed:
|
||||
|
||||
- **Docker & Docker Compose**: For running the backend services and authentication.
|
||||
- **Node.js (v18+)**: For local frontend development.
|
||||
- **Google Gemini API Key**: Required for AI quiz generation.
|
||||
|
||||
## Quick Start
|
||||
|
||||
The fastest way to get Kaboot running is using Docker Compose.
|
||||
|
||||
### 1. Initialize Environment
|
||||
Run the setup script to generate necessary secrets and create your `.env` file:
|
||||
|
||||
```bash
|
||||
chmod +x scripts/setup.sh
|
||||
./scripts/setup.sh
|
||||
```
|
||||
|
||||
### 2. Configure Gemini API
|
||||
Open the newly created `.env` file and add your Gemini API key:
|
||||
|
||||
```env
|
||||
GEMINI_API_KEY=your_api_key_here
|
||||
```
|
||||
|
||||
### 3. Start Services
|
||||
Launch the entire stack using Docker Compose:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
This will start:
|
||||
- **Authentik** (Port 9000)
|
||||
- **PostgreSQL** & **Redis** (Internal)
|
||||
- **Kaboot Backend** (Port 3001)
|
||||
|
||||
### 4. Setup Authentication
|
||||
Follow the [Authentik Setup Guide](docs/AUTHENTIK_SETUP.md) to configure the OIDC provider.
|
||||
|
||||
## Development Setup
|
||||
|
||||
If you want to run the frontend in development mode with hot-reloading:
|
||||
|
||||
1. **Install Dependencies**:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. **Run Development Server**:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
The frontend will be available at `http://localhost:5173`.
|
||||
|
||||
## Configuration
|
||||
|
||||
Kaboot uses environment variables for configuration. Refer to `.env.example` for a complete list.
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `KABOOT_BACKEND_PORT` | Port for the Express backend | `3001` |
|
||||
| `AUTHENTIK_PORT_HTTP` | Port for Authentik web interface | `9000` |
|
||||
| `GEMINI_API_KEY` | Your Google Gemini API key | (Required) |
|
||||
| `CORS_ORIGIN` | Allowed origin for API requests | `http://localhost:5173` |
|
||||
| `PG_PASS` | PostgreSQL password for Authentik | (Generated) |
|
||||
| `AUTHENTIK_SECRET_KEY` | Secret key for Authentik | (Generated) |
|
||||
|
||||
## Testing
|
||||
|
||||
### Backend Tests
|
||||
To run the backend test suite:
|
||||
|
||||
```bash
|
||||
cd server
|
||||
npm install
|
||||
npm test
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Authentik Configuration](docs/AUTHENTIK_SETUP.md)
|
||||
- [API Reference](docs/API.md)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Authentik Initial Setup**: If you can't access the setup page, ensure `authentik-server` container is healthy using `docker compose ps`.
|
||||
- **CORS Errors**: Verify that `CORS_ORIGIN` in your `.env` matches your frontend URL.
|
||||
- **Gemini API Issues**: Ensure your API key is valid and has sufficient quota. Check the browser console for specific error messages from the GenAI SDK.
|
||||
- **Database Locked**: If you encounter SQLite locking issues, ensure only one instance of the backend is writing to the database volume.
|
||||
|
||||
---
|
||||
|
||||
View your app in AI Studio: [https://ai.studio/apps/drive/1N0ITrr45ZWdQvXMQNxOULCmJBQyaiWH8](https://ai.studio/apps/drive/1N0ITrr45ZWdQvXMQNxOULCmJBQyaiWH8)
|
||||
|
|
|
|||
3
ai-todo.md
Normal file
3
ai-todo.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Kaboot Documentation Tasks
|
||||
|
||||
- [x] Create /Users/joey/Downloads/kaboot/docs/PRODUCTION.md with production deployment guide
|
||||
35
docker-compose.caddy.yml
Normal file
35
docker-compose.caddy.yml
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
# Caddy Reverse Proxy for Kaboot Production
|
||||
#
|
||||
# This compose file adds Caddy as a reverse proxy with automatic HTTPS.
|
||||
# Use with the main docker-compose.yml using the -f flag.
|
||||
#
|
||||
# Usage:
|
||||
# docker compose -f docker-compose.yml -f docker-compose.caddy.yml up -d
|
||||
#
|
||||
# Prerequisites:
|
||||
# 1. Create a Caddyfile in the project root (see docs/PRODUCTION.md)
|
||||
# 2. Build the frontend: npm run build
|
||||
# 3. Update your domain DNS to point to your server
|
||||
|
||||
services:
|
||||
caddy:
|
||||
image: caddy:2-alpine
|
||||
container_name: kaboot-caddy
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- ./dist:/srv/frontend:ro
|
||||
- caddy-data:/data
|
||||
- caddy-config:/config
|
||||
depends_on:
|
||||
- kaboot-backend
|
||||
- authentik-server
|
||||
networks:
|
||||
- kaboot-network
|
||||
|
||||
volumes:
|
||||
caddy-data:
|
||||
caddy-config:
|
||||
239
docs/API.md
Normal file
239
docs/API.md
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
# Kaboot Backend API Documentation
|
||||
|
||||
The Kaboot backend provides a RESTful API for managing quizzes and user profiles.
|
||||
|
||||
## Base URL
|
||||
The backend server runs at:
|
||||
`http://localhost:3001`
|
||||
|
||||
## Authentication
|
||||
All routes under `/api/*` require authentication using an OIDC Bearer token in the `Authorization` header.
|
||||
|
||||
```http
|
||||
Authorization: Bearer <your_access_token>
|
||||
```
|
||||
|
||||
Tokens are issued by the Authentik Identity Provider.
|
||||
|
||||
---
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Health Check
|
||||
|
||||
#### GET /health
|
||||
Check the operational status of the backend server.
|
||||
|
||||
- **Authentication**: None
|
||||
- **Response**: `200 OK`
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"timestamp": "2026-01-13T10:00:00.000Z"
|
||||
}
|
||||
```
|
||||
- **Curl Example**:
|
||||
```bash
|
||||
curl http://localhost:3001/health
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### User Profile
|
||||
|
||||
#### GET /api/users/me
|
||||
Retrieve the profile of the currently authenticated user.
|
||||
|
||||
- **Authentication**: Required
|
||||
- **Response**: `200 OK`
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"id": "user_123",
|
||||
"username": "jdoe",
|
||||
"email": "jdoe@example.com",
|
||||
"displayName": "John Doe",
|
||||
"createdAt": "2026-01-13T10:00:00.000Z",
|
||||
"lastLogin": "2026-01-13T10:00:00.000Z",
|
||||
"isNew": false
|
||||
}
|
||||
```
|
||||
- **Curl Example**:
|
||||
```bash
|
||||
curl -H "Authorization: Bearer <token>" http://localhost:3001/api/users/me
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Quizzes
|
||||
|
||||
#### GET /api/quizzes
|
||||
List all quizzes created by the authenticated user.
|
||||
|
||||
- **Authentication**: Required
|
||||
- **Response**: `200 OK`
|
||||
- **Example Response**:
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "quiz_8b3f...",
|
||||
"title": "World Capitals",
|
||||
"source": "manual",
|
||||
"aiTopic": null,
|
||||
"createdAt": "2026-01-13T10:00:00.000Z",
|
||||
"updatedAt": "2026-01-13T10:00:00.000Z",
|
||||
"questionCount": 5
|
||||
}
|
||||
]
|
||||
```
|
||||
- **Curl Example**:
|
||||
```bash
|
||||
curl -H "Authorization: Bearer <token>" http://localhost:3001/api/quizzes
|
||||
```
|
||||
|
||||
#### GET /api/quizzes/:id
|
||||
Retrieve full details for a specific quiz, including all questions and answer options.
|
||||
|
||||
- **Authentication**: Required
|
||||
- **Response**: `200 OK` or `404 Not Found`
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"id": "quiz_8b3f...",
|
||||
"title": "World Capitals",
|
||||
"source": "manual",
|
||||
"aiTopic": null,
|
||||
"createdAt": "2026-01-13T10:00:00.000Z",
|
||||
"updatedAt": "2026-01-13T10:00:00.000Z",
|
||||
"questions": [
|
||||
{
|
||||
"id": "question_456",
|
||||
"text": "What is the capital of Japan?",
|
||||
"timeLimit": 20,
|
||||
"orderIndex": 0,
|
||||
"options": [
|
||||
{
|
||||
"id": "option_789",
|
||||
"text": "Tokyo",
|
||||
"isCorrect": true,
|
||||
"shape": "triangle",
|
||||
"color": "red",
|
||||
"reason": "Tokyo is the political and economic center of Japan.",
|
||||
"orderIndex": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
- **Curl Example**:
|
||||
```bash
|
||||
curl -H "Authorization: Bearer <token>" http://localhost:3001/api/quizzes/quiz_8b3f...
|
||||
```
|
||||
|
||||
#### POST /api/quizzes
|
||||
Create a new quiz.
|
||||
|
||||
- **Authentication**: Required
|
||||
- **Validation Rules**:
|
||||
- `title`: Required, non-empty string.
|
||||
- `source`: Required, must be `'manual'` or `'ai_generated'`.
|
||||
- `questions`: Required, array with at least 1 question.
|
||||
- **Question validation**:
|
||||
- `text`: Required, non-empty string.
|
||||
- `options`: Required, array with at least 2 options.
|
||||
- Each option:
|
||||
- `text`: Required, non-empty string.
|
||||
- `isCorrect`: Required, boolean.
|
||||
- `shape`: Required, one of `'triangle'`, `'diamond'`, `'circle'`, `'square'`.
|
||||
- `color`: Required, one of `'red'`, `'blue'`, `'yellow'`, `'green'`.
|
||||
- `reason`: Optional, string explaining the answer.
|
||||
- At least one option must be marked as correct (`isCorrect: true`).
|
||||
- **Request Body**:
|
||||
```json
|
||||
{
|
||||
"title": "Space Exploration",
|
||||
"source": "manual",
|
||||
"questions": [
|
||||
{
|
||||
"text": "Which planet is known as the Red Planet?",
|
||||
"timeLimit": 20,
|
||||
"options": [
|
||||
{ "text": "Mars", "isCorrect": true, "shape": "triangle", "color": "red" },
|
||||
{ "text": "Venus", "isCorrect": false, "shape": "diamond", "color": "blue" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
- **Response**: `201 Created`
|
||||
- **Example Response**:
|
||||
```json
|
||||
{
|
||||
"id": "new_quiz_uuid"
|
||||
}
|
||||
```
|
||||
- **Curl Example**:
|
||||
```bash
|
||||
curl -X POST -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
|
||||
-d '{"title":"Space Quiz","source":"manual","questions":[{"text":"Which planet is known as the Red Planet?","options":[{"text":"Mars","isCorrect":true,"shape":"triangle","color":"red"},{"text":"Venus","isCorrect":false,"shape":"diamond","color":"blue"}]}]}' \
|
||||
http://localhost:3001/api/quizzes
|
||||
```
|
||||
|
||||
#### PUT /api/quizzes/:id
|
||||
Update an existing quiz. This operation replaces the existing questions and options.
|
||||
|
||||
- **Authentication**: Required
|
||||
- **Validation Rules**: Same as `POST /api/quizzes`.
|
||||
- **Response**: `200 OK` or `404 Not Found`
|
||||
- **Curl Example**:
|
||||
```bash
|
||||
curl -X PUT -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
|
||||
-d '{"title":"Updated Space Quiz","questions":[{"text":"Which planet is red?","options":[{"text":"Mars","isCorrect":true,"shape":"triangle","color":"red"},{"text":"Venus","isCorrect":false,"shape":"diamond","color":"blue"}]}]}' \
|
||||
http://localhost:3001/api/quizzes/quiz_uuid
|
||||
```
|
||||
|
||||
#### DELETE /api/quizzes/:id
|
||||
Permanently delete a quiz.
|
||||
|
||||
- **Authentication**: Required
|
||||
- **Response**: `204 No Content` or `404 Not Found`
|
||||
- **Curl Example**:
|
||||
```bash
|
||||
curl -X DELETE -H "Authorization: Bearer <token>" http://localhost:3001/api/quizzes/quiz_uuid
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Responses
|
||||
|
||||
The API returns standard HTTP status codes along with a JSON error object.
|
||||
|
||||
- **400 Bad Request**
|
||||
Returned when the request body is invalid or validation fails.
|
||||
```json
|
||||
{ "error": "Title is required and cannot be empty" }
|
||||
```
|
||||
|
||||
- **401 Unauthorized**
|
||||
Returned when the Bearer token is missing, expired, or invalid.
|
||||
```json
|
||||
{ "error": "Missing or invalid authorization header" }
|
||||
```
|
||||
or
|
||||
```json
|
||||
{ "error": "Invalid token", "details": "..." }
|
||||
```
|
||||
|
||||
- **404 Not Found**
|
||||
Returned when the requested quiz does not exist or does not belong to the user.
|
||||
```json
|
||||
{ "error": "Quiz not found" }
|
||||
```
|
||||
|
||||
- **500 Internal Server Error**
|
||||
Returned when an unexpected error occurs on the server.
|
||||
```json
|
||||
{ "error": "Internal server error" }
|
||||
```
|
||||
326
docs/PRODUCTION.md
Normal file
326
docs/PRODUCTION.md
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
# Production Deployment Guide
|
||||
|
||||
This guide provides instructions for deploying Kaboot to a production environment. It covers security, persistence, and configuration for a robust setup.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- A Linux server with Docker and Docker Compose installed.
|
||||
- A registered domain name (e.g., `kaboot.example.com`).
|
||||
- SSL certificates (e.g., from Let's Encrypt).
|
||||
- A Google Gemini API key.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
In production, the stack consists of:
|
||||
- **Nginx**: Reverse proxy handling HTTPS and routing.
|
||||
- **Authentik**: Identity Provider for authentication.
|
||||
- **Kaboot Backend**: Express server for quiz logic and SQLite storage.
|
||||
- **Kaboot Frontend**: Static assets served via Nginx or a dedicated service.
|
||||
- **PostgreSQL**: Database for Authentik.
|
||||
- **Redis**: Cache and task queue for Authentik.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Create a production `.env` file. Do not commit this file to version control.
|
||||
|
||||
### Backend & Authentik Configuration
|
||||
|
||||
```env
|
||||
# Database Passwords (Generate strong secrets)
|
||||
PG_PASS=your_strong_postgres_password
|
||||
AUTHENTIK_SECRET_KEY=your_strong_authentik_secret
|
||||
|
||||
# Infrastructure
|
||||
AUTHENTIK_PORT_HTTP=9000
|
||||
KABOOT_BACKEND_PORT=3001
|
||||
|
||||
# AI Configuration
|
||||
GEMINI_API_KEY=your_gemini_api_key
|
||||
|
||||
# OIDC Production Settings
|
||||
OIDC_ISSUER=https://auth.example.com/application/o/kaboot/
|
||||
OIDC_JWKS_URI=https://auth.example.com/application/o/kaboot/jwks/
|
||||
|
||||
# Security
|
||||
CORS_ORIGIN=https://kaboot.example.com
|
||||
LOG_REQUESTS=true
|
||||
```
|
||||
|
||||
### Frontend Configuration
|
||||
|
||||
The frontend requires environment variables at build time:
|
||||
- `VITE_API_URL`: `https://kaboot.example.com/api`
|
||||
- `VITE_OIDC_AUTHORITY`: `https://auth.example.com/application/o/kaboot/`
|
||||
|
||||
## Docker Compose Production Example
|
||||
|
||||
Create a `docker-compose.prod.yml` for your production environment:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
postgresql:
|
||||
image: docker.io/library/postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
volumes:
|
||||
- postgresql-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${PG_PASS}
|
||||
POSTGRES_USER: ${PG_USER:-authentik}
|
||||
POSTGRES_DB: ${PG_DB:-authentik}
|
||||
networks:
|
||||
- kaboot-network
|
||||
|
||||
redis:
|
||||
image: docker.io/library/redis:alpine
|
||||
restart: unless-stopped
|
||||
command: --save 60 1 --loglevel warning
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
networks:
|
||||
- kaboot-network
|
||||
|
||||
authentik-server:
|
||||
image: ghcr.io/goauthentik/server:2025.2
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
AUTHENTIK_REDIS__HOST: redis
|
||||
AUTHENTIK_POSTGRESQL__HOST: postgresql
|
||||
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
|
||||
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
|
||||
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
|
||||
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
|
||||
volumes:
|
||||
- ./authentik/media:/media
|
||||
- ./authentik/custom-templates:/templates
|
||||
depends_on:
|
||||
postgresql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- kaboot-network
|
||||
|
||||
authentik-worker:
|
||||
image: ghcr.io/goauthentik/server:2025.2
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
environment:
|
||||
AUTHENTIK_REDIS__HOST: redis
|
||||
AUTHENTIK_POSTGRESQL__HOST: postgresql
|
||||
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
|
||||
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
|
||||
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
|
||||
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
|
||||
user: root
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./authentik/media:/media
|
||||
- ./authentik/certs:/certs
|
||||
- ./authentik/custom-templates:/templates
|
||||
depends_on:
|
||||
postgresql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- kaboot-network
|
||||
|
||||
kaboot-backend:
|
||||
build:
|
||||
context: ./server
|
||||
dockerfile: Dockerfile
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: 3001
|
||||
DATABASE_PATH: /data/kaboot.db
|
||||
OIDC_ISSUER: ${OIDC_ISSUER}
|
||||
OIDC_JWKS_URI: ${OIDC_JWKS_URI}
|
||||
CORS_ORIGIN: ${CORS_ORIGIN}
|
||||
LOG_REQUESTS: ${LOG_REQUESTS}
|
||||
volumes:
|
||||
- kaboot-data:/data
|
||||
networks:
|
||||
- kaboot-network
|
||||
|
||||
volumes:
|
||||
postgresql-data:
|
||||
redis-data:
|
||||
kaboot-data:
|
||||
|
||||
networks:
|
||||
kaboot-network:
|
||||
driver: bridge
|
||||
```
|
||||
|
||||
## HTTPS and Reverse Proxy
|
||||
|
||||
Choose one of the following reverse proxy options. Caddy is recommended for its simplicity and automatic HTTPS.
|
||||
|
||||
### Option 1: Caddy (Recommended)
|
||||
|
||||
Caddy automatically obtains and renews SSL certificates from Let's Encrypt.
|
||||
|
||||
A separate `docker-compose.caddy.yml` is provided to add Caddy to your stack.
|
||||
|
||||
**Step 1: Create the Caddyfile**
|
||||
|
||||
Copy and customize the example Caddyfile:
|
||||
|
||||
```bash
|
||||
cp Caddyfile.example Caddyfile
|
||||
```
|
||||
|
||||
Edit `Caddyfile` and replace `kaboot.example.com` and `auth.example.com` with your actual domains:
|
||||
|
||||
```caddyfile
|
||||
kaboot.example.com {
|
||||
root * /srv/frontend
|
||||
file_server
|
||||
try_files {path} /index.html
|
||||
|
||||
handle /api/* {
|
||||
reverse_proxy kaboot-backend:3001
|
||||
}
|
||||
|
||||
handle /health {
|
||||
reverse_proxy kaboot-backend:3001
|
||||
}
|
||||
}
|
||||
|
||||
auth.example.com {
|
||||
reverse_proxy authentik-server:9000
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Build the Frontend**
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
This creates the `dist/` directory with production assets.
|
||||
|
||||
**Step 3: Start with Caddy**
|
||||
|
||||
Use both compose files together:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.yml -f docker-compose.caddy.yml up -d
|
||||
```
|
||||
|
||||
This will:
|
||||
- Start all Kaboot services (backend, Authentik, PostgreSQL, Redis)
|
||||
- Start Caddy as a reverse proxy on ports 80 and 443
|
||||
- Automatically obtain SSL certificates from Let's Encrypt
|
||||
|
||||
**Step 4: Verify**
|
||||
|
||||
Check that all services are running:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.yml -f docker-compose.caddy.yml ps
|
||||
```
|
||||
|
||||
View Caddy logs:
|
||||
|
||||
```bash
|
||||
docker logs kaboot-caddy
|
||||
```
|
||||
|
||||
**Stopping the Stack**
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.yml -f docker-compose.caddy.yml down
|
||||
```
|
||||
|
||||
### Option 2: Nginx
|
||||
|
||||
Use Nginx as a reverse proxy with manual SSL certificate management.
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name kaboot.example.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/kaboot.example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/kaboot.example.com/privkey.pem;
|
||||
|
||||
location / {
|
||||
root /var/www/kaboot/frontend;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://localhost:3001/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
location /health {
|
||||
proxy_pass http://localhost:3001/health;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name auth.example.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/auth.example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/auth.example.com/privkey.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:9000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Authentik Configuration for Production
|
||||
|
||||
1. **Update Redirect URIs**: In the Authentik Admin interface, go to **Applications** > **Providers** > **Kaboot OAuth2**. Update the **Redirect URIs** to use your production domain:
|
||||
- `https://kaboot.example.com/callback`
|
||||
- `https://kaboot.example.com/silent-renew.html`
|
||||
- `https://kaboot.example.com`
|
||||
|
||||
2. **Email Configuration**: To enable password recovery, configure SMTP settings in Authentik. In the Admin interface, go to **System** > **Settings** and update the Email section.
|
||||
- Host: your SMTP server
|
||||
- Port: 587 or 465
|
||||
- Username/Password: your credentials
|
||||
- Use TLS/SSL: Enabled
|
||||
|
||||
## Database Backup Strategy
|
||||
|
||||
Kaboot uses SQLite, making backups straightforward.
|
||||
|
||||
### SQLite (Kaboot Data)
|
||||
The database file is located in the `kaboot-data` volume at `/data/kaboot.db`. To back it up:
|
||||
```bash
|
||||
docker exec kaboot-backend sqlite3 /data/kaboot.db ".backup '/data/backup_$(date +%F).db'"
|
||||
```
|
||||
Then, copy the backup file from the volume to a secure location.
|
||||
|
||||
### PostgreSQL (Authentik Data)
|
||||
For Authentik's metadata:
|
||||
```bash
|
||||
docker exec kaboot-postgresql pg_dump -U authentik authentik > authentik_backup_$(date +%F).sql
|
||||
```
|
||||
|
||||
## Security Checklist
|
||||
|
||||
- [ ] Change all default passwords (`PG_PASS`, `AUTHENTIK_SECRET_KEY`).
|
||||
- [ ] Ensure `NODE_ENV` is set to `production`.
|
||||
- [ ] Use HTTPS for all connections.
|
||||
- [ ] Set `CORS_ORIGIN` to your specific frontend domain.
|
||||
- [ ] Regularly back up the `kaboot.db` and PostgreSQL data.
|
||||
- [ ] Monitor logs by setting `LOG_REQUESTS=true`.
|
||||
- [ ] Keep Docker images updated to the latest stable versions.
|
||||
|
|
@ -12,6 +12,19 @@ app.use(cors({
|
|||
credentials: true,
|
||||
}));
|
||||
|
||||
const LOG_REQUESTS = process.env.LOG_REQUESTS === 'true';
|
||||
|
||||
app.use((req: Request, res: Response, next: NextFunction) => {
|
||||
if (LOG_REQUESTS && req.path !== '/health') {
|
||||
const start = Date.now();
|
||||
res.on('finish', () => {
|
||||
const duration = Date.now() - start;
|
||||
console.log(`${req.method} ${req.path} ${res.statusCode} ${duration}ms`);
|
||||
});
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
app.use((req: Request, res: Response, next: NextFunction) => {
|
||||
express.json({ limit: '10mb' })(req, res, (err) => {
|
||||
if (err instanceof SyntaxError && 'body' in err) {
|
||||
|
|
@ -27,7 +40,20 @@ app.use((req: Request, res: Response, next: NextFunction) => {
|
|||
});
|
||||
|
||||
app.get('/health', (_req: Request, res: Response) => {
|
||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||
try {
|
||||
db.prepare('SELECT 1').get();
|
||||
res.json({
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString(),
|
||||
database: 'connected'
|
||||
});
|
||||
} catch {
|
||||
res.status(503).json({
|
||||
status: 'error',
|
||||
timestamp: new Date().toISOString(),
|
||||
database: 'disconnected'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.use('/api/quizzes', quizzesRouter);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue