Adding results 2025 #114
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: 'Build, Publish, and Deploy container images' | |
| on: | |
| push: | |
| branches: | |
| - main | |
| tags: | |
| - v* | |
| pull_request: | |
| jobs: | |
| build-and-publish-container-images: | |
| name: 'Build and Publish container images' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v3 | |
| with: | |
| fetch-depth: 0 | |
| - name: Log in to the Docker Hub | |
| uses: docker/login-action@v2 | |
| with: | |
| username: ${{ secrets.DOCKER_USERNAME }} | |
| password: ${{ secrets.DOCKER_PASSWORD }} | |
| - name: Log in to the GitHub Container registry | |
| uses: docker/login-action@v2 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v2 | |
| - name: Setup Node | |
| uses: actions/setup-node@v3 | |
| with: | |
| node-version: 20 | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v2 | |
| with: | |
| version: 8 | |
| - name: Install Dependencies | |
| run: pnpm install | |
| - name: Get API environment files | |
| run: | | |
| curl -H "Authorization: token ${TOKEN}" https://raw.githubusercontent.com/fss-fmi/secrets/main/fmicodes/.env.preview > .env | |
| curl -H "Authorization: token ${TOKEN}" https://raw.githubusercontent.com/fss-fmi/secrets/main/fmicodes/apps/fmicodes-api/.env.preview > ./apps/fmicodes-api/.env | |
| env: | |
| TOKEN: ${{ secrets.FSS_FMI_GITHUB_TOKEN }} | |
| - name: Build images | |
| run: INPUT_GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }} npx nx run-many --target=container --push | |
| deploy-container-images: | |
| if: ${{ false }} # Temporary switch over to manual deployments | |
| name: 'Deploy ${{ matrix.IMAGE_NAME }} container image' | |
| runs-on: ubuntu-latest | |
| needs: build-and-publish-container-images | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| env: | |
| PRODUCTION_PUBLIC_IP: ${{ secrets[matrix.PRODUCTION_PUBLIC_IP] }} | |
| PRODUCTION_SSH_USER: ${{ secrets[matrix.PRODUCTION_SSH_USER] }} | |
| PRODUCTION_SSH_PASSWORD: ${{ secrets[matrix.PRODUCTION_SSH_PASSWORD] }} | |
| PRODUCTION_NGINX_PROXY_MANAGER_EMAIL: ${{ secrets[matrix.PRODUCTION_NGINX_PROXY_MANAGER_EMAIL] }} | |
| PRODUCTION_NGINX_PROXY_MANAGER_PASSWORD: ${{ secrets[matrix.PRODUCTION_NGINX_PROXY_MANAGER_PASSWORD] }} | |
| PREVIEW_PUBLIC_IP: ${{ secrets[matrix.PREVIEW_PUBLIC_IP] }} | |
| PREVIEW_SSH_USER: ${{ secrets[matrix.PREVIEW_SSH_USER] }} | |
| PREVIEW_SSH_PASSWORD: ${{ secrets[matrix.PREVIEW_SSH_PASSWORD] }} | |
| PREVIEW_NGINX_PROXY_MANAGER_EMAIL: ${{ secrets[matrix.PREVIEW_NGINX_PROXY_MANAGER_EMAIL] }} | |
| PREVIEW_NGINX_PROXY_MANAGER_PASSWORD: ${{ secrets[matrix.PREVIEW_NGINX_PROXY_MANAGER_PASSWORD] }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - IMAGE_NAME: 'fmicodes-api' | |
| PRODUCTION_PUBLIC_IP: 'FMICODES_API_PRODUCTION_PUBLIC_IP' | |
| PRODUCTION_SSH_USER: 'FMICODES_API_PRODUCTION_SSH_USER' | |
| PRODUCTION_SSH_PASSWORD: 'FMICODES_API_PRODUCTION_SSH_PASSWORD' | |
| PRODUCTION_NGINX_PROXY_MANAGER_EMAIL: 'FMICODES_API_PRODUCTION_NGINX_PROXY_MANAGER_EMAIL' | |
| PRODUCTION_NGINX_PROXY_MANAGER_PASSWORD: 'FMICODES_API_PRODUCTION_NGINX_PROXY_MANAGER_PASSWORD' | |
| PREVIEW_PUBLIC_IP: 'FMICODES_API_PREVIEW_PUBLIC_IP' | |
| PREVIEW_SSH_USER: 'FMICODES_API_PREVIEW_SSH_USER' | |
| PREVIEW_SSH_PASSWORD: 'FMICODES_API_PREVIEW_SSH_PASSWORD' | |
| PREVIEW_NGINX_PROXY_MANAGER_EMAIL: 'FMICODES_API_PREVIEW_NGINX_PROXY_MANAGER_EMAIL' | |
| PREVIEW_NGINX_PROXY_MANAGER_PASSWORD: 'FMICODES_API_PREVIEW_NGINX_PROXY_MANAGER_PASSWORD' | |
| - IMAGE_NAME: 'fmicodes-discord' | |
| PRODUCTION_PUBLIC_IP: 'FMICODES_DISCORD_PRODUCTION_PUBLIC_IP' | |
| PRODUCTION_SSH_USER: 'FMICODES_DISCORD_PRODUCTION_SSH_USER' | |
| PRODUCTION_SSH_PASSWORD: 'FMICODES_DISCORD_PRODUCTION_SSH_PASSWORD' | |
| PREVIEW_PUBLIC_IP: 'FMICODES_DISCORD_PREVIEW_PUBLIC_IP' | |
| PREVIEW_SSH_USER: 'FMICODES_DISCORD_PREVIEW_SSH_USER' | |
| PREVIEW_SSH_PASSWORD: 'FMICODES_DISCORD_PREVIEW_SSH_PASSWORD' | |
| steps: | |
| - name: Set environment variables | |
| run: | | |
| # If the deployment is started by: | |
| # - a pull requst -> set the image tag to be "pr-{PR_ID}" | |
| # - a push to main -> set the image tag to be "main" | |
| # - a push of a version tag -> set the image tag to be "latest" | |
| if [ -n "${{ github.event.pull_request }}" ]; then | |
| IMAGE_TAG="pr-${{ github.event.number }}" | |
| PUBLIC_IP="${{ env.PREVIEW_PUBLIC_IP }}" | |
| SSH_USER="${{ env.PREVIEW_SSH_USER }}" | |
| SSH_PASSWORD="${{ env.PREVIEW_SSH_PASSWORD }}" | |
| NGINX_PROXY_MANAGER_EMAIL="${{ env.PREVIEW_NGINX_PROXY_MANAGER_EMAIL }}" | |
| NGINX_PROXY_MANAGER_PASSWORD="${{ env.PREVIEW_NGINX_PROXY_MANAGER_PASSWORD }}" | |
| CONTAINER_NAME="${{ matrix.IMAGE_NAME }}-pr-${{ github.event.number }}" | |
| CONTAINER_PORT=`expr 4000 + ${{ github.event.number }}` | |
| API_DOMAIN="pr-${{ github.event.number }}-api.fmicodes.com" | |
| ENV_FILE=".env.preview" | |
| elif [ "${{ github.ref }}" = "refs/heads/main" ]; then | |
| IMAGE_TAG="main" | |
| PUBLIC_IP="${{ env.PREVIEW_PUBLIC_IP }}" | |
| SSH_USER="${{ env.PREVIEW_SSH_USER }}" | |
| SSH_PASSWORD="${{ env.PREVIEW_SSH_PASSWORD }}" | |
| NGINX_PROXY_MANAGER_EMAIL="${{ env.PREVIEW_NGINX_PROXY_MANAGER_EMAIL }}" | |
| NGINX_PROXY_MANAGER_PASSWORD="${{ env.PREVIEW_NGINX_PROXY_MANAGER_PASSWORD }}" | |
| CONTAINER_NAME="main" | |
| CONTAINER_PORT="4000" | |
| API_DOMAIN="preview-api.fmicodes.com" | |
| ENV_FILE=".env.preview" | |
| elif [ "${{ github.ref }}" = "refs/tags/${{ github.ref }}" ]; then | |
| IMAGE_TAG="latest" | |
| PUBLIC_IP="${{ env.PRODUCTION_PUBLIC_IP }}" | |
| SSH_USER="${{ env.PRODUCTION_SSH_USER }}" | |
| SSH_PASSWORD="${{ env.PRODUCTION_SSH_PASSWORD }}" | |
| NGINX_PROXY_MANAGER_EMAIL="${{ env.PRODUCTION_NGINX_PROXY_MANAGER_EMAIL }}" | |
| NGINX_PROXY_MANAGER_PASSWORD="${{ env.PRODUCTION_NGINX_PROXY_MANAGER_PASSWORD }}" | |
| CONTAINER_NAME="latest" | |
| CONTAINER_PORT="3000" | |
| API_DOMAIN=api.fmicodes.com | |
| ENV_FILE=".env.production" | |
| fi | |
| echo IMAGE_TAG="$IMAGE_TAG" >> $GITHUB_ENV | |
| echo PUBLIC_IP="$PUBLIC_IP" >> $GITHUB_ENV | |
| echo SSH_USER="$SSH_USER" >> $GITHUB_ENV | |
| echo SSH_PASSWORD="$SSH_PASSWORD" >> $GITHUB_ENV | |
| echo NGINX_PROXY_MANAGER_EMAIL="$NGINX_PROXY_MANAGER_EMAIL" >> $GITHUB_ENV | |
| echo NGINX_PROXY_MANAGER_PASSWORD="$NGINX_PROXY_MANAGER_PASSWORD" >> $GITHUB_ENV | |
| echo CONTAINER_NAME="$CONTAINER_NAME" >> $GITHUB_ENV | |
| echo CONTAINER_PORT="$CONTAINER_PORT" >> $GITHUB_ENV | |
| echo API_DOMAIN="$API_DOMAIN" >> $GITHUB_ENV | |
| echo ENV_FILE="$ENV_FILE" >> $GITHUB_ENV | |
| - name: Deploy container image | |
| uses: appleboy/ssh-action@v1.0.3 | |
| with: | |
| host: ${{ env.PUBLIC_IP }} | |
| username: ${{ env.SSH_USER }} | |
| password: ${{ env.SSH_PASSWORD }} | |
| script: | | |
| # Pull and run the container image | |
| cd /opt/deployment | |
| docker pull ghcr.io/fss-fmi/${{ matrix.IMAGE_NAME }}:${{ env.IMAGE_TAG }} | |
| docker stop ${{ matrix.IMAGE_NAME }}-${{ env.IMAGE_TAG }} || true | |
| docker rm ${{ matrix.IMAGE_NAME }}-${{ env.IMAGE_TAG }} || true | |
| # If running fmicodes_discord preview container, remove other preview containers | |
| if [ "${{ matrix.IMAGE_NAME }}" = "fmicodes-discord" ] && [ "${{ env.IMAGE_TAG }}" = "pr-${{ github.event.number }}" ]; then | |
| docker ps -a | grep "fmicodes-discord-pr-" | awk '{print $1}' | |
| docker ps -a | grep "fmicodes-discord-pr-" | awk '{print $1}' | xargs -I {} docker rm {} | |
| fi | |
| # if running fmicodes_discord container, don't expose the port | |
| if [ "${{ matrix.IMAGE_NAME }}" = "fmicodes-discord" ]; then | |
| docker run -d --name ${{ matrix.IMAGE_NAME }}-${{ env.IMAGE_TAG }} --env-file ${{ env.ENV_FILE }} --net deployment ghcr.io/fss-fmi/${{ matrix.IMAGE_NAME }}:${{ env.IMAGE_TAG }} | |
| else | |
| docker run -d --name ${{ matrix.IMAGE_NAME }}-${{ env.IMAGE_TAG }} --env-file ${{ env.ENV_FILE }} -p ${{ env.CONTAINER_PORT }}:3000 -v ./uploads:/usr/src/app/uploads --net deployment ghcr.io/fss-fmi/${{ matrix.IMAGE_NAME }}:${{ env.IMAGE_TAG }} | |
| fi | |
| - name: Add reverse proxy configuration for API containers | |
| uses: appleboy/ssh-action@v1.0.3 | |
| if: ${{ matrix.IMAGE_NAME == 'fmicodes-api' }} | |
| with: | |
| host: ${{ env.PUBLIC_IP }} | |
| username: ${{ env.SSH_USER }} | |
| password: ${{ env.SSH_PASSWORD }} | |
| script: | | |
| # Log in to Nginx Proxy Manager | |
| TOKEN=$(curl 'http://localhost:81/api/tokens' \ | |
| -H 'Content-Type: application/json; charset=UTF-8' \ | |
| --data-raw '{"identity":"${{ env.NGINX_PROXY_MANAGER_EMAIL }}","secret":"${{ env.NGINX_PROXY_MANAGER_PASSWORD }}"}' \ | |
| | jq -r '.token') | |
| # Check if the API container is already added to the reverse proxy and exit if it is | |
| curl "http://localhost:81/api/nginx/proxy-hosts" \ | |
| -H "Authorization: Bearer $TOKEN" \ | |
| -H "Content-Type: application/json; charset=UTF-8" \ | |
| | grep -q ${{ env.API_DOMAIN }} && exit 0 | |
| # Add reverse proxy configuration for the API container | |
| curl "http://localhost:81/api/nginx/proxy-hosts" \ | |
| -H "Authorization: Bearer $TOKEN" \ | |
| -H "Content-Type: application/json; charset=UTF-8" \ | |
| --data-raw '{"domain_names":["${{ env.API_DOMAIN }}"],"forward_scheme":"http","forward_host":"${{ env.PUBLIC_IP }}","forward_port":${{ env.CONTAINER_PORT }},"block_exploits":true,"allow_websocket_upgrade":true,"access_list_id":"0","certificate_id":1,"ssl_forced":true,"meta":{"letsencrypt_agree":false,"dns_challenge":false},"advanced_config":"","locations":[],"caching_enabled":false,"http2_support":false,"hsts_enabled":false,"hsts_subdomains":false}' | |
| - name: Add comment to the pull request | |
| if: "${{ github.event_name == 'pull_request' && matrix.IMAGE_NAME == 'fmicodes-api' }}" | |
| uses: actions/github-script@v5 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| }); | |
| const botComment = comments.find(comment => { | |
| return comment.user.login === 'github-actions[bot]' && comment.body.includes('The latest API deployment has been completed'); | |
| }); | |
| const output = `#### API Deployment | |
| The latest API deployment has been completed. | |
| | Name | Status | Preview | Updated | | |
| |:-----------------|:----------|:---------------------------------------------------|:-------------------------------| | |
| | **fmicodes-api** | ✅ Ready | [Visit Preview](https://${{ env.API_DOMAIN }}/api) | ${new Date().toLocaleString()} | | |
| `; | |
| if (botComment) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: botComment.id, | |
| body: output | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: context.issue.number, | |
| body: output | |
| }); | |
| } |