From 8242687b0745e43a689047b7c3d5a529e12f377f Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Wed, 28 Jan 2026 00:34:40 +0100 Subject: [PATCH] env --- .env | 16 ++++------------ .gitea/workflows/deploy.yml | 10 ++++++---- Dockerfile | 3 +-- docker-compose.override.yml | 4 ++++ docker-compose.yml | 17 ++++++----------- docs/DEPLOYMENT.md | 29 ++++++++++++++++++++++------ lib/config.ts | 38 +++++++++++++++++++++++-------------- next.config.mjs | 2 +- 8 files changed, 69 insertions(+), 50 deletions(-) create mode 100644 docker-compose.override.yml diff --git a/.env b/.env index 7c4b0e61..6cc5c955 100644 --- a/.env +++ b/.env @@ -1,11 +1,10 @@ # Application NODE_ENV=production NEXT_PUBLIC_BASE_URL=https://klz-cables.com +UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js +NEXT_PUBLIC_UMAMI_WEBSITE_ID=59a7db94-0100-4c7e-98ef-99f45b17f9c3 +SENTRY_DSN=https://c10957d0182245b1a2a806ac2d34a197@errors.infra.mintel.me/1 LOG_LEVEL=info -PDF_DEBUG_EXCEL=0 -PDF_LOCALE= -PDF_MATCH= -PDF_LIMIT=0 # WooCommerce & WordPress WOOCOMMERCE_URL=https://klz-cables.com @@ -13,15 +12,8 @@ WOOCOMMERCE_CONSUMER_KEY=ck_38d97df86880e8fefbd54ab5cdf47a9c5a9e5b39 WOOCOMMERCE_CONSUMER_SECRET=cs_d675ee2ac2ec7c22de84ae5451c07e42b1717759 WORDPRESS_APP_PASSWORD="DlJH 49dp fC3a Itc3 Sl7Z Wz0k" -# Umami Analytics -NEXT_PUBLIC_UMAMI_WEBSITE_ID=59a7db94-0100-4c7e-98ef-99f45b17f9c3 -UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js - -# GlitchTip (Sentry protocol) -SENTRY_DSN=https://c10957d0182245b1a2a806ac2d34a197@errors.infra.mintel.me/1 - # Redis Cache -REDIS_URL= +REDIS_URL=redis://redis:6379/2 REDIS_KEY_PREFIX=klz: # SMTP Configuration diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 5c7c7fa2..b0326a7e 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -74,8 +74,8 @@ jobs: echo "" echo "📦 Build Arguments:" echo " • NEXT_PUBLIC_UMAMI_WEBSITE_ID: ${{ secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID != '' && '***' || 'NOT SET' }}" + echo " • NEXT_PUBLIC_UMAMI_SCRIPT_URL: ${{ secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL != '' && '***' || 'NOT SET' }}" echo " • SENTRY_DSN: ${{ secrets.SENTRY_DSN != '' && '***' || 'NOT SET' }}" - echo " • NEXT_PUBLIC_SENTRY_DSN: ${{ secrets.NEXT_PUBLIC_SENTRY_DSN != '' && '***' || 'NOT SET' }}" echo " • NEXT_PUBLIC_BASE_URL: ${{ secrets.NEXT_PUBLIC_BASE_URL != '' && '***' || 'NOT SET' }}" echo "" echo "⏱️ Build started at: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" @@ -87,8 +87,8 @@ jobs: --pull \ --platform linux/arm64 \ --build-arg NEXT_PUBLIC_UMAMI_WEBSITE_ID="${{ secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID }}" \ + --build-arg NEXT_PUBLIC_UMAMI_SCRIPT_URL="${{ secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL }}" \ --build-arg SENTRY_DSN="${{ secrets.SENTRY_DSN }}" \ - --build-arg NEXT_PUBLIC_SENTRY_DSN="${{ secrets.NEXT_PUBLIC_SENTRY_DSN }}" \ --build-arg NEXT_PUBLIC_BASE_URL="${{ secrets.NEXT_PUBLIC_BASE_URL }}" \ -t registry.infra.mintel.me/mintel/klz-cables.com:latest \ --push . @@ -160,11 +160,13 @@ jobs: MAIL_RECIPIENTS='${{ secrets.MAIL_RECIPIENTS }}' \ MAIL_USERNAME='${{ secrets.MAIL_USERNAME }}' \ NEXT_PUBLIC_BASE_URL='${{ secrets.NEXT_PUBLIC_BASE_URL }}' \ - NEXT_PUBLIC_SENTRY_DSN='${{ secrets.NEXT_PUBLIC_SENTRY_DSN }}' \ NEXT_PUBLIC_UMAMI_WEBSITE_ID='${{ secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID }}' \ + NEXT_PUBLIC_UMAMI_SCRIPT_URL='${{ secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL }}' \ NODE_ENV='${{ secrets.NODE_ENV }}' \ SENTRY_DSN='${{ secrets.SENTRY_DSN }}' \ - sudo -u deploy -H -E /home/deploy/deploy.sh" + REDIS_URL='${{ secrets.REDIS_URL }}' \ + REDIS_KEY_PREFIX='${{ secrets.REDIS_KEY_PREFIX }}' \ + /home/deploy/deploy.sh" DEPLOY_EXIT_CODE=$? echo "" diff --git a/Dockerfile b/Dockerfile index 6565435c..6bf6fb22 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,12 +25,11 @@ ENV NEXT_TELEMETRY_DISABLED=1 ARG NEXT_PUBLIC_UMAMI_WEBSITE_ID ARG NEXT_PUBLIC_UMAMI_SCRIPT_URL ARG SENTRY_DSN -ARG NEXT_PUBLIC_SENTRY_DSN ARG NEXT_PUBLIC_BASE_URL + ENV NEXT_PUBLIC_UMAMI_WEBSITE_ID=$NEXT_PUBLIC_UMAMI_WEBSITE_ID ENV NEXT_PUBLIC_UMAMI_SCRIPT_URL=$NEXT_PUBLIC_UMAMI_SCRIPT_URL ENV SENTRY_DSN=$SENTRY_DSN -ENV NEXT_PUBLIC_SENTRY_DSN=$NEXT_PUBLIC_SENTRY_DSN ENV NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL RUN npm run build diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 00000000..47896880 --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,4 @@ +services: + app: + env_file: + - .env diff --git a/docker-compose.yml b/docker-compose.yml index 80663e2a..65e03646 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -57,24 +57,19 @@ services: retries: 5 start_period: 30s environment: - # Umami + - NODE_ENV=${NODE_ENV:-production} + - NEXT_PUBLIC_BASE_URL=${NEXT_PUBLIC_BASE_URL:?NEXT_PUBLIC_BASE_URL is required} - NEXT_PUBLIC_UMAMI_WEBSITE_ID=${NEXT_PUBLIC_UMAMI_WEBSITE_ID} - - NEXT_PUBLIC_UMAMI_SCRIPT_URL=${NEXT_PUBLIC_UMAMI_SCRIPT_URL:-https://analytics.infra.mintel.me/script.js} - # GlitchTip (Sentry protocol) + - NEXT_PUBLIC_UMAMI_SCRIPT_URL=${NEXT_PUBLIC_UMAMI_SCRIPT_URL} - SENTRY_DSN=${SENTRY_DSN} - - NEXT_PUBLIC_SENTRY_DSN=${NEXT_PUBLIC_SENTRY_DSN} - # Redis (app-spezifischer DB-Index) - - REDIS_URL=${REDIS_URL:-redis://redis:6379/2} - - REDIS_KEY_PREFIX=${REDIS_KEY_PREFIX:-klz:} - # Mail - MAIL_HOST=${MAIL_HOST} - - MAIL_PORT=${MAIL_PORT} + - MAIL_PORT=${MAIL_PORT:-587} - MAIL_USERNAME=${MAIL_USERNAME} - MAIL_PASSWORD=${MAIL_PASSWORD} - MAIL_FROM=${MAIL_FROM} - MAIL_RECIPIENTS=${MAIL_RECIPIENTS} - # App - - NEXT_PUBLIC_BASE_URL=${NEXT_PUBLIC_BASE_URL} + - REDIS_URL=${REDIS_URL} + - REDIS_KEY_PREFIX=${REDIS_KEY_PREFIX} networks: infra: diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index be6059c4..14dcd6b4 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -108,18 +108,35 @@ Next.js App (port 3000) - `staging.klz-cables.com` - Staging ## Environment Variables - + ### Build-time (in Dockerfile/Workflow) - + +These variables are baked into the Docker image during the build process. + +- `NEXT_PUBLIC_BASE_URL` (Required) - `NEXT_PUBLIC_UMAMI_WEBSITE_ID` - `NEXT_PUBLIC_UMAMI_SCRIPT_URL` -- `NEXT_PUBLIC_SENTRY_DSN` - -### Runtime (in docker-compose.yml) - - `SENTRY_DSN` + +### Runtime (in docker-compose.yml) + +These variables are passed to the container at runtime. + +- `NODE_ENV` (Defaults to `production`) +- `NEXT_PUBLIC_BASE_URL` (Must be passed again at runtime for server-side use) +- `SENTRY_DSN` +- `MAIL_HOST` +- `MAIL_PORT` +- `MAIL_USERNAME` +- `MAIL_PASSWORD` +- `MAIL_FROM` +- `MAIL_RECIPIENTS` - `REDIS_URL` - `REDIS_KEY_PREFIX` + +### Local Development + +For local development, use a `.env` file. The `docker-compose.override.yml` file automatically loads this file for the `app` service. ## Monitoring diff --git a/lib/config.ts b/lib/config.ts index 3db7f0db..a98a281b 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -12,20 +12,40 @@ const getEnv = (key: string, defaultValue?: string): string | undefined => { return (process.env as any)[key] || defaultValue; } - if (typeof process === 'undefined') return defaultValue; if (typeof process === 'undefined') return defaultValue; // In Docker/Production, variables are in process.env // In local development, they might be in .env const value = process.env[key]; - if (value !== undefined && value !== '') { - return value; + // Check for quoted values (common when passed via SSH/Docker) + if (typeof value === 'string') { + const trimmed = value.trim(); + if ((trimmed.startsWith("'") && trimmed.endsWith("'")) || + (trimmed.startsWith('"') && trimmed.endsWith('"'))) { + return trimmed.slice(1, -1); + } + if (trimmed !== '') return trimmed; } return defaultValue; }; +const isProduction = getEnv('NODE_ENV') === 'production'; + +// Required variables in production +if (isProduction && typeof window === 'undefined') { + const required = [ + 'NEXT_PUBLIC_BASE_URL', + ]; + + for (const key of required) { + if (!getEnv(key)) { + throw new Error(`Missing required environment variable: ${key}`); + } + } +} + export const config = { env: getEnv('NODE_ENV', 'development'), isProduction: getEnv('NODE_ENV') === 'production', @@ -37,7 +57,7 @@ export const config = { analytics: { umami: { websiteId: getEnv('NEXT_PUBLIC_UMAMI_WEBSITE_ID'), - scriptUrl: getEnv('UMAMI_SCRIPT_URL', 'https://analytics.infra.mintel.me/script.js'), + scriptUrl: getEnv('NEXT_PUBLIC_UMAMI_SCRIPT_URL', 'https://analytics.infra.mintel.me/script.js'), // The proxied path used in the frontend proxyPath: '/stats/script.js', enabled: Boolean(getEnv('NEXT_PUBLIC_UMAMI_WEBSITE_ID')), @@ -74,16 +94,6 @@ export const config = { from: getEnv('MAIL_FROM'), recipients: getEnv('MAIL_RECIPIENTS', '')?.split(',').filter(Boolean) || [], }, - - woocommerce: { - url: getEnv('WOOCOMMERCE_URL'), - consumerKey: getEnv('WOOCOMMERCE_CONSUMER_KEY'), - consumerSecret: getEnv('WOOCOMMERCE_CONSUMER_SECRET'), - }, - - wordpress: { - appPassword: getEnv('WORDPRESS_APP_PASSWORD'), - }, } as const; /** diff --git a/next.config.mjs b/next.config.mjs index 61f917d7..63a140f5 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -322,7 +322,7 @@ const nextConfig = { contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;", }, async rewrites() { - const umamiUrl = (process.env.UMAMI_SCRIPT_URL || 'https://analytics.infra.mintel.me').replace('/script.js', ''); + const umamiUrl = (process.env.NEXT_PUBLIC_UMAMI_SCRIPT_URL || 'https://analytics.infra.mintel.me').replace('/script.js', ''); const glitchtipUrl = process.env.SENTRY_DSN ? new URL(process.env.SENTRY_DSN).origin : 'https://errors.infra.mintel.me';