diff --git a/backend/.env.development.example b/backend/.env.development.example index 247ff054..c9f3cdbf 100644 --- a/backend/.env.development.example +++ b/backend/.env.development.example @@ -1,10 +1,16 @@ -DWENGO_PORT=3000 +# +# Basic configuration +# + +DWENGO_PORT=3000 # The port the backend will listen on DWENGO_DB_HOST=localhost -DWENGO_DB_PORT=5431 +DWENGO_DB_PORT=5432 DWENGO_DB_USERNAME=postgres DWENGO_DB_PASSWORD=postgres DWENGO_DB_UPDATE=true +# Auth + DWENGO_AUTH_STUDENT_URL=http://localhost:7080/realms/student DWENGO_AUTH_STUDENT_CLIENT_ID=dwengo DWENGO_AUTH_STUDENT_JWKS_ENDPOINT=http://localhost:7080/realms/student/protocol/openid-connect/certs @@ -14,3 +20,9 @@ DWENGO_AUTH_TEACHER_JWKS_ENDPOINT=http://localhost:7080/realms/teacher/protocol/ # Allow Vite dev-server to access the backend (for testing purposes). Don't forget to remove this in production! DWENGO_CORS_ALLOWED_ORIGINS=http://localhost:5173 + +# +# Advanced configuration +# + +# LOKI_HOST=http://localhost:9001 # The address of the Loki instance, used for logging diff --git a/backend/.env.example b/backend/.env.example index 105a1654..0a8f3994 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,4 +1,4 @@ -DWENGO_PORT=3000 # The port the backend will listen on +DWENGO_PORT=3000 # The port the backend will listen on DWENGO_DB_HOST=domain-or-ip-of-database DWENGO_DB_PORT=5432 diff --git a/backend/.env.production b/backend/.env.production deleted file mode 100644 index 8ee6113e..00000000 --- a/backend/.env.production +++ /dev/null @@ -1,7 +0,0 @@ -DWENGO_PORT=3000 -DWENGO_DB_HOST=localhost -DWENGO_DB_PORT=5432 -DWENGO_DB_NAME=postgres -DWENGO_DB_USERNAME=postgres -DWENGO_DB_PASSWORD=postgres -DWENGO_DB_UPDATE=true diff --git a/backend/.env.production.example b/backend/.env.production.example new file mode 100644 index 00000000..1adf7d95 --- /dev/null +++ b/backend/.env.production.example @@ -0,0 +1,28 @@ +DWENGO_PORT=3000 # The port the backend will listen on +DWENGO_DB_HOST=db # Name of the database container +DWENGO_DB_PORT=5432 + +# Change this to the actual credentials of the user Dwengo should use in the backend +DWENGO_DB_NAME=postgres +DWENGO_DB_USERNAME=postgres +DWENGO_DB_PASSWORD=postgres + +# Set this to true when the database scheme needs to be updated. In that case, take a backup first. +DWENGO_DB_UPDATE=false + +# Data for the identity provider via which the students authenticate. +DWENGO_AUTH_STUDENT_URL=https://sel2-1.ugent.be/idp/realms/student +DWENGO_AUTH_STUDENT_CLIENT_ID=dwengo +DWENGO_AUTH_STUDENT_JWKS_ENDPOINT=http://idp:7080/idp/realms/student/protocol/openid-connect/certs # Name of the idp container +# Data for the identity provider via which the teachers authenticate. +DWENGO_AUTH_TEACHER_URL=https://sel2-1.ugent.be/idp/realms/teacher +DWENGO_AUTH_TEACHER_CLIENT_ID=dwengo +DWENGO_AUTH_TEACHER_JWKS_ENDPOINT=http://idp:7080/idp/realms/teacher/protocol/openid-connect/certs # Name of the idp container + +# +# Advanced configuration +# + +# Logging and monitoring + +# LOKI_HOST=http://logging:3102 # The address of the Loki instance, used for logging diff --git a/backend/src/app.ts b/backend/src/app.ts index 61d173ec..55352220 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -30,29 +30,29 @@ app.use(responseTime(responseTimeLogger)); app.use(authenticateUser); // TODO Replace with Express routes -app.get('/', (_, res: Response) => { - logger.debug('GET /'); +app.get('/api/', (_, res: Response) => { + logger.debug('GET /api/'); res.json({ message: 'Hello Dwengo!🚀', }); }); -app.use('/student', studentRouter); -app.use('/group', groupRouter); -app.use('/assignment', assignmentRouter); -app.use('/submission', submissionRouter); -app.use('/class', classRouter); -app.use('/question', questionRouter); -app.use('/auth', authRouter); -app.use('/theme', themeRoutes); -app.use('/learningPath', learningPathRoutes); -app.use('/learningObject', learningObjectRoutes); +app.use('/api/student', studentRouter); +app.use('/api/group', groupRouter); +app.use('/api/assignment', assignmentRouter); +app.use('/api/submission', submissionRouter); +app.use('/api/class', classRouter); +app.use('/api/question', questionRouter); +app.use('/api/auth', authRouter); +app.use('/api/theme', themeRoutes); +app.use('/api/learningPath', learningPathRoutes); +app.use('/api/learningObject', learningObjectRoutes); async function startServer() { await initORM(); app.listen(port, () => { - logger.info(`Server is running at http://localhost:${port}`); + logger.info(`Server is running at http://localhost:${port}/api`); }); } diff --git a/compose.override.yml b/compose.override.yml new file mode 100644 index 00000000..5c35441e --- /dev/null +++ b/compose.override.yml @@ -0,0 +1,72 @@ +# +# Use this configuration to test the production configuration locally. +# +# This configuration builds the frontend and backend services as Docker images, +# and uses the paths for the services, instead of ports. +# +services: + web: + build: + context: . + dockerfile: frontend/Dockerfile + ports: + - '8080:8080/tcp' + restart: unless-stopped + labels: + - 'traefik.http.routers.web.rule=PathPrefix(`/`)' + - 'traefik.http.services.web.loadbalancer.server.port=8080' + + api: + build: + context: . + dockerfile: backend/Dockerfile + ports: + - '3000:3000/tcp' + restart: unless-stopped + volumes: + - ./backend/.env:/app/.env + depends_on: + - db + - logging + labels: + - 'traefik.http.routers.api.rule=PathPrefix(`/api`)' + - 'traefik.http.services.api.loadbalancer.server.port=3000' + + idp: + # Also see compose.yml + labels: + - 'traefik.http.routers.idp.rule=PathPrefix(`/idp`)' + - 'traefik.http.services.idp.loadbalancer.server.port=7080' + environment: + PROXY_ADDRESS_FORWARDING: 'true' + KC_HTTP_RELATIVE_PATH: '/idp' + + reverse-proxy: + image: traefik:v3.3 + command: + # Enable web UI + - '--api.insecure=true' + + # Add Docker provider + - '--providers.docker=true' + - '--providers.docker.exposedbydefault=true' + + # Add web entrypoint + - '--entrypoints.web.address=:80/tcp' + ports: + - '9000:8080' + - '80:80/tcp' + restart: unless-stopped + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + + dashboards: + image: grafana/grafana:latest + ports: + - '9002:3000' + volumes: + - dwengo_grafana_data:/var/lib/grafana + restart: unless-stopped + +volumes: + dwengo_grafana_data: diff --git a/compose.prod.yml b/compose.prod.yml new file mode 100644 index 00000000..2063323e --- /dev/null +++ b/compose.prod.yml @@ -0,0 +1,110 @@ +# +# This file is used to define the production environment for the project. +# It is used to deploy the project on a server. +# Should not be used for local development. +# +services: + web: + build: + context: . + dockerfile: frontend/Dockerfile + restart: unless-stopped + networks: + - dwengo-1 + labels: + - 'traefik.enable=true' + - 'traefik.http.routers.web.rule=PathPrefix(`/`)' + - 'traefik.http.services.web.loadbalancer.server.port=80' + + api: + build: + context: . + dockerfile: backend/Dockerfile + restart: unless-stopped + volumes: + # TODO Replace with environment keys + - ./backend/.env:/app/.env + depends_on: + - db + - logging + networks: + - dwengo-1 + labels: + - 'traefik.enable=true' + - 'traefik.http.routers.api.rule=PathPrefix(`/api`)' + - 'traefik.http.services.api.loadbalancer.server.port=3000' + + db: + # Also see compose.yml + networks: + - dwengo-1 + + idp: + # Also see compose.yml + # TODO Replace with proper production command + command: ['start-dev', '--http-port', '7080', '--https-port', '7443', '--import-realm'] + networks: + - dwengo-1 + labels: + - 'traefik.enable=true' + - 'traefik.http.routers.idp.rule=PathPrefix(`/idp`)' + - 'traefik.http.services.idp.loadbalancer.server.port=7080' + env_file: + - ./config/idp/.env + environment: + KC_HOSTNAME: 'sel2-1.ugent.be' + PROXY_ADDRESS_FORWARDING: 'true' + KC_HTTP_RELATIVE_PATH: '/idp' + + reverse-proxy: + image: traefik:v3.3 + ports: + - '80:80/tcp' + - '443:443/tcp' + command: + # Add Docker provider + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + + # Add web entrypoint + - "--entrypoints.web.address=:80/tcp" + - "--entrypoints.web.http.redirections.entryPoint.to=websecure" + - "--entrypoints.web.http.redirections.entryPoint.scheme=https" + + # Add websecure entrypoint + - "--entrypoints.websecure.address=:443/tcp" + - "--entrypoints.websecure.http.tls=true" + - "--entrypoints.websecure.http.tls.certResolver=letsencrypt" + - "--entrypoints.websecure.http.tls.domains[0].main=sel2-1.ugent.be" + + # Certificates + - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true" + - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web" + - "--certificatesresolvers.letsencrypt.acme.email=timo.demeyst@ugent.be" + - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" + restart: unless-stopped + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - dwengo_letsencrypt:/letsencrypt + networks: + - dwengo-1 + + logging: + # Also see compose.yml + networks: + - dwengo-1 + + dashboards: + image: grafana/grafana:latest + ports: + - '9002:3000' + restart: unless-stopped + volumes: + - dwengo_grafana_data:/var/lib/grafana + +volumes: + dwengo_grafana_data: + dwengo_letsencrypt: + +networks: + dwengo-1: diff --git a/compose.yml b/compose.yml new file mode 100644 index 00000000..2bb736bf --- /dev/null +++ b/compose.yml @@ -0,0 +1,52 @@ +# +# Use this configuration during development. +# +# This configuration is suitable to access the services using their ports. +# +services: + db: + image: postgres:latest + ports: + - '5432:5432' + restart: unless-stopped + volumes: + - dwengo_postgres_data:/var/lib/postgresql/data + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + + idp: # Based on: https://medium.com/@fingervinicius/easy-running-keycloak-with-docker-compose-b0d7a4ee2358 + image: quay.io/keycloak/keycloak:latest + ports: + - '7080:7080' + # - '7443:7443' + command: [ 'start-dev', '--http-port', '7080', '--https-port', '7443', '--import-realm' ] + restart: unless-stopped + volumes: + - ./config/idp:/opt/keycloak/data/import + depends_on: + - db + environment: + KC_HOSTNAME: localhost + KC_HOSTNAME_PORT: 7080 + KC_HOSTNAME_STRICT_BACKCHANNEL: 'true' + KC_BOOTSTRAP_ADMIN_USERNAME: admin + KC_BOOTSTRAP_ADMIN_PASSWORD: admin + KC_HEALTH_ENABLED: 'true' + KC_LOG_LEVEL: info + + logging: + image: grafana/loki:latest + ports: + - '9001:3102' + - '9095:9095' + command: -config.file=/etc/loki/config.yaml + restart: unless-stopped + volumes: + - ./config/loki/config.yml:/etc/loki/config.yaml + - dwengo_loki_data:/loki + +volumes: + dwengo_loki_data: + dwengo_postgres_data: diff --git a/config/nginx/nginx.conf b/config/nginx/nginx.conf index 7a3b995e..975b9580 100644 --- a/config/nginx/nginx.conf +++ b/config/nginx/nginx.conf @@ -15,7 +15,7 @@ http { } server { - listen 80; + listen 8080; location / { root /usr/share/nginx/html; diff --git a/docker-compose.production.yml b/docker-compose.production.yml deleted file mode 100644 index 7480746f..00000000 --- a/docker-compose.production.yml +++ /dev/null @@ -1,111 +0,0 @@ -services: - web: - build: - context: . - dockerfile: ./frontend/Dockerfile - restart: unless-stopped - networks: - - dwengo-1 - labels: - - 'traefik.enable=true' - - 'traefik.http.routers.web.rule=PathPrefix(`/`)' - - 'traefik.http.services.web.loadbalancer.server.port=80' - - api: - build: - context: . - dockerfile: ./backend/Dockerfile - restart: unless-stopped - volumes: - # TODO Replace with environment keys - - ./backend/.env:/app/.env - networks: - - dwengo-1 - depends_on: - - db - - logging - labels: - - 'traefik.enable=true' - - 'traefik.http.middlewares.api-prefix.stripprefix.prefixes=/api' - - 'traefik.http.routers.api.rule=Host(`sel2-1.ugent.be`)' - - 'traefik.http.routers.api.rule=PathPrefix(`/api`)' - - 'traefik.http.routers.api.middlewares=api-prefix' - - 'traefik.http.services.api.loadbalancer.server.port=3000' - - db: - image: postgres:latest - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: postgres - restart: unless-stopped - volumes: - - dwengo_postgres_data:/var/lib/postgresql/data - networks: - - dwengo-1 - - reverse-proxy: - image: traefik:v3.3 - command: > - --api.insecure=true - --providers.docker=true - --providers.docker.exposedbydefault=false - --entrypoints.web.address=:80/tcp - --entrypoints.web.http.redirections.entryPoint.to=websecure - --entrypoints.web.http.redirections.entrypoint.scheme=https - --entrypoints.websecure.address=:443/tcp - --entrypoints.websecure.http.tls=true - --entrypoints.websecure.http.tls.certResolver=letsencrypt - --entrypoints.websecure.http.tls.domains[0].main=sel2-1.ugent.be - --certificatesresolvers.letsencrypt.acme.email=timo.demeyst@ugent.be - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json - --certificatesresolvers.letsencrypt.acme.httpChallenge=true - --certificatesresolvers.letsencrypt.acme.httpChallenge.entrypoint=web - ports: - - '8080:8080' - - '80:80/tcp' - - '443:443/tcp' - restart: unless-stopped - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - - dwengo_letsencrypt:/letsencrypt:ro - networks: - - dwengo-1 - - logging: - image: grafana/loki:latest - ports: - - '3102:3102' - - '9095:9095' - volumes: - - ./config/loki/config.yml:/etc/loki/config.yaml - - dwengo_loki_data:/loki - command: -config.file=/etc/loki/config.yaml - restart: unless-stopped - networks: - - dwengo-1 - labels: - - 'traefik.enable=true' - - 'traefik.http.middlewares.logging-prefix.stripprefix.prefixes=/logging' - - 'traefik.http.routers.web.rule=PathPrefix(`/logging`)' - - 'traefik.http.routers.web.middlewares=logging-prefix' - - 'traefik.http.services.web.loadbalancer.server.port=3102' - - dashboards: - image: grafana/grafana:latest - ports: - - '3100:3000' - volumes: - - dwengo_grafana_data:/var/lib/grafana - restart: unless-stopped - networks: - - dwengo-1 - -volumes: - dwengo_postgres_data: - dwengo_letsencrypt: - dwengo_loki_data: - dwengo_grafana_data: - -networks: - dwengo-1: diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 8d3a0010..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,120 +0,0 @@ -services: - web: - build: - context: . - dockerfile: ./frontend/Dockerfile - ports: - - '8090:80/tcp' - restart: unless-stopped - # networks: - # - dwengo-1 - labels: - - 'traefik.enable=true' - - 'traefik.http.routers.web.rule=PathPrefix(`/`)' - - 'traefik.http.services.web.loadbalancer.server.port=80' - - api: - build: - context: . - dockerfile: ./backend/Dockerfile - ports: - - '3000:3000/tcp' - restart: unless-stopped - volumes: - - ./backend/.env:/app/.env - # networks: - # - dwengo-1 - depends_on: - - db - - logging - labels: - - 'traefik.enable=true' - - 'traefik.http.middlewares.api-prefix.stripprefix.prefixes=/api' - - 'traefik.http.routers.api.rule=PathPrefix(`/api`)' - - 'traefik.http.routers.api.middlewares=api-prefix' - - 'traefik.http.services.api.loadbalancer.server.port=3000' - - db: - image: postgres:latest - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: postgres - ports: - - '5431:5432' - restart: unless-stopped - volumes: - - dwengo_postgres_data:/var/lib/postgresql/data - # networks: - # - dwengo-1 - - reverse-proxy: - image: traefik:v3.3 - command: > - --api.insecure=true - --providers.docker=true - --providers.docker.exposedbydefault=false - --entrypoints.web.address=:80/tcp - ports: - - '8080:8080' - - '80:80/tcp' - # - '443:443/tcp' - restart: unless-stopped - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - - dwengo_letsencrypt:/letsencrypt:ro - # networks: - # - dwengo-1 - - logging: - image: grafana/loki:latest - ports: - - '3102:3102' - - '9095:9095' - volumes: - - ./config/loki/config.yml:/etc/loki/config.yaml - - dwengo_loki_data:/loki - command: -config.file=/etc/loki/config.yaml - restart: unless-stopped - # networks: - # - dwengo-1 - - dashboards: - image: grafana/grafana:latest - ports: - - '3100:3000' - volumes: - - dwengo_grafana_data:/var/lib/grafana - restart: unless-stopped - # networks: - # - dwengo-1 - - idp: # Based on: https://medium.com/@fingervinicius/easy-running-keycloak-with-docker-compose-b0d7a4ee2358 - image: quay.io/keycloak/keycloak:latest - volumes: - - ./idp:/opt/keycloak/data/import - environment: - KC_HOSTNAME: localhost - KC_HOSTNAME_PORT: 7080 - KC_HOSTNAME_STRICT_BACKCHANNEL: 'true' - KC_BOOTSTRAP_ADMIN_USERNAME: admin - KC_BOOTSTRAP_ADMIN_PASSWORD: admin - KC_HEALTH_ENABLED: 'true' - KC_LOG_LEVEL: info - healthcheck: - test: ['CMD', 'curl', '-f', 'http://localhost:7080/health/ready'] - interval: 15s - timeout: 2s - retries: 15 - command: ['start-dev', '--http-port', '7080', '--https-port', '7443', '--import-realm'] - ports: - - '7080:7080' - - '7443:7443' - depends_on: - - db - -volumes: - dwengo_postgres_data: - dwengo_letsencrypt: - dwengo_loki_data: - dwengo_grafana_data: diff --git a/frontend/Dockerfile b/frontend/Dockerfile index b7799a2d..9cbb61ea 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -31,6 +31,6 @@ COPY config/nginx/nginx.conf /etc/nginx/nginx.conf COPY --from=build-stage /app/assets /usr/share/nginx/html/assets COPY --from=build-stage /app/frontend/dist /usr/share/nginx/html -EXPOSE 80 +EXPOSE 8080 CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/src/config.ts b/frontend/src/config.ts index 9feb71b3..3f303846 100644 --- a/frontend/src/config.ts +++ b/frontend/src/config.ts @@ -1,5 +1,5 @@ export const apiConfig = { - baseUrl: window.location.hostname == "localhost" ? "http://localhost:3000" : window.location.origin, + baseUrl: (window.location.hostname === "localhost" && !(window.location.port === '80' || window.location.port === '')) ? "http://localhost:3000/api" : window.location.origin + "/api", }; export const loginRoute = "/login";