diff --git a/fitnes/.env.example b/fitnes/.env.example index 1cc82d5..8d538d5 100644 --- a/fitnes/.env.example +++ b/fitnes/.env.example @@ -10,6 +10,8 @@ VITE_BASE_PATH=/ # --------------------------------------------------------------------------- # Server # --------------------------------------------------------------------------- +# One port per landing so all four can run side by side: +# fitnes 3000, hotel 3001, medcenter 3002, medcenterpersonal 3003. PORT=3000 # --------------------------------------------------------------------------- diff --git a/fitnes/.nvmrc b/fitnes/.nvmrc new file mode 100644 index 0000000..a45fd52 --- /dev/null +++ b/fitnes/.nvmrc @@ -0,0 +1 @@ +24 diff --git a/fitnes/index.html b/fitnes/index.html index 7c9169f..1815920 100644 --- a/fitnes/index.html +++ b/fitnes/index.html @@ -33,7 +33,7 @@ "provider": { "@type": "Organization", "name": "Экзо Групп" }, "serviceType": "Проектирование и запуск зоны восстановления в фитнес-клубе", "areaServed": "RU", - "url": "https://exodevices.ru/fitness" + "url": "%VITE_SITE_URL%" } diff --git a/fitnes/package.json b/fitnes/package.json index 52a7217..1c6b8d6 100644 --- a/fitnes/package.json +++ b/fitnes/package.json @@ -3,6 +3,9 @@ "private": true, "version": "1.0.0", "type": "module", + "engines": { + "node": ">=22.12" + }, "scripts": { "dev": "concurrently -n client,server -c cyan,magenta \"vite\" \"tsx watch server/src/index.ts\"", "dev:client": "vite", diff --git a/fitnes/server/src/config.ts b/fitnes/server/src/config.ts index 0bc47cc..c92c7e3 100644 --- a/fitnes/server/src/config.ts +++ b/fitnes/server/src/config.ts @@ -54,5 +54,8 @@ export function readAmoConfig(): AmoConfig | null { export const serverConfig = { port: optionalNumber('PORT') ?? 3000, + // Loopback by default: in production nginx is the only thing that should + // reach this process, so the port is never exposed to the network. + host: optional('HOST') ?? '127.0.0.1', isProduction: process.env.NODE_ENV === 'production', } diff --git a/fitnes/server/src/index.ts b/fitnes/server/src/index.ts index 208e5d5..dc3825e 100644 --- a/fitnes/server/src/index.ts +++ b/fitnes/server/src/index.ts @@ -20,7 +20,10 @@ if (!leadService) { } const app = express() -app.set('trust proxy', true) +// 'loopback', not true: only the local nginx hop is trusted, so req.ip is the +// address nginx actually observed and the rate limiter below cannot be +// side-stepped with a forged X-Forwarded-For header. +app.set('trust proxy', 'loopback') app.use(express.json({ limit: '64kb' })) const limiter = createRateLimiter({ limit: 8, windowMs: 10 * 60 * 1000 }) @@ -103,7 +106,7 @@ if (serverConfig.isProduction) { }) } -app.listen(serverConfig.port, () => { +app.listen(serverConfig.port, serverConfig.host, () => { console.info( '[server] listening on http://localhost:%d%s', serverConfig.port, diff --git a/hotel/.env.example b/hotel/.env.example index 54ef4fd..d288f2b 100644 --- a/hotel/.env.example +++ b/hotel/.env.example @@ -10,7 +10,8 @@ VITE_BASE_PATH=/ # --------------------------------------------------------------------------- # Server # --------------------------------------------------------------------------- -# 3001 by default so this landing can run next to the fitness one (3000). +# One port per landing so all four can run side by side: +# fitnes 3000, hotel 3001, medcenter 3002, medcenterpersonal 3003. PORT=3001 # --------------------------------------------------------------------------- diff --git a/hotel/.nvmrc b/hotel/.nvmrc new file mode 100644 index 0000000..a45fd52 --- /dev/null +++ b/hotel/.nvmrc @@ -0,0 +1 @@ +24 diff --git a/hotel/index.html b/hotel/index.html index 1c91990..c00b1c6 100644 --- a/hotel/index.html +++ b/hotel/index.html @@ -37,7 +37,7 @@ "provider": { "@type": "Organization", "name": "Экзо Групп" }, "serviceType": "Проектирование и запуск кабинета восстановления в отеле", "areaServed": "RU", - "url": "https://exodevices.ru/hotels" + "url": "%VITE_SITE_URL%" } diff --git a/hotel/package.json b/hotel/package.json index dcca9ed..e652ce1 100644 --- a/hotel/package.json +++ b/hotel/package.json @@ -3,6 +3,9 @@ "private": true, "version": "1.0.0", "type": "module", + "engines": { + "node": ">=22.12" + }, "scripts": { "dev": "concurrently -n client,server -c cyan,magenta \"vite\" \"tsx watch server/src/index.ts\"", "dev:client": "vite", diff --git a/hotel/server/src/config.ts b/hotel/server/src/config.ts index 9c56723..fe161a7 100644 --- a/hotel/server/src/config.ts +++ b/hotel/server/src/config.ts @@ -54,5 +54,8 @@ export function readAmoConfig(): AmoConfig | null { export const serverConfig = { port: optionalNumber('PORT') ?? 3001, + // Loopback by default: in production nginx is the only thing that should + // reach this process, so the port is never exposed to the network. + host: optional('HOST') ?? '127.0.0.1', isProduction: process.env.NODE_ENV === 'production', } diff --git a/hotel/server/src/index.ts b/hotel/server/src/index.ts index 7b44874..aa8e86d 100644 --- a/hotel/server/src/index.ts +++ b/hotel/server/src/index.ts @@ -20,7 +20,10 @@ if (!leadService) { } const app = express() -app.set('trust proxy', true) +// 'loopback', not true: only the local nginx hop is trusted, so req.ip is the +// address nginx actually observed and the rate limiter below cannot be +// side-stepped with a forged X-Forwarded-For header. +app.set('trust proxy', 'loopback') app.use(express.json({ limit: '64kb' })) const limiter = createRateLimiter({ limit: 8, windowMs: 10 * 60 * 1000 }) @@ -103,7 +106,7 @@ if (serverConfig.isProduction) { }) } -app.listen(serverConfig.port, () => { +app.listen(serverConfig.port, serverConfig.host, () => { console.info( '[server] listening on http://localhost:%d%s', serverConfig.port, diff --git a/medcenter/.env.example b/medcenter/.env.example index 98d2b2d..34af6c5 100644 --- a/medcenter/.env.example +++ b/medcenter/.env.example @@ -11,7 +11,9 @@ VITE_BASE_PATH=/ # --------------------------------------------------------------------------- # Server # --------------------------------------------------------------------------- -PORT=3000 +# One port per landing so all four can run side by side: +# fitnes 3000, hotel 3001, medcenter 3002, medcenterpersonal 3003. +PORT=3002 # --------------------------------------------------------------------------- # amoCRM — server-side only, never exposed to the browser diff --git a/medcenter/.nvmrc b/medcenter/.nvmrc new file mode 100644 index 0000000..a45fd52 --- /dev/null +++ b/medcenter/.nvmrc @@ -0,0 +1 @@ +24 diff --git a/medcenter/index.html b/medcenter/index.html index f7bf089..35e03c8 100644 --- a/medcenter/index.html +++ b/medcenter/index.html @@ -36,7 +36,7 @@ "provider": { "@type": "Organization", "name": "Экзо Групп" }, "serviceType": "Аудит и модернизация кабинета физиотерапии медицинского центра", "areaServed": "RU", - "url": "https://exodevices.ru/medical-centers/with-physiotherapy" + "url": "%VITE_SITE_URL%" } diff --git a/medcenter/package.json b/medcenter/package.json index 7c8ca70..a65b4b8 100644 --- a/medcenter/package.json +++ b/medcenter/package.json @@ -3,6 +3,9 @@ "private": true, "version": "1.0.0", "type": "module", + "engines": { + "node": ">=22.12" + }, "scripts": { "dev": "concurrently -n client,server -c cyan,magenta \"vite\" \"tsx watch server/src/index.ts\"", "dev:client": "vite", diff --git a/medcenter/server/src/config.ts b/medcenter/server/src/config.ts index 0bc47cc..9796bc1 100644 --- a/medcenter/server/src/config.ts +++ b/medcenter/server/src/config.ts @@ -53,6 +53,9 @@ export function readAmoConfig(): AmoConfig | null { } export const serverConfig = { - port: optionalNumber('PORT') ?? 3000, + port: optionalNumber('PORT') ?? 3002, + // Loopback by default: in production nginx is the only thing that should + // reach this process, so the port is never exposed to the network. + host: optional('HOST') ?? '127.0.0.1', isProduction: process.env.NODE_ENV === 'production', } diff --git a/medcenter/server/src/index.ts b/medcenter/server/src/index.ts index c12c929..3dc19b7 100644 --- a/medcenter/server/src/index.ts +++ b/medcenter/server/src/index.ts @@ -20,7 +20,10 @@ if (!leadService) { } const app = express() -app.set('trust proxy', true) +// 'loopback', not true: only the local nginx hop is trusted, so req.ip is the +// address nginx actually observed and the rate limiter below cannot be +// side-stepped with a forged X-Forwarded-For header. +app.set('trust proxy', 'loopback') app.use(express.json({ limit: '64kb' })) const limiter = createRateLimiter({ limit: 8, windowMs: 10 * 60 * 1000 }) @@ -103,7 +106,7 @@ if (serverConfig.isProduction) { }) } -app.listen(serverConfig.port, () => { +app.listen(serverConfig.port, serverConfig.host, () => { console.info( '[server] listening on http://localhost:%d%s', serverConfig.port, diff --git a/medcenter/vite.config.ts b/medcenter/vite.config.ts index 73ce968..3fe7db8 100644 --- a/medcenter/vite.config.ts +++ b/medcenter/vite.config.ts @@ -15,7 +15,7 @@ export default defineConfig({ server: { port: 5173, proxy: { - '/api': { target: 'http://localhost:3000', changeOrigin: true }, + '/api': { target: 'http://localhost:3002', changeOrigin: true }, }, }, }) diff --git a/medcenterpersonal/.env.example b/medcenterpersonal/.env.example index 2e0bd93..75d7eaf 100644 --- a/medcenterpersonal/.env.example +++ b/medcenterpersonal/.env.example @@ -11,8 +11,9 @@ VITE_BASE_PATH=/ # --------------------------------------------------------------------------- # Server # --------------------------------------------------------------------------- -# 3000 and 3001 belong to the fitnes/medcenter and hotel landings. -PORT=3002 +# One port per landing so all four can run side by side: +# fitnes 3000, hotel 3001, medcenter 3002, medcenterpersonal 3003. +PORT=3003 # --------------------------------------------------------------------------- # amoCRM — server-side only, never exposed to the browser diff --git a/medcenterpersonal/.nvmrc b/medcenterpersonal/.nvmrc new file mode 100644 index 0000000..a45fd52 --- /dev/null +++ b/medcenterpersonal/.nvmrc @@ -0,0 +1 @@ +24 diff --git a/medcenterpersonal/index.html b/medcenterpersonal/index.html index 6502d2e..04a7eef 100644 --- a/medcenterpersonal/index.html +++ b/medcenterpersonal/index.html @@ -36,7 +36,7 @@ "provider": { "@type": "Organization", "name": "Экзо Групп" }, "serviceType": "Запуск направления физиотерапии в медицинском центре под ключ", "areaServed": "RU", - "url": "https://exodevices.ru/medical-centers/no-physiotherapy" + "url": "%VITE_SITE_URL%" } diff --git a/medcenterpersonal/package.json b/medcenterpersonal/package.json index b448e7b..a80b708 100644 --- a/medcenterpersonal/package.json +++ b/medcenterpersonal/package.json @@ -3,6 +3,9 @@ "private": true, "version": "1.0.0", "type": "module", + "engines": { + "node": ">=22.12" + }, "scripts": { "dev": "concurrently -n client,server -c cyan,magenta \"vite\" \"tsx watch server/src/index.ts\"", "dev:client": "vite", diff --git a/medcenterpersonal/server/src/config.ts b/medcenterpersonal/server/src/config.ts index 0bc47cc..7d8bd91 100644 --- a/medcenterpersonal/server/src/config.ts +++ b/medcenterpersonal/server/src/config.ts @@ -53,6 +53,9 @@ export function readAmoConfig(): AmoConfig | null { } export const serverConfig = { - port: optionalNumber('PORT') ?? 3000, + port: optionalNumber('PORT') ?? 3003, + // Loopback by default: in production nginx is the only thing that should + // reach this process, so the port is never exposed to the network. + host: optional('HOST') ?? '127.0.0.1', isProduction: process.env.NODE_ENV === 'production', } diff --git a/medcenterpersonal/server/src/index.ts b/medcenterpersonal/server/src/index.ts index 0f42a89..53655fa 100644 --- a/medcenterpersonal/server/src/index.ts +++ b/medcenterpersonal/server/src/index.ts @@ -20,7 +20,10 @@ if (!leadService) { } const app = express() -app.set('trust proxy', true) +// 'loopback', not true: only the local nginx hop is trusted, so req.ip is the +// address nginx actually observed and the rate limiter below cannot be +// side-stepped with a forged X-Forwarded-For header. +app.set('trust proxy', 'loopback') app.use(express.json({ limit: '64kb' })) const limiter = createRateLimiter({ limit: 8, windowMs: 10 * 60 * 1000 }) @@ -103,7 +106,7 @@ if (serverConfig.isProduction) { }) } -app.listen(serverConfig.port, () => { +app.listen(serverConfig.port, serverConfig.host, () => { console.info( '[server] listening on http://localhost:%d%s', serverConfig.port, diff --git a/medcenterpersonal/vite.config.ts b/medcenterpersonal/vite.config.ts index eb326c6..4c69a19 100644 --- a/medcenterpersonal/vite.config.ts +++ b/medcenterpersonal/vite.config.ts @@ -17,7 +17,7 @@ export default defineConfig({ // landings, so all of them can run side by side. port: 5175, proxy: { - '/api': { target: 'http://localhost:3002', changeOrigin: true }, + '/api': { target: 'http://localhost:3003', changeOrigin: true }, }, }, })