diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c2b8da4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,14 @@ +node_modules +dist +.git +.gitignore +*.md +.env +.env.* +!.env.example +.DS_Store +.idea +.vscode +coverage +uploads +agent-transcripts diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f6a506a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +# syntax=docker/dockerfile:1 + +FROM node:22-alpine AS builder + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN npm ci + +COPY . . +RUN npm run build + +FROM nginx:1.27-alpine + +RUN apk add --no-cache nodejs + +COPY --from=builder /app/dist /usr/share/nginx/html +COPY scripts/generate-config.js /docker/generate-config.js +COPY docker/nginx.conf /etc/nginx/conf.d/default.conf +COPY docker/entrypoint.sh /docker/entrypoint.sh + +RUN chmod +x /docker/entrypoint.sh \ + && sed -i 's/\r$//' /docker/entrypoint.sh + +EXPOSE 80 + +ENTRYPOINT ["/docker/entrypoint.sh"] diff --git a/README.md b/README.md index ac3fb8d..fcb8c66 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,31 @@ npm run lint Copy `.env.example` to `.env` and set branding / theme variables. `npm run serve` / `npm run build` regenerates `public/config.js` automatically. +### Docker +Build and run (config via environment, same keys as `.env.example`): + +``` +docker compose up --build +``` + +Open http://localhost:8080 + +Change branding without rebuilding the image — only restart with new env: + +``` +docker compose up -d +``` + +Or: + +``` +docker build -t irandom . +docker run --rm -p 8080:80 ^ + -e VUE_APP_NAME=LuckyPenguin ^ + -e VUE_APP_COLOR_PRIMARY=#3DDC97 ^ + irandom +``` + ### Offline (PWA) Production build registers a service worker and caches the app shell. After opening the site once online: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..184b517 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,26 @@ +services: + irandom: + build: . + image: irandom:latest + ports: + - "8080:80" + environment: + VUE_APP_NAME: irandom + VUE_APP_TITLE: irandom + VUE_APP_BRAND_URL: https://git.imiheev.online/admin/irandom + VUE_APP_LOGO_URL: /logo.png + VUE_APP_DEFAULT_LOCALE: ru + VUE_APP_COLOR_PRIMARY: "#c9a66b" + VUE_APP_COLOR_ACCENT: "#e6c566" + VUE_APP_COLOR_BG: "#1a1815" + VUE_APP_COLOR_SURFACE: "#2e2820" + VUE_APP_COLOR_SURFACE_RAISED: "#3a3228" + VUE_APP_COLOR_TEXT: "#ffffff" + VUE_APP_COLOR_TEXT_MUTED: "#c8c0b4" + VUE_APP_COLOR_DANGER: "#ee5c5c" + VUE_APP_COLOR_LINK: "#4a6a8a" + VUE_APP_COLOR_BORDER: "#4a4034" + # Or use a file: + # env_file: + # - .env + restart: unless-stopped diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..969e016 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -e + +export CONFIG_OUT="${CONFIG_OUT:-/usr/share/nginx/html/config.js}" + +echo "Generating runtime config at ${CONFIG_OUT}..." +node /docker/generate-config.js + +exec nginx -g 'daemon off;' diff --git a/docker/nginx.conf b/docker/nginx.conf new file mode 100644 index 0000000..bbfd2db --- /dev/null +++ b/docker/nginx.conf @@ -0,0 +1,32 @@ +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + # SPA history mode + location / { + try_files $uri $uri/ /index.html; + } + + # Runtime config — always revalidate so env changes apply after restart + location = /config.js { + add_header Cache-Control "no-cache, no-store, must-revalidate"; + try_files $uri =404; + } + + # Hashed assets — long cache + location /assets/ { + add_header Cache-Control "public, max-age=31536000, immutable"; + try_files $uri =404; + } + + # Service worker / workbox — always revalidate + location ~* ^/(sw\.js|workbox-.*\.js)$ { + add_header Cache-Control "no-cache"; + try_files $uri =404; + } + + gzip on; + gzip_types text/css application/javascript application/json image/svg+xml; +} diff --git a/scripts/generate-config.js b/scripts/generate-config.js index 4512e35..b1af078 100644 --- a/scripts/generate-config.js +++ b/scripts/generate-config.js @@ -3,7 +3,8 @@ const path = require('path') const ROOT = path.resolve(__dirname, '..') const ENV_PATH = path.join(ROOT, '.env') -const OUT_PATH = path.join(ROOT, 'public', 'config.js') +const OUT_PATH = + process.env.CONFIG_OUT || path.join(ROOT, 'public', 'config.js') const DEFAULTS = { VUE_APP_NAME: 'irandom', @@ -56,8 +57,9 @@ function parseEnvFile(filePath) { return result } -function buildConfig(env) { - const pick = (key) => env[key] || process.env[key] || DEFAULTS[key] +function buildConfig(fileEnv) { + // Container / shell env overrides .env file + const pick = (key) => process.env[key] || fileEnv[key] || DEFAULTS[key] return { name: pick('VUE_APP_NAME'), @@ -88,5 +90,6 @@ const output = `/* Generated by scripts/generate-config.js — do not edit by ha window.__APP_CONFIG__ = ${payload}; ` +fs.mkdirSync(path.dirname(OUT_PATH), { recursive: true }) fs.writeFileSync(OUT_PATH, output, 'utf8') -console.log(`Wrote ${path.relative(ROOT, OUT_PATH)}`) +console.log(`Wrote ${OUT_PATH}`)