From 43f982bd5a0840b4d6e244bbfa732e1dedfd887d Mon Sep 17 00:00:00 2001 From: einhornimmond Date: Sat, 18 Jan 2025 17:55:01 +0100 Subject: [PATCH] fix it for docker setup --- config/common.schema.js | 46 ++++--- deployment/bare_metal/.env.dist | 2 +- docker-compose.override.yml | 2 +- docker-compose.yml | 4 +- frontend/.env.dist | 2 +- frontend/Dockerfile | 2 +- frontend/src/config/index.js | 34 +---- frontend/src/config/schema.js | 52 ++++---- frontend/vite.config.js | 224 +++++++++++++++++++------------- 9 files changed, 203 insertions(+), 165 deletions(-) diff --git a/config/common.schema.js b/config/common.schema.js index 601748849..13e1b25f7 100644 --- a/config/common.schema.js +++ b/config/common.schema.js @@ -1,37 +1,45 @@ const Joi = require('joi') module.exports = { - BROWSER_PROTOCOL: Joi.string() - .valid('http', 'https') - .description( - 'Protocol for all URLs in the browser, must be either http or https to prevent mixed content issues.', - ) - .default('http') + browserUrls: Joi.array() + .items(Joi.string().uri()) + .custom((value, helpers) => { + const protocol = new URL(value[0]).protocol + for (const url of value) { + if (new URL(url).protocol !== protocol) { + return helpers.error('any.invalid') + } + } + return value; + }) + .required() + .description('All URLs need to have same protocol to prevent mixed block errors'), + + DECAY_START_TIME: Joi.date() + .iso() // ISO 8601 format for date validation + .description('The start time for decay, expected in ISO 8601 format (e.g. 2021-05-13T17:46:31Z)') + .default(new Date('2021-05-13T17:46:31Z')) // default to the specified date if not provided .required(), COMMUNITY_URL: Joi.string() .uri({ scheme: ['http', 'https'] }) - .when('BROWSER_PROTOCOL', { - is: 'https', - then: Joi.string().uri({ scheme: 'https' }), - otherwise: Joi.string().uri({ scheme: 'http' }), + .custom((value, helpers) => { + if (value.endsWith('/')) { + return helpers.error('any.invalid', { message: 'URL should not end with a slash (/)' }) + } + return value; }) - .description('The base URL of the community, should have the same scheme like frontend, admin and backend api to prevent mixed contend issues.') + .description('The base URL of the community, should have the same protocol as frontend, admin and backend api to prevent mixed contend issues.') .default('http://0.0.0.0') .required(), GRAPHQL_URI: Joi.string() .uri({ scheme: ['http', 'https'] }) - .when('BROWSER_PROTOCOL', { - is: 'https', - then: Joi.string().uri({ scheme: 'https' }), - otherwise: Joi.string().uri({ scheme: 'http' }), - }) .description( ` The external URL of the backend service, accessible from outside the server (e.g., via Nginx or the server's public URL), - must use the same protocol as browser_protocol. + should have the same protocol as frontend and admin to prevent mixed contend issues. `, ) .default('http://0.0.0.0/graphql') @@ -67,7 +75,7 @@ module.exports = { .description('Geographical location of the community in "latitude, longitude" format') .default('49.280377, 9.690151'), - GRAPHIQL: Joi.boolean() + GRAPHIQL: Joi.boolean() .description('Flag for enabling GraphQL playground for debugging.') .default(false) .when('NODE_ENV', { @@ -163,7 +171,7 @@ module.exports = { NODE_ENV: Joi.string() .valid('production', 'development') .default('development') - .description('Specifies the environment in which the application is running, "production" or "development".'), + .description('Specifies the environment in which the application is running.'), DEBUG: Joi.boolean() .description('Indicates whether the application is in debugging mode. Set to true when NODE_ENV is not "production".') diff --git a/deployment/bare_metal/.env.dist b/deployment/bare_metal/.env.dist index e88d0f1ff..252b28ff8 100644 --- a/deployment/bare_metal/.env.dist +++ b/deployment/bare_metal/.env.dist @@ -63,7 +63,7 @@ EMAIL_LINK_VERIFICATION_PATH=/checkEmail/{optin}{code} EMAIL_LINK_SETPASSWORD_PATH=/reset-password/{optin} EMAIL_LINK_FORGOTPASSWORD_PATH=/forgot-password EMAIL_LINK_OVERVIEW_PATH=/overview -ADMIN_AUTH_PATH=/admin/authenticate?token={token} +ADMIN_AUTH_PATH=/admin/authenticate?token= GRAPHQL_PATH=/graphql # login expire time diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 4807a6bbf..3f967f229 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -9,7 +9,7 @@ services: build: target: development environment: - - NODE_ENV="development" + - NODE_ENV=development # - DEBUG=true volumes: # This makes sure the docker container has its own node modules. diff --git a/docker-compose.yml b/docker-compose.yml index eaf828aab..0ab610f48 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,7 +26,9 @@ services: # - BUILD_DATE="1970-01-01T00:00:00.00Z" # - BUILD_VERSION="0.0.0.0" # - BUILD_COMMIT="0000000" - - NODE_ENV="production" + - NODE_ENV=production + volumes: + - ./config:/config # env_file: # - ./.env # - ./frontend/.env diff --git a/frontend/.env.dist b/frontend/.env.dist index 51a52b3a4..817bbc5de 100644 --- a/frontend/.env.dist +++ b/frontend/.env.dist @@ -1,6 +1,6 @@ # Endpoints GRAPHQL_PATH=/graphql -ADMIN_AUTH_PATH=/admin/authenticate?token={token} +ADMIN_AUTH_PATH=/admin/authenticate?token= # Community COMMUNITY_NAME=Gradido Entwicklung diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 365906941..04dceb6f5 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -11,7 +11,7 @@ ENV BUILD_DATE="1970-01-01T00:00:00.00Z" ## We cannot do $(npm run version).${BUILD_NUMBER} here so we default to 0.0.0.0 ENV BUILD_VERSION="0.0.0.0" ## We cannot do `$(git rev-parse --short HEAD)` here so we default to 0000000 -ENV BUILD_COMMIT="0000000" +ENV BUILD_COMMIT_SHORT="0000000" ## SET NODE_ENV ENV NODE_ENV="production" ## App relevant Envs diff --git a/frontend/src/config/index.js b/frontend/src/config/index.js index a161f10e5..2e469a33c 100644 --- a/frontend/src/config/index.js +++ b/frontend/src/config/index.js @@ -3,15 +3,12 @@ // Load Package Details for some default values const pkg = require('../../package') -const schema = require('./schema') -// const joi = require('joi') const constants = { DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 } const version = { - BROWSER_PROTOCOL: process.env.BROWSER_PROTOCOL ?? 'http', FRONTEND_MODULE_PROTOCOL: process.env.FRONTEND_MODULE_PROTOCOL ?? 'http', FRONTEND_MODULE_HOST: process.env.FRONTEND_MODULE_HOST ?? '0.0.0.0', FRONTEND_MODULE_PORT: process.env.FRONTEND_MODULE_PORT ?? '3000', @@ -21,7 +18,9 @@ const version = { BUILD_COMMIT_SHORT: (process.env.BUILD_COMMIT ?? '0000000').slice(0, 7), } -let FRONTEND_MODULE_URL +// in case of hosting the frontend module with a nginx +let FRONTEND_MODULE_URL = version.FRONTEND_MODULE_PROTOCOL + '://' + version.FRONTEND_MODULE_HOST + // in case of hosting the frontend module with a nodejs-instance if (process.env.FRONTEND_HOSTING === 'nodejs') { FRONTEND_MODULE_URL = @@ -30,9 +29,6 @@ if (process.env.FRONTEND_HOSTING === 'nodejs') { version.FRONTEND_MODULE_HOST + ':' + version.FRONTEND_MODULE_PORT -} else { - // in case of hosting the frontend module with a nginx - FRONTEND_MODULE_URL = version.FRONTEND_MODULE_PROTOCOL + '://' + version.FRONTEND_MODULE_HOST } // const FRONTEND_MODULE_URI = version.FRONTEND_MODULE_PROTOCOL + '://' + version.FRONTEND_MODULE_HOST // + @@ -97,26 +93,8 @@ const CONFIG = { ...endpoints, ...community, ...meta, + ...constants, + FRONTEND_MODULE_URL, } -// Check config -// TODO: use validate and construct error message including description -// joi.attempt(CONFIG, schema) - -const { error } = schema.validate(CONFIG, { stack: true, debug: true }) -const schemaJson = schema.describe() -if (error) { - error.details.forEach((err) => { - const key = err.context.key - const description = schemaJson.keys[key] - ? schema.describe().keys[key].flags.description - : 'No description available' - if (CONFIG[key] === undefined) { - throw new Error(`Environment Variable '${key}' is missing. ${description}`) - } else { - throw new Error(`Error on Environment Variable '${key}': ${err.message}. ${description}`) - } - }) -} - -module.exports = { ...CONFIG, ...constants } +module.exports = CONFIG diff --git a/frontend/src/config/schema.js b/frontend/src/config/schema.js index f0326cc34..ac762562b 100644 --- a/frontend/src/config/schema.js +++ b/frontend/src/config/schema.js @@ -1,6 +1,6 @@ -import { +const { + browserUrls, APP_VERSION, - BROWSER_PROTOCOL, BUILD_COMMIT, BUILD_COMMIT_SHORT, COMMUNITY_DESCRIPTION, @@ -9,19 +9,20 @@ import { COMMUNITY_LOCATION, COMMUNITY_URL, DEBUG, + DECAY_START_TIME, GMS_ACTIVE, GRAPHQL_URI, HUMHUB_ACTIVE, NODE_ENV, PRODUCTION, -} from '../../../config/common.schema' +} = require('../../../config/common.schema') // from '../../../config/common.schema' const Joi = require('joi') // console.log(commonSchema) module.exports = Joi.object({ + browserUrls, APP_VERSION, - BROWSER_PROTOCOL, BUILD_COMMIT, BUILD_COMMIT_SHORT, COMMUNITY_DESCRIPTION, @@ -30,6 +31,7 @@ module.exports = Joi.object({ COMMUNITY_LOCATION, COMMUNITY_URL, DEBUG, + DECAY_START_TIME, GMS_ACTIVE, GRAPHQL_URI, HUMHUB_ACTIVE, @@ -38,40 +40,46 @@ module.exports = Joi.object({ ADMIN_AUTH_URL: Joi.string() .uri({ scheme: ['http', 'https'] }) - .when('browser_protocol', { - is: 'https', - then: Joi.string().uri({ scheme: 'https' }), - otherwise: Joi.string().uri({ scheme: 'http' }), - }) .description('Extern Url for admin-frontend') .default('http://0.0.0.0/admin/authenticate?token=') .required(), COMMUNITY_REGISTER_URL: Joi.string() .uri({ scheme: ['http', 'https'] }) - .when('browser_protocol', { - is: 'https', - then: Joi.string().uri({ scheme: 'https' }), - otherwise: Joi.string().uri({ scheme: 'http' }), - }) .description('URL for Register a new Account in frontend.') .required(), - FRONTEND_MODULE_PROTOCOL: Joi.string() - .valid('http', 'https') - .when('BROWSER_PROTOCOL', { - is: 'https', - then: Joi.string().uri({ scheme: 'https' }), - otherwise: Joi.string().uri({ scheme: 'http' }), + FRONTEND_MODULE_URL: Joi.string() + .uri({ scheme: ['http', 'https'] }) + .when('COMMUNITY_URL', { + is: Joi.exist(), + then: Joi.optional(), // not required if COMMUNITY_URL is provided + otherwise: Joi.required(), // required if COMMUNITY_URL is missing }) .description( - 'Protocol for frontend module hosting, has to be the same as for backend api url and admin to prevent mixed block errors' + "Base Url for reaching frontend in browser, only needed if COMMUNITY_URL wasn't set", + ) + .optional(), // optional in general, but conditionally required + + FRONTEND_MODULE_PROTOCOL: Joi.string() + .when('FRONTEND_HOSTING', { + is: Joi.valid('nodejs'), + then: Joi.valid('http').required(), + otherwise: Joi.valid('http', 'https').required(), + }) + .description( + ` + Protocol for frontend module hosting + - it has to be the same as for backend api url and admin to prevent mixed block errors, + - if frontend is served with nodejs: + is have to be http or setup must be updated to include a ssl certificate + `, ) .default('http') .required(), FRONTEND_HOSTING: Joi.string() - .valid('nodejs') + .valid('nodejs', 'nginx') .description('set to `nodejs` if frontend is hosted by vite with a own nodejs instance') .optional(), diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 33ee02239..98bc67b92 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -7,6 +7,7 @@ import Icons from 'unplugin-icons/vite' import IconsResolve from 'unplugin-icons/resolver' import EnvironmentPlugin from 'vite-plugin-environment' import { createHtmlPlugin } from 'vite-plugin-html' +import schema from './src/config/schema' import { BootstrapVueNextResolver } from 'bootstrap-vue-next' import dotenv from 'dotenv' @@ -16,100 +17,141 @@ dotenv.config() // load env vars from .env const CONFIG = require('./src/config') // https://vitejs.dev/config/ -export default defineConfig({ - server: { - host: CONFIG.FRONTEND_MODULE_HOST, // '0.0.0.0', - port: CONFIG.FRONTEND_MODULE_PORT, // 3000, - https: CONFIG.FRONTEND_MODULE_PROTOCOL === 'https', - }, - resolve: { - alias: { - '@': path.resolve(__dirname, './src'), - assets: path.join(__dirname, 'src/assets'), - '@vee-validate/i18n/dist/locale/en.json': - '/node_modules/@vee-validate/i18n/dist/locale/en.json', - '@vee-validate/i18n/dist/locale/de.json': - '/node_modules/@vee-validate/i18n/dist/locale/de.json', - '@vee-validate/i18n/dist/locale/es.json': - '/node_modules/@vee-validate/i18n/dist/locale/es.json', - '@vee-validate/i18n/dist/locale/fr.json': - '/node_modules/@vee-validate/i18n/dist/locale/fr.json', - '@vee-validate/i18n/dist/locale/nl.json': - '/node_modules/@vee-validate/i18n/dist/locale/nl.json', - '@vee-validate/i18n/dist/locale/tr.json': - '/node_modules/@vee-validate/i18n/dist/locale/tr.json', +export default defineConfig(({ command }) => { + if (command === 'serve') { + CONFIG.FRONTEND_HOSTING = 'nodejs' + } else { + CONFIG.FRONTEND_HOSTING = 'nginx' + } + // Check config + const configDataForValidation = { + ...CONFIG, + // make sure that all urls used in browser have the same protocol to prevent mixed content errors + browserUrls: [ + CONFIG.ADMIN_AUTH_URL, + CONFIG.COMMUNITY_URL, + CONFIG.COMMUNITY_REGISTER_URL, + CONFIG.GRAPHQL_URI, + CONFIG.FRONTEND_MODULE_URL, + ], + } + const { error } = schema.validate(configDataForValidation, { stack: true }) + const schemaJson = schema.describe() + if (error) { + error.details.forEach((err) => { + const key = err.context.key + const value = err.context.value + const description = schemaJson.keys[key] + ? schema.describe().keys[key].flags.description + : 'No description available' + if (configDataForValidation[key] === undefined) { + throw new Error(`Environment Variable '${key}' is missing. ${description}`) + } else { + throw new Error( + `Error on Environment Variable ${key} with value = ${value}: ${err.message}. ${description}`, + ) + } + }) + } + + return { + server: { + host: CONFIG.FRONTEND_MODULE_HOST, // '0.0.0.0', + port: CONFIG.FRONTEND_MODULE_PORT, // 3000, + https: CONFIG.FRONTEND_MODULE_PROTOCOL === 'https', + fs: { + strict: true, + }, + esbuild: { + minify: CONFIG.PRODUCTION === true, + }, }, - extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'], - }, - plugins: [ - vue(), - createHtmlPlugin({ - minify: true, - inject: { - data: { - VITE_META_TITLE_DE: CONFIG.META_TITLE_DE, - VITE_META_TITLE_EN: CONFIG.META_TITLE_EN, - VITE_META_DESCRIPTION_DE: CONFIG.META_DESCRIPTION_DE, - VITE_META_DESCRIPTION_EN: CONFIG.META_DESCRIPTION_EN, - VITE_META_KEYWORDS_DE: CONFIG.META_KEYWORDS_DE, - VITE_META_KEYWORDS_EN: CONFIG.META_KEYWORDS_EN, - VITE_META_AUTHOR: CONFIG.META_AUTHOR, - VITE_META_URL: CONFIG.META_URL, + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + assets: path.join(__dirname, 'src/assets'), + '@vee-validate/i18n/dist/locale/en.json': + '/node_modules/@vee-validate/i18n/dist/locale/en.json', + '@vee-validate/i18n/dist/locale/de.json': + '/node_modules/@vee-validate/i18n/dist/locale/de.json', + '@vee-validate/i18n/dist/locale/es.json': + '/node_modules/@vee-validate/i18n/dist/locale/es.json', + '@vee-validate/i18n/dist/locale/fr.json': + '/node_modules/@vee-validate/i18n/dist/locale/fr.json', + '@vee-validate/i18n/dist/locale/nl.json': + '/node_modules/@vee-validate/i18n/dist/locale/nl.json', + '@vee-validate/i18n/dist/locale/tr.json': + '/node_modules/@vee-validate/i18n/dist/locale/tr.json', + }, + extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'], + }, + plugins: [ + vue(), + createHtmlPlugin({ + minify: CONFIG.PRODUCTION === true, + inject: { + data: { + VITE_META_TITLE_DE: CONFIG.META_TITLE_DE, + VITE_META_TITLE_EN: CONFIG.META_TITLE_EN, + VITE_META_DESCRIPTION_DE: CONFIG.META_DESCRIPTION_DE, + VITE_META_DESCRIPTION_EN: CONFIG.META_DESCRIPTION_EN, + VITE_META_KEYWORDS_DE: CONFIG.META_KEYWORDS_DE, + VITE_META_KEYWORDS_EN: CONFIG.META_KEYWORDS_EN, + VITE_META_AUTHOR: CONFIG.META_AUTHOR, + VITE_META_URL: CONFIG.META_URL, + }, + }, + }), + Components({ + resolvers: [BootstrapVueNextResolver(), IconsResolve()], + dts: true, + }), + Icons({ + compiler: 'vue3', + autoInstall: true, + }), + EnvironmentPlugin({ + BUILD_COMMIT: null, + GMS_ACTIVE: null, + HUMHUB_ACTIVE: null, + DEFAULT_PUBLISHER_ID: null, + PORT: null, + COMMUNITY_HOST: null, + URL_PROTOCOL: null, + COMMUNITY_URL: CONFIG.COMMUNITY_URL, + GRAPHQL_PATH: null, + GRAPHQL_URI: CONFIG.GRAPHQL_URI, // null, + ADMIN_AUTH_PATH: CONFIG.ADMIN_AUTH_PATH ?? null, // it is the only env without exported default + ADMIN_AUTH_URL: CONFIG.ADMIN_AUTH_URL, // null, + COMMUNITY_NAME: null, + COMMUNITY_REGISTER_PATH: null, + COMMUNITY_REGISTER_URL: null, + COMMUNITY_DESCRIPTION: null, + COMMUNITY_SUPPORT_MAIL: null, + META_URL: null, + META_TITLE_DE: null, + META_TITLE_EN: null, + META_DESCRIPTION_DE: null, + META_DESCRIPTION_EN: null, + META_KEYWORDS_DE: null, + META_KEYWORDS_EN: null, + META_AUTHOR: null, + }), + commonjs(), + ], + css: { + extract: CONFIG.PRODUCTION === true, + preprocessorOptions: { + scss: { + additionalData: `@import "@/assets/scss/gradido.scss";`, }, }, - }), - Components({ - resolvers: [BootstrapVueNextResolver(), IconsResolve()], - dts: true, - }), - Icons({ - compiler: 'vue3', - autoInstall: true, - }), - EnvironmentPlugin({ - BUILD_COMMIT: null, - GMS_ACTIVE: null, - HUMHUB_ACTIVE: null, - NODE_ENV: null, - DEFAULT_PUBLISHER_ID: null, - PORT: null, - COMMUNITY_HOST: null, - URL_PROTOCOL: null, - COMMUNITY_URL: null, - GRAPHQL_PATH: null, - GRAPHQL_URI: CONFIG.GRAPHQL_URI, // null, - ADMIN_AUTH_PATH: CONFIG.ADMIN_AUTH_PATH ?? null, // it is the only env without exported default - ADMIN_AUTH_URL: CONFIG.ADMIN_AUTH_URL, // null, - COMMUNITY_NAME: null, - COMMUNITY_REGISTER_PATH: null, - COMMUNITY_REGISTER_URL: null, - COMMUNITY_DESCRIPTION: null, - COMMUNITY_SUPPORT_MAIL: null, - META_URL: null, - META_TITLE_DE: null, - META_TITLE_EN: null, - META_DESCRIPTION_DE: null, - META_DESCRIPTION_EN: null, - META_KEYWORDS_DE: null, - META_KEYWORDS_EN: null, - META_AUTHOR: null, - CONFIG_VERSION: null, - }), - commonjs(), - ], - css: { - extract: true, - preprocessorOptions: { - scss: { - additionalData: `@import "@/assets/scss/gradido.scss";`, - }, }, - }, - build: { - outDir: path.resolve(__dirname, './build'), - chunkSizeWarningLimit: 1600, - rollupOptions: { - external: ['joi'], + build: { + outDir: path.resolve(__dirname, './build'), + chunkSizeWarningLimit: 1600, + minify: 'esbuild', + sourcemap: false, }, - }, + } })