diff --git a/DOCKER.md b/DOCKER.md index ce7ecc24..26e6ffc6 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -16,6 +16,68 @@ docker run -d \ Open http://localhost in your browser. +## Whitelabel / Custom Branding + +Build your own branded image with custom name, logo, and colors: + +```bash +docker build \ + --build-arg BRAND_NAME="My Prompt Library" \ + --build-arg BRAND_DESCRIPTION="Our team's AI prompts" \ + --build-arg BRAND_COLOR="#ff6600" \ + --build-arg AUTH_PROVIDERS="github,google" \ + --build-arg LOCALES="en,es,fr" \ + -f docker/Dockerfile \ + -t my-prompts . + +docker run -p 80:80 -v prompts-data:/data my-prompts +``` + +### Build Arguments + +| Argument | Description | Default | +|----------|-------------|---------| +| `BRAND_NAME` | App name shown in UI | `My Prompt Library` | +| `BRAND_DESCRIPTION` | App description | `Collect, organize, and share AI prompts` | +| `BRAND_LOGO` | Logo path (in public/) | `/logo.svg` | +| `BRAND_LOGO_DARK` | Dark mode logo | Same as `BRAND_LOGO` | +| `BRAND_FAVICON` | Favicon path | `/logo.svg` | +| `BRAND_COLOR` | Primary color (hex) | `#6366f1` | +| `THEME_RADIUS` | Border radius: `none\|sm\|md\|lg` | `sm` | +| `THEME_VARIANT` | UI style: `default\|flat\|brutal` | `default` | +| `THEME_DENSITY` | Spacing: `compact\|default\|comfortable` | `default` | +| `AUTH_PROVIDERS` | Auth providers (comma-separated) | `credentials` | +| `ALLOW_REGISTRATION` | Allow public signup | `true` | +| `LOCALES` | Supported locales (comma-separated) | `en` | +| `DEFAULT_LOCALE` | Default locale | `en` | +| `FEATURE_PRIVATE_PROMPTS` | Enable private prompts | `true` | +| `FEATURE_CHANGE_REQUESTS` | Enable versioning | `true` | +| `FEATURE_CATEGORIES` | Enable categories | `true` | +| `FEATURE_TAGS` | Enable tags | `true` | +| `FEATURE_COMMENTS` | Enable comments | `true` | +| `FEATURE_AI_SEARCH` | Enable AI search | `false` | +| `FEATURE_AI_GENERATION` | Enable AI generation | `false` | +| `FEATURE_MCP` | Enable MCP features | `false` | + +### Adding Custom Logo + +1. Create your logo file (SVG recommended) +2. Mount it when running: + +```bash +docker run -p 80:80 \ + -v ./my-logo.svg:/app/public/logo.svg \ + -v prompts-data:/data \ + my-prompts +``` + +Or include it in your own Dockerfile: + +```dockerfile +FROM ghcr.io/f/prompts.chat +COPY my-logo.svg /app/public/logo.svg +``` + ## Configuration ### Environment Variables diff --git a/docker/Dockerfile b/docker/Dockerfile index dc5f2afa..862f8072 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -2,12 +2,40 @@ # Contains Node.js + PostgreSQL for single-container deployment # # Usage: -# docker run -p 80:80 ghcr.io/f/prompts.chat -# docker run -p 80:80 -e AUTH_SECRET=xxx ghcr.io/f/prompts.chat -# docker run -p 80:80 --name my-prompts ghcr.io/f/prompts.chat +# docker build -t my-prompts . +# docker build --build-arg BRAND_NAME="My App" --build-arg BRAND_COLOR="#ff0000" -t my-prompts . +# docker run -p 80:80 my-prompts +# +# Build Arguments (all optional): +# BRAND_NAME, BRAND_DESCRIPTION, BRAND_LOGO, BRAND_COLOR +# AUTH_PROVIDERS (comma-separated: github,google,credentials) +# LOCALES (comma-separated: en,es,fr) FROM node:24-bookworm-slim AS builder +# Build arguments for whitelabel branding +ARG BRAND_NAME="My Prompt Library" +ARG BRAND_DESCRIPTION="Collect, organize, and share AI prompts" +ARG BRAND_LOGO="/logo.svg" +ARG BRAND_LOGO_DARK="" +ARG BRAND_FAVICON="/logo.svg" +ARG BRAND_COLOR="#6366f1" +ARG THEME_RADIUS="sm" +ARG THEME_VARIANT="default" +ARG THEME_DENSITY="default" +ARG AUTH_PROVIDERS="credentials" +ARG ALLOW_REGISTRATION="true" +ARG LOCALES="en" +ARG DEFAULT_LOCALE="en" +ARG FEATURE_PRIVATE_PROMPTS="true" +ARG FEATURE_CHANGE_REQUESTS="true" +ARG FEATURE_CATEGORIES="true" +ARG FEATURE_TAGS="true" +ARG FEATURE_COMMENTS="true" +ARG FEATURE_AI_SEARCH="false" +ARG FEATURE_AI_GENERATION="false" +ARG FEATURE_MCP="false" + WORKDIR /app # Copy package files first for better caching @@ -23,6 +51,31 @@ COPY . . # Remove unnecessary files RUN rm -rf .github .claude packages .git +# Run docker-setup.js to generate prompts.config.ts with branding +ENV BRAND_NAME=${BRAND_NAME} +ENV BRAND_DESCRIPTION=${BRAND_DESCRIPTION} +ENV BRAND_LOGO=${BRAND_LOGO} +ENV BRAND_LOGO_DARK=${BRAND_LOGO_DARK} +ENV BRAND_FAVICON=${BRAND_FAVICON} +ENV BRAND_COLOR=${BRAND_COLOR} +ENV THEME_RADIUS=${THEME_RADIUS} +ENV THEME_VARIANT=${THEME_VARIANT} +ENV THEME_DENSITY=${THEME_DENSITY} +ENV AUTH_PROVIDERS=${AUTH_PROVIDERS} +ENV ALLOW_REGISTRATION=${ALLOW_REGISTRATION} +ENV LOCALES=${LOCALES} +ENV DEFAULT_LOCALE=${DEFAULT_LOCALE} +ENV FEATURE_PRIVATE_PROMPTS=${FEATURE_PRIVATE_PROMPTS} +ENV FEATURE_CHANGE_REQUESTS=${FEATURE_CHANGE_REQUESTS} +ENV FEATURE_CATEGORIES=${FEATURE_CATEGORIES} +ENV FEATURE_TAGS=${FEATURE_TAGS} +ENV FEATURE_COMMENTS=${FEATURE_COMMENTS} +ENV FEATURE_AI_SEARCH=${FEATURE_AI_SEARCH} +ENV FEATURE_AI_GENERATION=${FEATURE_AI_GENERATION} +ENV FEATURE_MCP=${FEATURE_MCP} + +RUN node scripts/docker-setup.js + # Generate Prisma client and build # DATABASE_URL is needed at build time for Prisma, but not used ENV NEXT_TELEMETRY_DISABLED=1 diff --git a/docker/Dockerfile.app b/docker/Dockerfile.app index be73b580..a2ab2b9a 100644 --- a/docker/Dockerfile.app +++ b/docker/Dockerfile.app @@ -1,5 +1,8 @@ # prompts.chat App-Only Dockerfile # For use with docker-compose (separate database container) +# +# Build with branding: +# docker build --build-arg BRAND_NAME="My App" -f docker/Dockerfile.app -t my-prompts . FROM node:24-alpine AS base @@ -17,6 +20,19 @@ RUN npm ci # Rebuild the source code only when needed FROM base AS builder + +# Build arguments for whitelabel branding +ARG BRAND_NAME="My Prompt Library" +ARG BRAND_DESCRIPTION="Collect, organize, and share AI prompts" +ARG BRAND_LOGO="/logo.svg" +ARG BRAND_LOGO_DARK="" +ARG BRAND_FAVICON="/logo.svg" +ARG BRAND_COLOR="#6366f1" +ARG THEME_RADIUS="sm" +ARG THEME_VARIANT="default" +ARG AUTH_PROVIDERS="credentials" +ARG LOCALES="en" + WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . @@ -24,6 +40,20 @@ COPY . . # Remove unnecessary files RUN rm -rf .github .claude packages docker .git +# Run docker-setup.js to generate prompts.config.ts with branding +ENV BRAND_NAME=${BRAND_NAME} \ + BRAND_DESCRIPTION=${BRAND_DESCRIPTION} \ + BRAND_LOGO=${BRAND_LOGO} \ + BRAND_LOGO_DARK=${BRAND_LOGO_DARK} \ + BRAND_FAVICON=${BRAND_FAVICON} \ + BRAND_COLOR=${BRAND_COLOR} \ + THEME_RADIUS=${THEME_RADIUS} \ + THEME_VARIANT=${THEME_VARIANT} \ + AUTH_PROVIDERS=${AUTH_PROVIDERS} \ + LOCALES=${LOCALES} + +RUN node scripts/docker-setup.js + # DATABASE_URL is needed at build time for Prisma, but not used ENV NEXT_TELEMETRY_DISABLED=1 ENV DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy" diff --git a/scripts/docker-setup.js b/scripts/docker-setup.js new file mode 100644 index 00000000..49166314 --- /dev/null +++ b/scripts/docker-setup.js @@ -0,0 +1,172 @@ +#!/usr/bin/env node +/* eslint-disable @typescript-eslint/no-require-imports */ + +/** + * Non-interactive setup script for Docker builds. + * Generates prompts.config.ts from environment variables. + * + * Usage: BRAND_NAME="My App" node scripts/docker-setup.js + * + * Environment Variables: + * BRAND_NAME - App name (default: "My Prompt Library") + * BRAND_DESCRIPTION - App description + * BRAND_LOGO - Logo path (default: "/logo.svg") + * BRAND_LOGO_DARK - Dark mode logo path + * BRAND_FAVICON - Favicon path + * BRAND_COLOR - Primary color hex (default: "#6366f1") + * THEME_RADIUS - Border radius: none|sm|md|lg (default: "sm") + * THEME_VARIANT - UI variant: default|flat|brutal (default: "default") + * THEME_DENSITY - Spacing: compact|default|comfortable (default: "default") + * AUTH_PROVIDERS - Comma-separated: github,google,credentials (default: "credentials") + * ALLOW_REGISTRATION - Allow public registration: true|false (default: "true") + * LOCALES - Comma-separated locales (default: "en") + * DEFAULT_LOCALE - Default locale (default: "en") + * FEATURE_PRIVATE_PROMPTS - Enable private prompts (default: "true") + * FEATURE_CHANGE_REQUESTS - Enable change requests (default: "true") + * FEATURE_CATEGORIES - Enable categories (default: "true") + * FEATURE_TAGS - Enable tags (default: "true") + * FEATURE_COMMENTS - Enable comments (default: "true") + * FEATURE_AI_SEARCH - Enable AI search (default: "false") + * FEATURE_AI_GENERATION - Enable AI generation (default: "false") + * FEATURE_MCP - Enable MCP (default: "false") + */ + +const fs = require('fs'); +const path = require('path'); + +const CONFIG_FILE = path.join(__dirname, '..', 'prompts.config.ts'); + +function env(key, defaultValue) { + return process.env[key] || defaultValue; +} + +function envBool(key, defaultValue) { + const val = process.env[key]; + if (val === undefined) return defaultValue; + return val.toLowerCase() === 'true' || val === '1'; +} + +function envArray(key, defaultValue) { + const val = process.env[key]; + if (!val) return defaultValue; + return val.split(',').map(s => s.trim()).filter(Boolean); +} + +function generateConfig(config) { + return `import { defineConfig } from "@/lib/config"; + +// Docker build configuration - generated by docker-setup.js +const useCloneBranding = true; + +export default defineConfig({ + // Branding - your organization's identity + branding: { + name: "${config.branding.name}", + logo: "${config.branding.logo}", + logoDark: "${config.branding.logoDark}", + favicon: "${config.branding.favicon}", + description: "${config.branding.description}", + }, + + // Theme - design system configuration + theme: { + radius: "${config.theme.radius}", + variant: "${config.theme.variant}", + density: "${config.theme.density}", + colors: { + primary: "${config.theme.primaryColor}", + }, + }, + + // Authentication plugins + auth: { + providers: [${config.auth.providers.map(p => `"${p}"`).join(', ')}], + allowRegistration: ${config.auth.allowRegistration}, + }, + + // Internationalization + i18n: { + locales: [${config.i18n.locales.map(l => `"${l}"`).join(', ')}], + defaultLocale: "${config.i18n.defaultLocale}", + }, + + // Features + features: { + privatePrompts: ${config.features.privatePrompts}, + changeRequests: ${config.features.changeRequests}, + categories: ${config.features.categories}, + tags: ${config.features.tags}, + comments: ${config.features.comments}, + aiSearch: ${config.features.aiSearch}, + aiGeneration: ${config.features.aiGeneration}, + mcp: ${config.features.mcp}, + }, + + // Homepage customization (clone branding mode) + homepage: { + useCloneBranding, + achievements: { + enabled: false, + }, + sponsors: { + enabled: false, + items: [], + }, + }, +}); +`; +} + +function main() { + console.log('🔧 Docker Setup: Generating prompts.config.ts...'); + + const config = { + branding: { + name: env('BRAND_NAME', 'My Prompt Library'), + description: env('BRAND_DESCRIPTION', 'Collect, organize, and share AI prompts'), + logo: env('BRAND_LOGO', '/logo.svg'), + logoDark: env('BRAND_LOGO_DARK', env('BRAND_LOGO', '/logo.svg')), + favicon: env('BRAND_FAVICON', '/logo.svg'), + }, + theme: { + primaryColor: env('BRAND_COLOR', '#6366f1'), + radius: env('THEME_RADIUS', 'sm'), + variant: env('THEME_VARIANT', 'default'), + density: env('THEME_DENSITY', 'default'), + }, + auth: { + providers: envArray('AUTH_PROVIDERS', ['credentials']), + allowRegistration: envBool('ALLOW_REGISTRATION', true), + }, + i18n: { + locales: envArray('LOCALES', ['en']), + defaultLocale: env('DEFAULT_LOCALE', 'en'), + }, + features: { + privatePrompts: envBool('FEATURE_PRIVATE_PROMPTS', true), + changeRequests: envBool('FEATURE_CHANGE_REQUESTS', true), + categories: envBool('FEATURE_CATEGORIES', true), + tags: envBool('FEATURE_TAGS', true), + comments: envBool('FEATURE_COMMENTS', true), + aiSearch: envBool('FEATURE_AI_SEARCH', false), + aiGeneration: envBool('FEATURE_AI_GENERATION', false), + mcp: envBool('FEATURE_MCP', false), + }, + }; + + // Log configuration + console.log(''); + console.log(' Brand: ', config.branding.name); + console.log(' Color: ', config.theme.primaryColor); + console.log(' Auth: ', config.auth.providers.join(', ')); + console.log(' Locales: ', config.i18n.locales.join(', ')); + console.log(''); + + // Generate and write config + const content = generateConfig(config); + fs.writeFileSync(CONFIG_FILE, content); + + console.log('✅ Generated prompts.config.ts'); +} + +main();