From 704ad358cb57f20bd38d521e2343ac84f0aee071 Mon Sep 17 00:00:00 2001 From: Mubangizi Allan Date: Wed, 16 Jul 2025 14:03:21 +0300 Subject: [PATCH 1/4] add deployment workflows --- .github/pull_request_template.md | 15 +++ .github/workflows/bin/create_envs.sh | 21 +++ .github/workflows/prod.yml | 192 +++++++++++++++++++++++++++ .github/workflows/test.yml | 24 ++++ README.md | 2 - 5 files changed, 252 insertions(+), 2 deletions(-) create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/bin/create_envs.sh create mode 100644 .github/workflows/prod.yml create mode 100644 .github/workflows/test.yml diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..97e4e4a --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ +# Description + +Please include a summary of what the PR does adding Please relevant motivation and context. + +## Trello Ticket ID + +Please add a link to the Trello ticket for the task if any. + +## Checklist + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] My changes generate no new warnings +- [ ] Any dependent changes have been merged and published in downstream modules diff --git a/.github/workflows/bin/create_envs.sh b/.github/workflows/bin/create_envs.sh new file mode 100644 index 0000000..67fc139 --- /dev/null +++ b/.github/workflows/bin/create_envs.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +set -e + +# env variables +SANITY_STUDIO_PROJECT_ID="${SANITY_STUDIO_PROJECT_ID}" +SANITY_STUDIO_DATASET="${SANITY_STUDIO_DATASET}" + +function create_env_file +{ + echo SANITY_STUDIO_PROJECT_ID=$SANITY_STUDIO_PROJECT_ID >> .env + echo SANITY_STUDIO_DATASET=$SANITY_STUDIO_DATASET >> .env +} + + +function run +{ + create_env_file +} + +run \ No newline at end of file diff --git a/.github/workflows/prod.yml b/.github/workflows/prod.yml new file mode 100644 index 0000000..28f36c5 --- /dev/null +++ b/.github/workflows/prod.yml @@ -0,0 +1,192 @@ +name: Production Deployment + +on: + push: + branches: + - ch-deploy-app + + release: + types: + - released + - prereleased + workflow_dispatch: + +jobs: + build_and_deploy_staging: + outputs: + image: ${{ steps.export.outputs.image }} + tag: ${{ steps.export.outputs.tag }} + release_version: ${{ steps.version.outputs.version }} + + runs-on: ubuntu-latest + env: + image: cranecloud/cranecloud-cms + namespace: cranecloud-prod + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: ch-deploy-app + + - name: Get version + id: version + run: | + if [[ $GITHUB_EVENT_NAME == "release" ]]; then + echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + else + echo "version=dev-$(date +'%Y%m%d')-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + fi + + - name: Update CHANGELOG.md + if: github.event_name == 'release' + run: | + # Get the release version and date + VERSION=${GITHUB_REF#refs/tags/} + DATE=$(date +'%Y-%m-%d') + + # Get the release notes from the GitHub release + RELEASE_NOTES=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/repos/${{ github.repository }}/releases/tags/$VERSION" | \ + jq -r '.body') + + # Create the new version section + NEW_SECTION="## [$VERSION] - $DATE\n\n" + + # Parse the release notes and categorize them + if echo "$RELEASE_NOTES" | grep -q "### Added"; then + NEW_SECTION+="### Added\n" + NEW_SECTION+=$(echo "$RELEASE_NOTES" | sed -n '/### Added/,/^$/p' | sed '1d') + NEW_SECTION+="\n" + fi + + if echo "$RELEASE_NOTES" | grep -q "### Changed"; then + NEW_SECTION+="### Changed\n" + NEW_SECTION+=$(echo "$RELEASE_NOTES" | sed -n '/### Changed/,/^$/p' | sed '1d') + NEW_SECTION+="\n" + fi + + if echo "$RELEASE_NOTES" | grep -q "### Fixed"; then + NEW_SECTION+="### Fixed\n" + NEW_SECTION+=$(echo "$RELEASE_NOTES" | sed -n '/### Fixed/,/^$/p' | sed '1d') + NEW_SECTION+="\n" + fi + + if echo "$RELEASE_NOTES" | grep -q "### Security"; then + NEW_SECTION+="### Security\n" + NEW_SECTION+=$(echo "$RELEASE_NOTES" | sed -n '/### Security/,/^$/p' | sed '1d') + NEW_SECTION+="\n" + fi + + # Update the CHANGELOG.md + awk -v new="$NEW_SECTION" ' + /^## \[Unreleased\]/ { + print; + print ""; + print new; + next; + } + { print } + ' CHANGELOG.md > CHANGELOG.md.new + + mv CHANGELOG.md.new CHANGELOG.md + + # Commit and push the changes + git config --global user.name "GitHub Actions" + git config --global user.email "actions@github.com" + git add CHANGELOG.md + git commit -m "docs: update CHANGELOG.md for $VERSION" + git push + + - name: Install (Buildx) + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login (GCP) + uses: google-github-actions/auth@v2 + with: + credentials_json: ${{ secrets.CREDENTIALS_JSON }} + + - name: Install (Gcloud) + uses: google-github-actions/setup-gcloud@v2 + with: + project_id: crane-cloud-274413 + install_components: 'gke-gcloud-auth-plugin' + + - name: Get Kubernetes credentials + run: | + gcloud container clusters get-credentials staging-cluster --zone us-central1-a + + - id: meta + name: Tag + uses: docker/metadata-action@v3 + with: + flavor: | + latest=auto + prefix= + images: ${{ env.image }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=sha + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + + - name: Add Env vars + env: + SANITY_STUDIO_PROJECT_ID: ${{ secrets.SANITY_STUDIO_PROJECT_ID }} + SANITY_STUDIO_DATASET: ${{ secrets.SANITY_STUDIO_DATASET }} + run: | + chmod +x ./.github/workflows/bin/create_envs.sh + ./.github/workflows/bin/create_envs.sh + + - name: Build + uses: docker/build-push-action@v5 + with: + cache-from: type=gha + cache-to: type=gha,mode=max + context: . + file: docker/prod/Dockerfile + labels: ${{ steps.meta.outputs.labels }} + push: true + tags: ${{ steps.meta.outputs.tags }} + + - id: export + name: Export + uses: actions/github-script@v7 + with: + script: | + const metadata = JSON.parse(`${{ steps.meta.outputs.json }}`) + const fullUrl = metadata.tags.find((t) => t.includes(':sha-')) + if (fullUrl == null) { + core.error('Unable to find sha tag of image') + } else { + const tag = fullUrl.split(':')[1] + core.setOutput('image', fullUrl) + core.setOutput('tag', tag) + } + + - name: Update deployment image + run: | + kubectl set image deployment/cranecloud-cms cranecloud-cms=${{ env.image }}:${{ steps.export.outputs.tag }} -n $namespace + + - name: Verify deployment + run: | + echo "Waiting for deployment to roll out..." + kubectl rollout status deployment/cranecloud-cms -n $namespace --timeout=300s + + echo "Verifying deployment health..." + kubectl get pods -n $namespace -l app=cranecloud-cms -o wide + + # Add basic health check + POD_NAME=$(kubectl get pods -n $namespace -l app=cranecloud-cms -o jsonpath="{.items[0].metadata.name}") + kubectl exec -n $namespace $POD_NAME -- curl -f http://localhost:80/health || exit 1 + + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..d018efa --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,24 @@ +name: Test + +on: + push: + pull_request: + +jobs: + test_and_report: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 23 + + - name: Setup Dependencies + run: yarn + + - name: Run Test + run: yarn eslint diff --git a/README.md b/README.md index 3d57a0e..affeac5 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,6 @@ A modern content management system built with Sanity for managing CraneCloud com - `yarn dev` - Start development server - `yarn start` - Start production server - `yarn build` - Build for production -- `yarn deploy` - Deploy to Sanity -- `yarn deploy-graphql` - Deploy GraphQL API ## 🌐 API Access From dfff655ebcdf62e8dd9c60cde6b9eeb6b0f04fce Mon Sep 17 00:00:00 2001 From: Mubangizi Allan Date: Wed, 16 Jul 2025 14:14:32 +0300 Subject: [PATCH 2/4] add docker file --- .github/workflows/prod.yml | 17 ++++++++--------- Dockerfile | 21 +++++++++++++++++++++ nginx.conf | 10 ++++++++++ 3 files changed, 39 insertions(+), 9 deletions(-) create mode 100644 Dockerfile create mode 100644 nginx.conf diff --git a/.github/workflows/prod.yml b/.github/workflows/prod.yml index 28f36c5..7ea7267 100644 --- a/.github/workflows/prod.yml +++ b/.github/workflows/prod.yml @@ -14,9 +14,9 @@ on: jobs: build_and_deploy_staging: outputs: - image: ${{ steps.export.outputs.image }} - tag: ${{ steps.export.outputs.tag }} - release_version: ${{ steps.version.outputs.version }} + image: ${{ env.IMAGE }} + tag: ${{ env.TAG }} + release_version: ${{ env.version }} runs-on: ubuntu-latest env: @@ -33,9 +33,9 @@ jobs: id: version run: | if [[ $GITHUB_EVENT_NAME == "release" ]]; then - echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV else - echo "version=dev-$(date +'%Y%m%d')-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + echo "version=dev-$(date +'%Y%m%d')-$(git rev-parse --short HEAD)" >> $GITHUB_ENV fi - name: Update CHANGELOG.md @@ -152,7 +152,6 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max context: . - file: docker/prod/Dockerfile labels: ${{ steps.meta.outputs.labels }} push: true tags: ${{ steps.meta.outputs.tags }} @@ -168,13 +167,13 @@ jobs: core.error('Unable to find sha tag of image') } else { const tag = fullUrl.split(':')[1] - core.setOutput('image', fullUrl) - core.setOutput('tag', tag) + core.exportVariable('IMAGE', fullUrl) + core.exportVariable('TAG', tag) } - name: Update deployment image run: | - kubectl set image deployment/cranecloud-cms cranecloud-cms=${{ env.image }}:${{ steps.export.outputs.tag }} -n $namespace + kubectl set image deployment/cranecloud-cms cranecloud-cms=${{ env.image }}:${{ env.TAG }} -n $namespace - name: Verify deployment run: | diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..776fb83 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM node:23-alpine as build_step + +WORKDIR /app + +COPY package.json yarn.lock* ./ +RUN yarn install + +COPY . /app + +RUN yarn build + + +FROM nginx:1.25-alpine as production + +COPY --from=build_step /app/dist /usr/share/nginx/html + +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..0c5aa0e --- /dev/null +++ b/nginx.conf @@ -0,0 +1,10 @@ +server { + listen 80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ /index.html; + } +} \ No newline at end of file From 4f729b4ad77e41e30e3a02ad7fe820b26efd24c2 Mon Sep 17 00:00:00 2001 From: Mubangizi Allan Date: Wed, 16 Jul 2025 14:55:15 +0300 Subject: [PATCH 3/4] fix memetypes issue --- .github/workflows/bin/create_envs.sh | 1 + Dockerfile | 3 ++ README.md | 2 +- nginx.conf | 7 ++++ nginx2.conf | 55 ++++++++++++++++++++++++++++ scripts/deployment.yml | 47 ++++++++++++++++++++++++ 6 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 nginx2.conf create mode 100644 scripts/deployment.yml diff --git a/.github/workflows/bin/create_envs.sh b/.github/workflows/bin/create_envs.sh index 67fc139..1bb69e4 100644 --- a/.github/workflows/bin/create_envs.sh +++ b/.github/workflows/bin/create_envs.sh @@ -10,6 +10,7 @@ function create_env_file { echo SANITY_STUDIO_PROJECT_ID=$SANITY_STUDIO_PROJECT_ID >> .env echo SANITY_STUDIO_DATASET=$SANITY_STUDIO_DATASET >> .env + echo NODE_ENV=production >> .env } diff --git a/Dockerfile b/Dockerfile index 776fb83..bb75b37 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,9 @@ RUN yarn install COPY . /app +# Set production environment variables +ENV NODE_ENV=production + RUN yarn build diff --git a/README.md b/README.md index affeac5..a3860a8 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ A modern content management system built with Sanity for managing CraneCloud com 1. **Clone the repository** ```bash - git clone + git clone https://github.com/crane-cloud/cranecloud-cms.git cd cranecloud-cms ``` diff --git a/nginx.conf b/nginx.conf index 0c5aa0e..eeaf228 100644 --- a/nginx.conf +++ b/nginx.conf @@ -1,6 +1,13 @@ server { listen 80; server_name localhost; + include mime.types; + default_type application/octet-stream; + + # Add MIME type for JavaScript modules + types { + application/javascript js mjs; + } location / { root /usr/share/nginx/html; diff --git a/nginx2.conf b/nginx2.conf new file mode 100644 index 0000000..7bb2b36 --- /dev/null +++ b/nginx2.conf @@ -0,0 +1,55 @@ +server { + listen 80; + server_name localhost; + + root /usr/share/nginx/html; + index index.html; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + # Handle JavaScript modules (.mjs files) + location ~* \.mjs$ { + add_header Content-Type "application/javascript; charset=utf-8"; + add_header Cache-Control "public, max-age=31536000, immutable"; + try_files $uri =404; + } + + # Handle regular JavaScript files + location ~* \.js$ { + add_header Content-Type "application/javascript; charset=utf-8"; + add_header Cache-Control "public, max-age=31536000, immutable"; + try_files $uri =404; + } + + # Handle CSS files + location ~* \.css$ { + add_header Content-Type "text/css; charset=utf-8"; + add_header Cache-Control "public, max-age=31536000, immutable"; + try_files $uri =404; + } + + # Handle static assets + location ~* \.(png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + add_header Cache-Control "public, max-age=31536000, immutable"; + try_files $uri =404; + } + + # Handle Sanity static assets - serve from root + location /static/ { + try_files $uri =404; + add_header Cache-Control "public, max-age=31536000, immutable"; + } + + # Handle all other routes - serve index.html for SPA routing + location / { + try_files $uri $uri/ /index.html; + + # Add security headers for HTML + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-Content-Type-Options "nosniff" always; + } +} \ No newline at end of file diff --git a/scripts/deployment.yml b/scripts/deployment.yml new file mode 100644 index 0000000..1943aa1 --- /dev/null +++ b/scripts/deployment.yml @@ -0,0 +1,47 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cranecloud-cms + namespace: cranecloud-prod + labels: + app: cranecloud-cms +spec: + replicas: 1 + minReadySeconds: 15 + revisionHistoryLimit: 3 + strategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 1 + maxSurge: 1 + selector: + matchLabels: + app: cranecloud-cms + template: + metadata: + labels: + app: cranecloud-cms + spec: + containers: + - name: cranecloud-cms + image: cranecloud/cranecloud-cms + imagePullPolicy: Always + ports: + - containerPort: 80 + name: cranecloud-cms +--- +apiVersion: v1 +kind: Service +metadata: + name: cranecloud-cms + namespace: cranecloud-prod + labels: + app: cranecloud-cms +spec: + type: NodePort + ports: + - port: 80 + protocol: TCP + targetPort: cranecloud-cms + selector: + app: cranecloud-cms From c7566783e7b75ee5d6a3ebf400d37045ca079931 Mon Sep 17 00:00:00 2001 From: Mubangizi Allan Date: Wed, 16 Jul 2025 16:03:50 +0300 Subject: [PATCH 4/4] revert to master branch triggers --- .github/workflows/prod.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/prod.yml b/.github/workflows/prod.yml index 7ea7267..db77a3c 100644 --- a/.github/workflows/prod.yml +++ b/.github/workflows/prod.yml @@ -1,10 +1,6 @@ name: Production Deployment on: - push: - branches: - - ch-deploy-app - release: types: - released @@ -27,7 +23,7 @@ jobs: - name: Checkout code uses: actions/checkout@v4 with: - ref: ch-deploy-app + ref: master - name: Get version id: version