fix: expose coolify production envs
This commit is contained in:
parent
b1d70f2603
commit
860000d0b3
5 changed files with 134 additions and 16 deletions
18
README.md
18
README.md
|
|
@ -104,6 +104,24 @@ La prod utilise uniquement `docker-compose.prod.yml`.
|
|||
|
||||
Ce compose ne contient pas de `build:`. Il tire les images du registry.
|
||||
|
||||
Sur Coolify, `docker-compose.prod.yml` est la source de vérité des variables
|
||||
d'environnement. Les variables sont déclarées dans les blocs `environment:` des
|
||||
services pour que Coolify les crée dans son UI au chargement du compose. Il ne
|
||||
faut pas compter sur un fichier `.env` du dépôt en prod : il est ignoré par Git.
|
||||
|
||||
Variables générées/préremplies pour Coolify :
|
||||
|
||||
- `SERVICE_PASSWORD_POSTGRES` : mot de passe PostgreSQL partagé entre la base et le backend
|
||||
- `SERVICE_BASE64_DATA_ENCRYPTION_KEY` : clé stable de 32 caractères pour `DATA_ENCRYPTION_KEY`
|
||||
- `SERVICE_URL_HOPPSCOTCH_APP` : URL publique de l'app
|
||||
- `SERVICE_URL_HOPPSCOTCH_ADMIN` : URL publique de l'admin
|
||||
- `SERVICE_URL_HOPPSCOTCH_BACKEND` : URL publique du backend
|
||||
- `SERVICE_FQDN_HOPPSCOTCH_BACKEND` : domaine du backend, utilisé pour `VITE_BACKEND_WS_URL`
|
||||
|
||||
Après l'import du compose dans Coolify, vérifier au minimum que les trois URLs
|
||||
publiques correspondent aux domaines assignés aux services app, admin et
|
||||
backend.
|
||||
|
||||
Démarrer avec le tag `latest` :
|
||||
|
||||
```sh
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ services:
|
|||
image: postgres:15
|
||||
user: postgres
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-testpass}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-hoppscotch}
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRES:-testpass}
|
||||
- POSTGRES_DB=${POSTGRES_DB:-hoppscotch}
|
||||
volumes:
|
||||
- hoppscotch-db:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
|
|
@ -22,19 +22,26 @@ services:
|
|||
container_name: hoppscotch-backend
|
||||
restart: unless-stopped
|
||||
image: ${API_CLIENT_REGISTRY:-forge.lclr.dev}/${API_CLIENT_NAMESPACE:-thibaud-lclr}/${API_CLIENT_IMAGE_PREFIX:-api-client}-backend:${API_CLIENT_TAG:-latest}
|
||||
env_file:
|
||||
- ./.env
|
||||
environment:
|
||||
DATABASE_URL: postgresql://postgres:${POSTGRES_PASSWORD:-testpass}@hoppscotch-db:5432/${POSTGRES_DB:-hoppscotch}
|
||||
- DATABASE_URL=postgresql://postgres:${SERVICE_PASSWORD_POSTGRES:-testpass}@hoppscotch-db:5432/${POSTGRES_DB:-hoppscotch}
|
||||
- DATA_ENCRYPTION_KEY=${SERVICE_BASE64_DATA_ENCRYPTION_KEY:-0123456789abcdef0123456789abcdef}
|
||||
- VITE_BASE_URL=${SERVICE_URL_HOPPSCOTCH_APP:-http://localhost:3000}
|
||||
- VITE_SHORTCODE_BASE_URL=${SERVICE_URL_HOPPSCOTCH_APP:-http://localhost:3000}
|
||||
- VITE_ADMIN_URL=${SERVICE_URL_HOPPSCOTCH_ADMIN:-http://localhost:3100}
|
||||
- VITE_BACKEND_GQL_URL=${SERVICE_URL_HOPPSCOTCH_BACKEND:-http://localhost:3170}/graphql
|
||||
- VITE_BACKEND_WS_URL=wss://${SERVICE_FQDN_HOPPSCOTCH_BACKEND:-localhost:3170}/graphql
|
||||
- VITE_BACKEND_API_URL=${SERVICE_URL_HOPPSCOTCH_BACKEND:-http://localhost:3170}/v1
|
||||
- VITE_APP_TOS_LINK=${VITE_APP_TOS_LINK:-https://docs.hoppscotch.io/support/terms}
|
||||
- VITE_APP_PRIVACY_POLICY_LINK=${VITE_APP_PRIVACY_POLICY_LINK:-https://docs.hoppscotch.io/support/privacy}
|
||||
- VITE_PROXYSCOTCH_ACCESS_TOKEN=${VITE_PROXYSCOTCH_ACCESS_TOKEN:-}
|
||||
- ENABLE_SUBPATH_BASED_ACCESS=${ENABLE_SUBPATH_BASED_ACCESS:-false}
|
||||
- WHITELISTED_ORIGINS=${SERVICE_URL_HOPPSCOTCH_APP:-http://localhost:3000},${SERVICE_URL_HOPPSCOTCH_ADMIN:-http://localhost:3100}
|
||||
- TRUST_PROXY=${TRUST_PROXY:-true}
|
||||
depends_on:
|
||||
hoppscotch-db:
|
||||
condition: service_healthy
|
||||
command:
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
"pnpm exec prisma migrate deploy && node prod_run.mjs",
|
||||
]
|
||||
["sh", "-c", "pnpm exec prisma migrate deploy && node prod_run.mjs"]
|
||||
ports:
|
||||
- "3170:3170"
|
||||
|
||||
|
|
@ -42,8 +49,17 @@ services:
|
|||
container_name: hoppscotch-app
|
||||
restart: unless-stopped
|
||||
image: ${API_CLIENT_REGISTRY:-forge.lclr.dev}/${API_CLIENT_NAMESPACE:-thibaud-lclr}/${API_CLIENT_IMAGE_PREFIX:-api-client}-app:${API_CLIENT_TAG:-latest}
|
||||
env_file:
|
||||
- ./.env
|
||||
environment:
|
||||
- VITE_BASE_URL=${SERVICE_URL_HOPPSCOTCH_APP:-http://localhost:3000}
|
||||
- VITE_SHORTCODE_BASE_URL=${SERVICE_URL_HOPPSCOTCH_APP:-http://localhost:3000}
|
||||
- VITE_ADMIN_URL=${SERVICE_URL_HOPPSCOTCH_ADMIN:-http://localhost:3100}
|
||||
- VITE_BACKEND_GQL_URL=${SERVICE_URL_HOPPSCOTCH_BACKEND:-http://localhost:3170}/graphql
|
||||
- VITE_BACKEND_WS_URL=wss://${SERVICE_FQDN_HOPPSCOTCH_BACKEND:-localhost:3170}/graphql
|
||||
- VITE_BACKEND_API_URL=${SERVICE_URL_HOPPSCOTCH_BACKEND:-http://localhost:3170}/v1
|
||||
- VITE_APP_TOS_LINK=${VITE_APP_TOS_LINK:-https://docs.hoppscotch.io/support/terms}
|
||||
- VITE_APP_PRIVACY_POLICY_LINK=${VITE_APP_PRIVACY_POLICY_LINK:-https://docs.hoppscotch.io/support/privacy}
|
||||
- VITE_PROXYSCOTCH_ACCESS_TOKEN=${VITE_PROXYSCOTCH_ACCESS_TOKEN:-}
|
||||
- ENABLE_SUBPATH_BASED_ACCESS=${ENABLE_SUBPATH_BASED_ACCESS:-false}
|
||||
depends_on:
|
||||
hoppscotch-backend:
|
||||
condition: service_started
|
||||
|
|
@ -55,8 +71,16 @@ services:
|
|||
container_name: hoppscotch-sh-admin
|
||||
restart: unless-stopped
|
||||
image: ${API_CLIENT_REGISTRY:-forge.lclr.dev}/${API_CLIENT_NAMESPACE:-thibaud-lclr}/${API_CLIENT_IMAGE_PREFIX:-api-client}-sh-admin:${API_CLIENT_TAG:-latest}
|
||||
env_file:
|
||||
- ./.env
|
||||
environment:
|
||||
- VITE_BASE_URL=${SERVICE_URL_HOPPSCOTCH_APP:-http://localhost:3000}
|
||||
- VITE_SHORTCODE_BASE_URL=${SERVICE_URL_HOPPSCOTCH_APP:-http://localhost:3000}
|
||||
- VITE_ADMIN_URL=${SERVICE_URL_HOPPSCOTCH_ADMIN:-http://localhost:3100}
|
||||
- VITE_BACKEND_GQL_URL=${SERVICE_URL_HOPPSCOTCH_BACKEND:-http://localhost:3170}/graphql
|
||||
- VITE_BACKEND_API_URL=${SERVICE_URL_HOPPSCOTCH_BACKEND:-http://localhost:3170}/v1
|
||||
- VITE_APP_TOS_LINK=${VITE_APP_TOS_LINK:-https://docs.hoppscotch.io/support/terms}
|
||||
- VITE_APP_PRIVACY_POLICY_LINK=${VITE_APP_PRIVACY_POLICY_LINK:-https://docs.hoppscotch.io/support/privacy}
|
||||
- VITE_PROXYSCOTCH_ACCESS_TOKEN=${VITE_PROXYSCOTCH_ACCESS_TOKEN:-}
|
||||
- ENABLE_SUBPATH_BASED_ACCESS=${ENABLE_SUBPATH_BASED_ACCESS:-false}
|
||||
depends_on:
|
||||
hoppscotch-backend:
|
||||
condition: service_started
|
||||
|
|
|
|||
39
packages/hoppscotch-backend/src/cors-config.spec.ts
Normal file
39
packages/hoppscotch-backend/src/cors-config.spec.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { ConfigService } from '@nestjs/config';
|
||||
import { resolveProductionCorsOrigins } from './cors-config';
|
||||
|
||||
describe('resolveProductionCorsOrigins', () => {
|
||||
const configService = (values: Record<string, string | undefined>) =>
|
||||
({
|
||||
get: (key: string) => values[key],
|
||||
}) as ConfigService;
|
||||
|
||||
test('Should use WHITELISTED_ORIGINS when it is configured', () => {
|
||||
expect(
|
||||
resolveProductionCorsOrigins(
|
||||
configService({
|
||||
WHITELISTED_ORIGINS:
|
||||
'https://app.example.test, https://admin.example.test',
|
||||
VITE_BASE_URL: 'https://ignored.example.test',
|
||||
VITE_ADMIN_URL: 'https://ignored-admin.example.test',
|
||||
}),
|
||||
),
|
||||
).toEqual(['https://app.example.test', 'https://admin.example.test']);
|
||||
});
|
||||
|
||||
test('Should fall back to app and admin URLs when WHITELISTED_ORIGINS is missing', () => {
|
||||
expect(
|
||||
resolveProductionCorsOrigins(
|
||||
configService({
|
||||
VITE_BASE_URL: 'https://app.example.test',
|
||||
VITE_ADMIN_URL: 'https://admin.example.test',
|
||||
}),
|
||||
),
|
||||
).toEqual(['https://app.example.test', 'https://admin.example.test']);
|
||||
});
|
||||
|
||||
test('Should explain the missing production CORS configuration', () => {
|
||||
expect(() => resolveProductionCorsOrigins(configService({}))).toThrow(
|
||||
'Missing production CORS configuration. Set WHITELISTED_ORIGINS or configure VITE_BASE_URL/VITE_ADMIN_URL.',
|
||||
);
|
||||
});
|
||||
});
|
||||
36
packages/hoppscotch-backend/src/cors-config.ts
Normal file
36
packages/hoppscotch-backend/src/cors-config.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
const MISSING_PRODUCTION_CORS_CONFIG =
|
||||
'Missing production CORS configuration. Set WHITELISTED_ORIGINS or configure VITE_BASE_URL/VITE_ADMIN_URL.';
|
||||
|
||||
function parseOrigins(origins: string | undefined): string[] {
|
||||
return (
|
||||
origins
|
||||
?.split(',')
|
||||
.map((origin) => origin.trim())
|
||||
.filter(Boolean) ?? []
|
||||
);
|
||||
}
|
||||
|
||||
export function resolveProductionCorsOrigins(
|
||||
configService: ConfigService,
|
||||
): string[] {
|
||||
const whitelistedOrigins = parseOrigins(
|
||||
configService.get('WHITELISTED_ORIGINS'),
|
||||
);
|
||||
|
||||
if (whitelistedOrigins.length > 0) {
|
||||
return whitelistedOrigins;
|
||||
}
|
||||
|
||||
const derivedOrigins = [
|
||||
configService.get('VITE_BASE_URL'),
|
||||
configService.get('VITE_ADMIN_URL'),
|
||||
].filter(Boolean);
|
||||
|
||||
if (derivedOrigins.length === 0) {
|
||||
throw new Error(MISSING_PRODUCTION_CORS_CONFIG);
|
||||
}
|
||||
|
||||
return derivedOrigins;
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import { ConfigService } from '@nestjs/config';
|
|||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
import { InfraTokenModule } from './infra-token/infra-token.module';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import { resolveProductionCorsOrigins } from './cors-config';
|
||||
|
||||
function setupSwagger(
|
||||
app: NestExpressApplication,
|
||||
|
|
@ -58,7 +59,7 @@ async function bootstrap() {
|
|||
if (isProduction) {
|
||||
console.log('Enabling CORS with production settings');
|
||||
app.enableCors({
|
||||
origin: configService.get('WHITELISTED_ORIGINS').split(','),
|
||||
origin: resolveProductionCorsOrigins(configService),
|
||||
credentials: true,
|
||||
});
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Reference in a new issue