Skip to content

Adding results 2025 #114

Adding results 2025

Adding results 2025 #114

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
});
}