diff --git a/.env.example b/.env.example index 64cfa59..3570e6e 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,8 @@ +SERVER_NAME="localdomain.localhost localhost" +HOST_NAME=localdocmain.localhost +SSL_CERT_PATH=/path/to/cert.pem +SSL_KEY_PATH=/path/to/key.pem + BM_POSTGRES_PASSWORD=secret BM_POSTGRES_USER=user BM_POSTGRES_DB=db @@ -8,4 +13,4 @@ BM_SESSION_STORE=memory BM_REDIS_HOST=redis://session SSO_GOOG_CLIENT_ID=goog_sso_id SSO_GOOG_CLIENT_SECRET=goog_sso_secret -DEBUG= +DEBUG=some_feature diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b737f2..053e452 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v3.6.0 with: - node-version: '20.8.1' + node-version: '20.10.0' - name: Install Dependencies run: npm ci - name: Save error log @@ -29,6 +29,8 @@ jobs: path: npm-debug.log - name: Circular Dependencies run: npm run cycles + - name: Lint + run: npm run lint - name: Build Docker env: BM_COOKIE_SECRET: ${{ secrets.BM_COOKIE_SECRET }} @@ -44,5 +46,3 @@ jobs: run: npm run build -w @busmap/common - name: Build UI run: npm run build -w ui - - name: Lint - run: npm run lint diff --git a/Dockerfile b/Dockerfile index 82a6e6f..7c68f14 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,18 +2,16 @@ FROM postgres:16.1-bookworm as postgres COPY packages/api/initdb.d /docker-entrypoint-initdb.d EXPOSE 5432 -FROM adminer:4.8.1 as adminer -EXPOSE 8080 - FROM redis:7.2.3 AS redis EXPOSE 6739 -FROM node:20.9-bookworm-slim AS busmap +FROM node:20.10-bookworm-slim AS busmap WORKDIR /app COPY package-lock.json package.json . COPY packages/api/package.json /app/packages/api/package.json COPY packages/ui/package.json /app/packages/ui/package.json COPY packages/components/package.json /app/packages/components/package.json +COPY packages/common/package.json /app/packages/common/package.json RUN npm config set registry https://registry.npmjs.org/ RUN npm install EXPOSE 3000 5173 9000 @@ -25,25 +23,10 @@ RUN npm run build -w @busmap/components RUN npm run build -w @busmap/common RUN npm run build -w ui -FROM nginx:1.25.2 AS dev -COPY packages/web/nginx.local.conf /etc/nginx/nginx.conf -COPY packages/web/certs/ /etc/nginx/certs/ -COPY packages/web/conf.d/core/ /etc/nginx/conf.d/core/ - -FROM nginx:1.25.2 as stage +FROM nginx:1.25.3 as stage +ARG HOST_NAME=busmap.localhost COPY packages/web/certs/ /etc/nginx/certs/ COPY packages/web/conf.d/core/ /etc/nginx/conf.d/core/ -# Set stage.local.conf as the default server block in the container -COPY packages/web/conf.d/stage.local.conf /etc/nginx/conf.d/default.conf +COPY packages/web/templates/ /etc/nginx/templates/ COPY packages/web/nginx.conf /etc/nginx/nginx.conf -COPY --from=uibuild /app/packages/ui/dist /var/www/busmap.localhost - -FROM nginx:1.25.2 AS web -# core nginx conf -COPY packages/web/nginx.conf /etc/nginx/nginx.conf -# dir nginx confs -COPY packages/web/conf.d/ /etc/nginx/conf.d/ -# ssl certs -COPY packages/web/certs/ /etc/nginx/certs/ -# ui build -COPY --from=uibuild /app/packages/ui/dist /usr/share/nginx/html +COPY --from=uibuild /app/packages/ui/dist /var/www/${HOST_NAME} diff --git a/Dockerfile.override b/Dockerfile.override new file mode 100644 index 0000000..fa8e936 --- /dev/null +++ b/Dockerfile.override @@ -0,0 +1,7 @@ +FROM adminer:4.8.1 as adminer +EXPOSE 8080 + +FROM nginx:1.25.3 AS dev +COPY packages/web/nginx.local.conf /etc/nginx/nginx.conf +COPY packages/web/certs/ /etc/nginx/certs/ +COPY packages/web/conf.d/core/ /etc/nginx/conf.d/core/ diff --git a/compose.override.yaml b/compose.override.yaml new file mode 100644 index 0000000..bcee4a3 --- /dev/null +++ b/compose.override.yaml @@ -0,0 +1,78 @@ +services: + adminer: + depends_on: + - db + build: + context: . + dockerfile: Dockerfile.override + target: adminer + container_name: adminer + ports: + - "8080:8080" + api: + ports: + - "3000:3000" + command: + - npm + - run + - dev + - -w + - api + ui: + depends_on: + - api + build: + context: . + target: busmap + container_name: ui + volumes: + - .:/app + - hoisted_node_modules:/app/node_modules + - common_node_modules:/app/packages/common/node_modules + - ui_node_modules:/app/packages/ui/node_modules + - components_node_modules:/app/packages/components/node_modules + ports: + - "5173:5173" + environment: + API_HOST: ${API_HOST} + command: + - npm + - run + - dev + - -w + - ui + storybook: + build: + context: . + target: busmap + container_name: storybook + volumes: + - .:/app + - hoisted_node_modules:/app/node_modules + - components_node_modules:/app/packages/components/node_modules + ports: + - "9000:9000" + command: + - npm + - run + - storybook + - -w + - "@busmap/components" + dev: + depends_on: + - ui + build: + context: . + dockerfile: Dockerfile.override + target: dev + container_name: dev + ports: + - "80:80" + - "443:443" + stage: + volumes: + - ./packages/ui/dist:/var/www/${HOST_NAME} + +volumes: + ui_node_modules: + components_node_modules: diff --git a/compose.yaml b/compose.yaml index 92bc312..28f2dc6 100644 --- a/compose.yaml +++ b/compose.yaml @@ -12,15 +12,6 @@ services: SSO_GOOG_CLIENT_SECRET: ${SSO_GOOG_CLIENT_SECRET:?err} volumes: - db:/var/lib/postgresql/data - adminer: - depends_on: - - db - build: - context: . - target: adminer - container_name: adminer - ports: - - "8080:8080" session: build: context: . @@ -38,93 +29,37 @@ services: container_name: api volumes: - .:/app - - shared_node_modules:/app/node_modules + - hoisted_node_modules:/app/node_modules + - common_node_modules:/app/packages/common/node_modules - api_node_modules:/app/packages/api/node_modules - ports: - - "3000:3000" env_file: ./packages/api/.env - entrypoint: + command: - npm - run - - dev + - start - -w - api - ui: - depends_on: - - api - build: - context: . - target: busmap - container_name: ui - volumes: - - .:/app - - shared_node_modules:/app/node_modules - - ui_node_modules:/app/packages/ui/node_modules - - components_node_modules:/app/packages/components/node_modules - ports: - - "5173:5173" - environment: - API_HOST: ${API_HOST} - entrypoint: - - npm - - run - - dev - - -w - - ui - storybook: - build: - context: . - target: busmap - container_name: storybook - volumes: - - .:/app - - shared_node_modules:/app/node_modules - - components_node_modules:/app/packages/components/node_modules - ports: - - "9000:9000" - entrypoint: - - npm - - run - - storybook - - -w - - "@busmap/components" - dev: - depends_on: - - ui - build: - context: . - target: dev - container_name: dev - ports: - - "80:80" - - "443:443" stage: depends_on: - api build: context: . target: stage + args: + HOST_NAME: ${HOST_NAME} container_name: stage ports: - "80:80" - "443:443" - volumes: - - ./packages/ui/dist:/var/www/busmap.localhost - web: - depends_on: - - api - build: - context: . - target: web - container_name: web - ports: - - "80:80" - - "443:443" + environment: + SERVER_NAME: ${SERVER_NAME} + HOST_NAME: ${HOST_NAME} + SSL_CERT_PATH: ${SSL_CERT_PATH} + SSL_KEY_PATH: ${SSL_KEY_PATH} volumes: db: session: - shared_node_modules: + hoisted_node_modules: api_node_modules: - ui_node_modules: - components_node_modules: + common_node_modules: diff --git a/docs/development.md b/docs/development.md index 6aef42f..efa7567 100644 --- a/docs/development.md +++ b/docs/development.md @@ -4,10 +4,11 @@ Development is done using Docker and Compose. Quick start: -1. `docker compose build` -2. `docker compose up dev` +1. Rename `.env.example` to `.env` filling in any missing values as needed. +2. `docker compose build` +3. `docker compose up dev` -That will start the `web`, `session`, `api` and `ui` containers. If you have already setup local DNS and [created the SSL certifcates](../packages/web/certs/README.md), then you should be able to reach the site at `https://busmap.localhost`. +That will start the `dev`, `session`, `api` and `ui` containers. If you have already setup local DNS and [created the SSL certifcates](../packages/web/certs/README.md), then you should be able to reach the site at `https://busmap.localhost`. ### Session @@ -61,3 +62,27 @@ A separate container can be started to run Storybook for `packages/components` a It can also be run locally. * `npm run storybook -w @busmap/components` + +### Stage + +To stage a production build locally: + +1. Build the UI which is mounted as a volume into the stage container: `npm run build -w ui`. +2. Set the environment variables to desired values. For example the `HOST_NAME` and `SERVER_NAME` to use, and any others variables inside the `.env` file. You can also define them directly in the shell when running step 3 below. +3. Start the staging container `docker compose up --attach-dependencies stage` (optionally prepending any env vars). + +Note, this is a local stage in that it uses the UI build from ./packages/ui/dist and rebuilds are updated while the container is running. Also, the API is started in watch mode. + +To stage a production build without mounting a local UI build or starting the API in watch mode, do not include the `compose.override.yaml` file when starting the container (which is included by default when not using `-f`): + +``` +docker compose -f compose.yaml up --attach-dependencies stage +``` + +This will use whatever UI build was included in the image for stage. + +Here is another example using shell environment variables defined when starting the container to override ones from .env files: + +``` +SERVER_NAME=busmap.online HOST_NAME=busmap.online docker compose -f compose.yaml up --attach-dependencies stage +``` diff --git a/package.json b/package.json index 6cbdb04..1496b73 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,12 @@ ], "scripts": { "loc": "git ls-files | grep '\\.ts' | xargs wc -l", + "clean": "rm -rf ./node_modules ./dist", + "clean:all": "npm run clean --workspaces --include-workspace-root", "cycles": "npm run cycles --workspaces", "prettier": "npm run prettier --workspaces", "lint": "npm run lint --workspaces", + "build:deps": "npm run build -w @busmap/components && npm run build -w @busmap/common", "test": "echo \"Error: no test specified\" && exit 1" }, "devDependencies": { diff --git a/packages/api/package.json b/packages/api/package.json index b723313..a6d2876 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -5,6 +5,7 @@ "type": "module", "main": "src/index.js", "scripts": { + "clean": "rm -rf ./node_modules ./dist", "check-types": "tsc --noEmit", "cycles": "madge --circular --warning --exclude node_modules --ts-config ./tsconfig.json --extensions ts,tsx src", "prettier": "prettier -w .", diff --git a/packages/common/.prettierignore b/packages/common/.prettierignore new file mode 100644 index 0000000..1521c8b --- /dev/null +++ b/packages/common/.prettierignore @@ -0,0 +1 @@ +dist diff --git a/packages/common/package.json b/packages/common/package.json index 8b8ca9a..1d04f39 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -15,6 +15,7 @@ }, "devDependencies": {}, "scripts": { + "clean": "rm -rf ./node_modules ./dist", "check-types": "tsc --noEmit", "cycles": "madge --circular --warning --exclude node_modules --ts-config ./tsconfig.json --extensions ts,tsx src", "prettier": "prettier -w .", diff --git a/packages/components/package.json b/packages/components/package.json index 311f893..4b8c1ba 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -64,6 +64,7 @@ "sideEffects": false, "types": "./dist/index.d.ts", "scripts": { + "clean": "rm -rf ./node_modules ./dist", "check-types": "tsc --noEmit", "cycles": "madge --circular --warning --exclude node_modules --ts-config ./tsconfig.json --extensions ts,tsx src", "prettier": "prettier -w .", diff --git a/packages/ui/package.json b/packages/ui/package.json index 54e1149..2fff040 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -5,6 +5,7 @@ "type": "module", "main": "dist", "scripts": { + "clean": "rm -rf ./node_modules ./dist", "check-types": "tsc --noEmit", "cycles": "madge --circular --warning --exclude node_modules --ts-config ./tsconfig.json --extensions ts,tsx src", "prettier": "prettier -w .", diff --git a/packages/web/conf.d/core/upstreams.conf b/packages/web/conf.d/core/upstreams.conf index 1d0f631..74e7ac1 100644 --- a/packages/web/conf.d/core/upstreams.conf +++ b/packages/web/conf.d/core/upstreams.conf @@ -1,4 +1,4 @@ - # DNS resolution works at startup via `depends_on` for `web` service in docker-compose.yml. + # DNS resolution works at startup via `depends_on` for `web` service in compose.yaml. upstream api_server { server api:3000; } diff --git a/packages/web/nginx.local.conf b/packages/web/nginx.local.conf index 5fd7f83..de3549c 100644 --- a/packages/web/nginx.local.conf +++ b/packages/web/nginx.local.conf @@ -53,11 +53,6 @@ http { location / { resolver 8.8.8.8 valid=30s; - location ~ /restbus { - access_log /var/log/nginx/api.log api; - proxy_pass http://api_server; - } - proxy_pass http://dev_server; proxy_http_version 1.1; proxy_set_header Host $host; diff --git a/packages/web/package.json b/packages/web/package.json index 6bdee9c..312081f 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -5,6 +5,7 @@ "main": "src/index.js", "devDependencies": {}, "scripts": { + "clean": "rm -rf ./node_modules ./dist", "cycles": "echo Skip cycles for web package...", "prettier": "prettier -w .", "lint": "eslint . src --ext .ts,.tsx --no-error-on-unmatched-pattern", diff --git a/packages/web/templates/core/ssl.conf.template b/packages/web/templates/core/ssl.conf.template new file mode 100644 index 0000000..7975a16 --- /dev/null +++ b/packages/web/templates/core/ssl.conf.template @@ -0,0 +1,7 @@ +ssl_certificate ${SSL_CERT_PATH}; +ssl_certificate_key ${SSL_KEY_PATH}; +ssl_protocols TLSv1.2 TLSv1.3; +ssl_ciphers HIGH:!aNULL:!MD5; +ssl_session_cache shared:SSL:15m; +ssl_session_timeout 3h; +ssl_session_tickets off; diff --git a/packages/web/conf.d/stage.local.conf b/packages/web/templates/default.conf.template similarity index 64% rename from packages/web/conf.d/stage.local.conf rename to packages/web/templates/default.conf.template index 8b502c3..d81b62e 100644 --- a/packages/web/conf.d/stage.local.conf +++ b/packages/web/templates/default.conf.template @@ -1,11 +1,11 @@ # Configuration to stage a production build locally. -# Some minor differences like the SSL certs/config used, api-host (potentially), etc. -# Otherwise, identical to production +# Known differences: +# - TODO (when they arise) server { # Redirect server configuration listen 80 default_server; - server_name busmap.localhost; + server_name ${SERVER_NAME}; server_tokens off; location / { @@ -14,20 +14,19 @@ server { } server { - listen 80; listen 443 ssl; - server_name busmap.localhost localhost; - # Using compose volume to populate in container (packages/ui/dist) - root /var/www/busmap.localhost; + http2 on; + server_name ${SERVER_NAME}; + root /var/www/${HOST_NAME}; index index.html; - ssl_certificate /etc/nginx/certs/busmap.localhost.pem; - ssl_certificate_key /etc/nginx/certs/busmap.localhost-key.pem; - + include /etc/nginx/conf.d/core/ssl.conf; include /etc/nginx/conf.d/core/compression.conf; location / { - location ~ /restbus { + resolver 8.8.8.8 valid=30s; + + location ~ /(authn|restbus|favorite) { access_log /var/log/nginx/api.log api; proxy_pass http://api_server; }