diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml index c0d2591..e86f72e 100644 --- a/.github/workflows/deploy-production.yml +++ b/.github/workflows/deploy-production.yml @@ -1,175 +1,16 @@ name: Deploy Production on: -# push: -# branches: [main] workflow_dispatch: inputs: - imageTag: - description: 'Docker image tag to deploy' + api_base_url: + description: 'API base URL for Electron apps' required: false - default: 'latest' - -env: - AZURE_RESOURCE_GROUP: rg-raysoncv-production - LOCATION: uksouth - ACR_NAME: acrraysoncvproduction - API_IMAGE_NAME: raysoncv-api - UI_IMAGE_NAME: raysoncv-ui - OLLAMA_IMAGE_NAME: raysoncv-ollama + default: 'https://api.rayson.dev/' jobs: - build: - runs-on: ubuntu-latest - outputs: - imageTag: ${{ steps.set-tag.outputs.imageTag }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup .NET SDK - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '8.0.x' - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20.x' - cache: 'npm' - cache-dependency-path: Rayson.CV/UI/package-lock.json - - - name: Build API - run: dotnet build Rayson.CV/Api/Presentation/Presentation.csproj --configuration Release - - - name: Install UI dependencies - run: cd Rayson.CV/UI && npm ci - - - name: Build UI - run: cd Rayson.CV/UI && npm run build - - - name: Set image tag - id: set-tag - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "${{ github.event.inputs.imageTag }}" ]; then - echo "imageTag=${{ github.event.inputs.imageTag }}" >> $GITHUB_OUTPUT - else - echo "imageTag=${{ github.sha }}" >> $GITHUB_OUTPUT - fi - - deploy-core: - runs-on: ubuntu-latest - needs: build - outputs: - acrLoginServer: ${{ steps.deploy.outputs.acrLoginServer }} - acrName: ${{ steps.deploy.outputs.acrName }} - caeId: ${{ steps.deploy.outputs.caeId }} - caeName: ${{ steps.deploy.outputs.caeName }} - defaultDomain: ${{ steps.deploy.outputs.defaultDomain }} - storageAccountName: ${{ steps.deploy.outputs.storageAccountName }} - storageAccountKey: ${{ steps.deploy.outputs.storageAccountKey }} - postgresFqdn: ${{ steps.deploy.outputs.postgresFqdn }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Azure Login - uses: azure/login@v1 - with: - creds: ${{ secrets.PROD_AZURE_CREDENTIALS }} - - - name: Deploy core infrastructure - id: deploy - run: | - az deployment sub create \ - --name raysoncv-production-core \ - --location ${{ env.LOCATION }} \ - --template-file infra/main-core.bicep \ - --parameters location=${{ env.LOCATION }} \ - resourceGroupName=${{ env.AZURE_RESOURCE_GROUP }} \ - environmentName=production \ - acrName=${{ env.ACR_NAME }} \ - postgresAdminUsername=raysoncvadmin \ - postgresAdminPassword="${{ secrets.PROD_POSTGRES_PASSWORD }}" - - ACR_LOGIN_SERVER=$(az deployment sub show --name raysoncv-production-core --query properties.outputs.acrLoginServer.value --output tsv) - ACR_NAME_OUT=$(az deployment sub show --name raysoncv-production-core --query properties.outputs.acrName.value --output tsv) - CAE_ID=$(az deployment sub show --name raysoncv-production-core --query properties.outputs.containerAppEnvId.value --output tsv) - CAE_NAME=$(az deployment sub show --name raysoncv-production-core --query properties.outputs.containerAppEnvName.value --output tsv) - DEFAULT_DOMAIN=$(az deployment sub show --name raysoncv-production-core --query properties.outputs.defaultDomain.value --output tsv) - STORAGE_ACCOUNT_NAME=$(az deployment sub show --name raysoncv-production-core --query properties.outputs.storageAccountName.value --output tsv) - STORAGE_ACCOUNT_KEY=$(az deployment sub show --name raysoncv-production-core --query properties.outputs.storageAccountKey.value --output tsv) - POSTGRES_FQDN=$(az deployment sub show --name raysoncv-production-core --query properties.outputs.postgresFqdn.value --output tsv) - - echo "acrLoginServer=$ACR_LOGIN_SERVER" >> $GITHUB_OUTPUT - echo "acrName=$ACR_NAME_OUT" >> $GITHUB_OUTPUT - echo "caeId=$CAE_ID" >> $GITHUB_OUTPUT - echo "caeName=$CAE_NAME" >> $GITHUB_OUTPUT - echo "defaultDomain=$DEFAULT_DOMAIN" >> $GITHUB_OUTPUT - echo "storageAccountName=$STORAGE_ACCOUNT_NAME" >> $GITHUB_OUTPUT - echo "storageAccountKey=$STORAGE_ACCOUNT_KEY" >> $GITHUB_OUTPUT - echo "postgresFqdn=$POSTGRES_FQDN" >> $GITHUB_OUTPUT - - push: - runs-on: ubuntu-latest - needs: [build, deploy-core] - outputs: - apiFqdn: ${{ steps.set-fqdn.outputs.apiFqdn }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Azure Login - uses: azure/login@v1 - with: - creds: ${{ secrets.PROD_AZURE_CREDENTIALS }} - - - name: Login to Azure Container Registry - run: az acr login --name ${{ env.ACR_NAME }} - - - name: Build and push API image - run: | - docker build \ - -t ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.API_IMAGE_NAME }}:${{ needs.build.outputs.imageTag }} \ - -t ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.API_IMAGE_NAME }}:latest \ - ./Rayson.CV/Api - docker push ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.API_IMAGE_NAME }}:${{ needs.build.outputs.imageTag }} - docker push ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.API_IMAGE_NAME }}:latest - - - name: Build and push UI image - run: | - API_FQDN="ca-api-production.${{ needs.deploy-core.outputs.defaultDomain }}" - docker build \ - --build-arg VITE_API_BASE_URL=https://$API_FQDN/ \ - -t ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.UI_IMAGE_NAME }}:${{ needs.build.outputs.imageTag }} \ - -t ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.UI_IMAGE_NAME }}:latest \ - ./Rayson.CV/UI - docker push ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.UI_IMAGE_NAME }}:${{ needs.build.outputs.imageTag }} - docker push ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.UI_IMAGE_NAME }}:latest - - echo "apiFqdn=$API_FQDN" >> $GITHUB_OUTPUT - - - name: Build and push Ollama image - run: | - docker build \ - -t ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.OLLAMA_IMAGE_NAME }}:${{ needs.build.outputs.imageTag }} \ - -t ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.OLLAMA_IMAGE_NAME }}:latest \ - -f Rayson.CV/ollama.Dockerfile \ - Rayson.CV - docker push ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.OLLAMA_IMAGE_NAME }}:${{ needs.build.outputs.imageTag }} - docker push ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.OLLAMA_IMAGE_NAME }}:latest - - - name: Set FQDN output - id: set-fqdn - run: | - echo "apiFqdn=ca-api-production.${{ needs.deploy-core.outputs.defaultDomain }}" >> $GITHUB_OUTPUT - build-electron-linux: runs-on: ubuntu-latest - needs: [build, deploy-core] steps: - name: Checkout code @@ -187,8 +28,8 @@ jobs: - name: Build Electron app run: | - API_FQDN="https://ca-api-production.${{ needs.deploy-core.outputs.defaultDomain }}/" - cd Rayson.CV/UI && VITE_API_BASE_URL=$API_FQDN npm run electron:build -- --linux + API_URL="${{ github.event.inputs.api_base_url || 'https://api.rayson.dev/' }}" + cd Rayson.CV/UI && VITE_API_BASE_URL=$API_URL npm run electron:build -- --linux - name: Upload artifact uses: actions/upload-artifact@v4 @@ -198,7 +39,6 @@ jobs: build-electron-mac: runs-on: macos-latest - needs: [build, deploy-core] steps: - name: Checkout code @@ -216,9 +56,8 @@ jobs: - name: Build Electron app run: | - apiFqdn="https://ca-api-production.${{ needs.deploy-core.outputs.defaultDomain }}/" - cd Rayson.CV/UI - VITE_API_BASE_URL=$apiFqdn npm run electron:build -- --mac + API_URL="${{ github.event.inputs.api_base_url || 'https://api.rayson.dev/' }}" + cd Rayson.CV/UI && VITE_API_BASE_URL=$API_URL npm run electron:build -- --mac - name: Upload artifact uses: actions/upload-artifact@v4 @@ -228,7 +67,6 @@ jobs: build-electron-windows: runs-on: windows-latest - needs: [build, deploy-core] steps: - name: Checkout code @@ -246,9 +84,9 @@ jobs: - name: Build Electron app run: | - $apiFqdn = "https://ca-api-production.${{ needs.deploy-core.outputs.defaultDomain }}/" + $apiUrl = "${{ github.event.inputs.api_base_url || 'https://api.rayson.dev/' }}" cd Rayson.CV/UI - $env:VITE_API_BASE_URL=$apiFqdn + $env:VITE_API_BASE_URL=$apiUrl npm run electron:build -- --win - name: Upload artifact @@ -256,55 +94,3 @@ jobs: with: name: electron-windows path: Rayson.CV/UI/build/*.exe - - deploy-apps: - runs-on: ubuntu-latest - needs: [build, deploy-core, push] - outputs: - apiFqdn: ${{ steps.deploy.outputs.apiFqdn }} - uiFqdn: ${{ steps.deploy.outputs.uiFqdn }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Azure Login - uses: azure/login@v1 - with: - creds: ${{ secrets.PROD_AZURE_CREDENTIALS }} - - - name: Deploy container apps - id: deploy - run: | - az deployment group create \ - --name raysoncv-production-apps \ - --resource-group ${{ env.AZURE_RESOURCE_GROUP }} \ - --template-file infra/main-apps.bicep \ - --parameters location=${{ env.LOCATION }} \ - caeId=${{ needs.deploy-core.outputs.caeId }} \ - caeName=${{ needs.deploy-core.outputs.caeName }} \ - defaultDomain=${{ needs.deploy-core.outputs.defaultDomain }} \ - acrLoginServer=${{ needs.deploy-core.outputs.acrLoginServer }} \ - acrName=${{ needs.deploy-core.outputs.acrName }} \ - imageTag=${{ needs.build.outputs.imageTag }} \ - environmentName=production \ - postgresFqdn=${{ needs.deploy-core.outputs.postgresFqdn }} \ - postgresAdminUsername=raysoncvadmin \ - postgresAdminPassword="${{ secrets.PROD_POSTGRES_PASSWORD }}" \ - logLevel=Information \ - customDomainName=rayson.dev - - API_FQDN=$(az deployment group show --name raysoncv-production-apps --resource-group ${{ env.AZURE_RESOURCE_GROUP }} --query properties.outputs.apiFqdn.value --output tsv) - UI_FQDN=$(az deployment group show --name raysoncv-production-apps --resource-group ${{ env.AZURE_RESOURCE_GROUP }} --query properties.outputs.uiFqdn.value --output tsv) - - echo "apiFqdn=$API_FQDN" >> $GITHUB_OUTPUT - echo "uiFqdn=$UI_FQDN" >> $GITHUB_OUTPUT - - - name: Deployment Summary - run: | - echo "## Production Deployment Complete" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Service | URL |" >> $GITHUB_STEP_SUMMARY - echo "|---------|-----|" >> $GITHUB_STEP_SUMMARY - echo "| API | https://${{ steps.deploy.outputs.apiFqdn }} |" >> $GITHUB_STEP_SUMMARY - echo "| UI | https://${{ steps.deploy.outputs.uiFqdn }} |" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml deleted file mode 100644 index 0c2fdae..0000000 --- a/.github/workflows/deploy-staging.yml +++ /dev/null @@ -1,343 +0,0 @@ -name: Deploy Staging - -on: -# push: -# branches: [develop] - workflow_dispatch: - inputs: - imageTag: - description: 'Docker image tag to deploy' - required: false - default: 'latest' - -env: - AZURE_RESOURCE_GROUP: rg-raysoncv-staging - LOCATION: uksouth - ACR_NAME: acrraysoncvstaging - API_IMAGE_NAME: raysoncv-api - UI_IMAGE_NAME: raysoncv-ui - OLLAMA_IMAGE_NAME: raysoncv-ollama - -jobs: - build: - runs-on: ubuntu-latest - outputs: - imageTag: ${{ steps.set-tag.outputs.imageTag }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup .NET SDK - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '8.0.x' - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20.x' - cache: 'npm' - cache-dependency-path: Rayson.CV/UI/package-lock.json - - - name: Build API - run: dotnet build Rayson.CV/Api/Presentation/Presentation.csproj --configuration Release - - - name: Install UI dependencies - run: cd Rayson.CV/UI && npm ci - - - name: Build UI - run: cd Rayson.CV/UI && npm run build - - - name: Set image tag - id: set-tag - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "${{ github.event.inputs.imageTag }}" ]; then - echo "imageTag=${{ github.event.inputs.imageTag }}" >> $GITHUB_OUTPUT - else - echo "imageTag=${{ github.sha }}" >> $GITHUB_OUTPUT - fi - - deploy-core: - runs-on: ubuntu-latest - needs: build - outputs: - acrLoginServer: ${{ steps.deploy.outputs.acrLoginServer }} - acrName: ${{ steps.deploy.outputs.acrName }} - caeId: ${{ steps.deploy.outputs.caeId }} - caeName: ${{ steps.deploy.outputs.caeName }} - defaultDomain: ${{ steps.deploy.outputs.defaultDomain }} - storageAccountName: ${{ steps.deploy.outputs.storageAccountName }} - storageAccountKey: ${{ steps.deploy.outputs.storageAccountKey }} - postgresFqdn: ${{ steps.deploy.outputs.postgresFqdn }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Azure Login - uses: azure/login@v1 - with: - creds: ${{ secrets.STAGING_AZURE_CREDENTIALS }} - - - name: Deploy core infrastructure - id: deploy - run: | - az deployment sub create \ - --name raysoncv-staging-core \ - --location ${{ env.LOCATION }} \ - --template-file infra/main-core.bicep \ - --parameters location=${{ env.LOCATION }} \ - resourceGroupName=${{ env.AZURE_RESOURCE_GROUP }} \ - environmentName=staging \ - acrName=${{ env.ACR_NAME }} \ - postgresAdminUsername=raysoncvadmin \ - postgresAdminPassword="${{ secrets.STAGING_POSTGRES_PASSWORD }}" - - ACR_LOGIN_SERVER=$(az deployment sub show --name raysoncv-staging-core --query properties.outputs.acrLoginServer.value --output tsv) - ACR_NAME_OUT=$(az deployment sub show --name raysoncv-staging-core --query properties.outputs.acrName.value --output tsv) - CAE_ID=$(az deployment sub show --name raysoncv-staging-core --query properties.outputs.containerAppEnvId.value --output tsv) - CAE_NAME=$(az deployment sub show --name raysoncv-staging-core --query properties.outputs.containerAppEnvName.value --output tsv) - DEFAULT_DOMAIN=$(az deployment sub show --name raysoncv-staging-core --query properties.outputs.defaultDomain.value --output tsv) - STORAGE_ACCOUNT_NAME=$(az deployment sub show --name raysoncv-staging-core --query properties.outputs.storageAccountName.value --output tsv) - STORAGE_ACCOUNT_KEY=$(az deployment sub show --name raysoncv-staging-core --query properties.outputs.storageAccountKey.value --output tsv) - POSTGRES_FQDN=$(az deployment sub show --name raysoncv-staging-core --query properties.outputs.postgresFqdn.value --output tsv) - - echo "acrLoginServer=$ACR_LOGIN_SERVER" >> $GITHUB_OUTPUT - echo "acrName=$ACR_NAME_OUT" >> $GITHUB_OUTPUT - echo "caeId=$CAE_ID" >> $GITHUB_OUTPUT - echo "caeName=$CAE_NAME" >> $GITHUB_OUTPUT - echo "defaultDomain=$DEFAULT_DOMAIN" >> $GITHUB_OUTPUT - echo "storageAccountName=$STORAGE_ACCOUNT_NAME" >> $GITHUB_OUTPUT - echo "storageAccountKey=$STORAGE_ACCOUNT_KEY" >> $GITHUB_OUTPUT - echo "postgresFqdn=$POSTGRES_FQDN" >> $GITHUB_OUTPUT - - push: - runs-on: ubuntu-latest - needs: [build, deploy-core] - outputs: - apiFqdn: ${{ steps.set-fqdn.outputs.apiFqdn }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Azure Login - uses: azure/login@v1 - with: - creds: ${{ secrets.STAGING_AZURE_CREDENTIALS }} - - - name: Login to Azure Container Registry - run: az acr login --name ${{ env.ACR_NAME }} - - - name: Build and push API image - run: | - docker build \ - -t ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.API_IMAGE_NAME }}:${{ needs.build.outputs.imageTag }} \ - -t ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.API_IMAGE_NAME }}:latest \ - ./Rayson.CV/Api - docker push ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.API_IMAGE_NAME }}:${{ needs.build.outputs.imageTag }} - docker push ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.API_IMAGE_NAME }}:latest - - - name: Build and push UI image - run: | - API_FQDN="ca-api-staging.${{ needs.deploy-core.outputs.defaultDomain }}" - docker build \ - --build-arg VITE_API_BASE_URL=https://$API_FQDN/ \ - -t ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.UI_IMAGE_NAME }}:${{ needs.build.outputs.imageTag }} \ - -t ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.UI_IMAGE_NAME }}:latest \ - ./Rayson.CV/UI - docker push ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.UI_IMAGE_NAME }}:${{ needs.build.outputs.imageTag }} - docker push ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.UI_IMAGE_NAME }}:latest - - echo "apiFqdn=$API_FQDN" >> $GITHUB_OUTPUT - - - name: Build and push Ollama image - run: | - docker build \ - -t ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.OLLAMA_IMAGE_NAME }}:${{ needs.build.outputs.imageTag }} \ - -t ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.OLLAMA_IMAGE_NAME }}:latest \ - -f Rayson.CV/ollama.Dockerfile \ - Rayson.CV - docker push ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.OLLAMA_IMAGE_NAME }}:${{ needs.build.outputs.imageTag }} - docker push ${{ needs.deploy-core.outputs.acrLoginServer }}/${{ env.OLLAMA_IMAGE_NAME }}:latest - - - name: Set FQDN output - id: set-fqdn - run: | - echo "apiFqdn=ca-api-staging.${{ needs.deploy-core.outputs.defaultDomain }}" >> $GITHUB_OUTPUT - - build-electron-linux: - runs-on: ubuntu-latest - needs: [build, deploy-core] - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20.x' - cache: 'npm' - cache-dependency-path: Rayson.CV/UI/package-lock.json - - - name: Install dependencies - run: cd Rayson.CV/UI && npm ci - - - name: Build Electron app - run: | - API_FQDN="https://ca-api-staging.${{ needs.deploy-core.outputs.defaultDomain }}/" - cd Rayson.CV/UI && VITE_API_BASE_URL=$API_FQDN npm run electron:build -- --linux - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: electron-linux - path: Rayson.CV/UI/build/*.AppImage - - build-electron-mac: - runs-on: macos-latest - needs: [build, deploy-core] - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20.x' - cache: 'npm' - cache-dependency-path: Rayson.CV/UI/package-lock.json - - - name: Install dependencies - run: cd Rayson.CV/UI && npm ci - - - name: Build Electron app - run: | - apiFqdn="https://ca-api-staging.${{ needs.deploy-core.outputs.defaultDomain }}/" - cd Rayson.CV/UI - VITE_API_BASE_URL=$apiFqdn npm run electron:build -- --mac - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: electron-mac - path: Rayson.CV/UI/build/*.dmg - - build-electron-windows: - runs-on: windows-latest - needs: [build, deploy-core] - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20.x' - cache: 'npm' - cache-dependency-path: Rayson.CV/UI/package-lock.json - - - name: Install dependencies - run: cd Rayson.CV/UI && npm ci - - - name: Build Electron app - run: | - $apiFqdn = "https://ca-api-staging.${{ needs.deploy-core.outputs.defaultDomain }}/" - cd Rayson.CV/UI - $env:VITE_API_BASE_URL=$apiFqdn - npm run electron:build -- --win - - - name: Upload artifact - uses: actions/upload-artifact@v4 - with: - name: electron-windows - path: Rayson.CV/UI/build/*.exe - - deploy-apps: - runs-on: ubuntu-latest - needs: [build, deploy-core, push] - outputs: - apiFqdn: ${{ steps.deploy.outputs.apiFqdn }} - uiFqdn: ${{ steps.deploy.outputs.uiFqdn }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Azure Login - uses: azure/login@v1 - with: - creds: ${{ secrets.STAGING_AZURE_CREDENTIALS }} - - - name: Deploy container apps - id: deploy - run: | - az deployment group create \ - --name raysoncv-staging-apps \ - --resource-group ${{ env.AZURE_RESOURCE_GROUP }} \ - --template-file infra/main-apps.bicep \ - --parameters location=${{ env.LOCATION }} \ - caeId=${{ needs.deploy-core.outputs.caeId }} \ - caeName=${{ needs.deploy-core.outputs.caeName }} \ - defaultDomain=${{ needs.deploy-core.outputs.defaultDomain }} \ - acrLoginServer=${{ needs.deploy-core.outputs.acrLoginServer }} \ - acrName=${{ needs.deploy-core.outputs.acrName }} \ - imageTag=${{ needs.build.outputs.imageTag }} \ - environmentName=staging \ - postgresFqdn=${{ needs.deploy-core.outputs.postgresFqdn }} \ - postgresAdminUsername=raysoncvadmin \ - postgresAdminPassword="${{ secrets.STAGING_POSTGRES_PASSWORD }}" \ - logLevel=Debug - - API_FQDN=$(az deployment group show --name raysoncv-staging-apps --resource-group ${{ env.AZURE_RESOURCE_GROUP }} --query properties.outputs.apiFqdn.value --output tsv) - UI_FQDN=$(az deployment group show --name raysoncv-staging-apps --resource-group ${{ env.AZURE_RESOURCE_GROUP }} --query properties.outputs.uiFqdn.value --output tsv) - - echo "apiFqdn=$API_FQDN" >> $GITHUB_OUTPUT - echo "uiFqdn=$UI_FQDN" >> $GITHUB_OUTPUT - - - name: Deployment Summary - run: | - echo "## Deployment Complete" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Service | URL |" >> $GITHUB_STEP_SUMMARY - echo "|---------|-----|" >> $GITHUB_STEP_SUMMARY - echo "| API | https://${{ steps.deploy.outputs.apiFqdn }} |" >> $GITHUB_STEP_SUMMARY - echo "| UI | https://${{ steps.deploy.outputs.uiFqdn }} |" >> $GITHUB_STEP_SUMMARY - - e2e-tests: - runs-on: ubuntu-latest - needs: deploy-apps - if: github.event_name == 'push' - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20.x' - - - name: Install dependencies - run: cd Rayson.CV/Test/e2e && npm ci - - - name: Install Playwright browsers - run: npx playwright install chromium - - - name: Run E2E tests - working-directory: Rayson.CV/Test/e2e - run: npm run e2e:staging - env: - E2E_API_URL: https://${{ needs.deploy-apps.outputs.apiFqdn }} - E2E_UI_URL: https://${{ needs.deploy-apps.outputs.uiFqdn }} - - - name: Upload reports - if: always() - uses: actions/upload-artifact@v4 - with: - name: e2e-report - path: Rayson.CV/Test/e2e/reports/ diff --git a/Rayson.CV/Api/Presentation/Endpoints/Files/StaticFilesEndpoints.cs b/Rayson.CV/Api/Presentation/Endpoints/Files/StaticFilesEndpoints.cs index e720822..a4efb83 100644 --- a/Rayson.CV/Api/Presentation/Endpoints/Files/StaticFilesEndpoints.cs +++ b/Rayson.CV/Api/Presentation/Endpoints/Files/StaticFilesEndpoints.cs @@ -4,9 +4,9 @@ public static class StaticFilesEndpoints { private static readonly Dictionary AppFiles = new() { - ["win"] = ("RaysonCV-Setup.exe", "application/x-msdownload"), - ["linux"] = ("RaysonCV.AppImage", "application/x-iso9660-image"), - ["mac"] = ("RaysonCV.dmg", "application/x-apple-diskimage") + ["win"] = ("Dan Rayson CV.exe", "application/x-msdownload"), + ["linux"] = ("Dan Rayson CV.AppImage", "application/x-iso9660-image"), + ["mac"] = ("Dan Rayson CV.dmg", "application/x-apple-diskimage") }; public static void MapStaticFilesEndpoints(this WebApplication webApplication) diff --git a/Rayson.CV/UI/package.json b/Rayson.CV/UI/package.json index 83c2db8..a7c3a76 100644 --- a/Rayson.CV/UI/package.json +++ b/Rayson.CV/UI/package.json @@ -52,7 +52,7 @@ "main": "electron/main.cjs", "build": { "appId": "com.raysoncv", - "productName": "RaysonCV", + "productName": "Dan Rayson CV", "directories": { "output": "build" }, @@ -61,14 +61,19 @@ "electron/**/*" ], "win": { - "target": "nsis" + "target": "nsis", + "icon": "public/icon.ico", + "artifactName": "${productName}.${ext}" }, "mac": { - "target": "dmg" + "target": "dmg", + "icon": "public/icon.png", + "artifactName": "${productName}.${ext}" }, "linux": { + "target": "AppImage", "icon": "public/rayson.dev.svg", - "target": "AppImage" + "artifactName": "${productName}.${ext}" } } } diff --git a/Rayson.CV/UI/public/icon.ico b/Rayson.CV/UI/public/icon.ico new file mode 100644 index 0000000..84c534d Binary files /dev/null and b/Rayson.CV/UI/public/icon.ico differ diff --git a/Rayson.CV/UI/public/icon.png b/Rayson.CV/UI/public/icon.png new file mode 100644 index 0000000..351b511 Binary files /dev/null and b/Rayson.CV/UI/public/icon.png differ diff --git a/Rayson.CV/UI/src/pages/LandingPage.tsx b/Rayson.CV/UI/src/pages/LandingPage.tsx index bb59639..dd3021b 100644 --- a/Rayson.CV/UI/src/pages/LandingPage.tsx +++ b/Rayson.CV/UI/src/pages/LandingPage.tsx @@ -2,17 +2,21 @@ import { useState } from 'react'; import PdfViewerModal from '../components/PdfViewerModal'; const getAppDownloadUrl = () => { - const baseUrl = import.meta.env.VITE_API_BASE_URL || ''; - const platform = navigator.platform.toLowerCase(); const userAgent = navigator.userAgent.toLowerCase(); - if (platform.includes('mac') || userAgent.includes('mac')) { + // Detect mobile/tablet + const isMobile = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent); + if (isMobile) return null; + + const baseUrl = import.meta.env.VITE_API_BASE_URL || ''; + + if (userAgent.includes('mac')) { return `${baseUrl}files/app/mac`; } - if (platform.includes('win') || userAgent.includes('win')) { + if (userAgent.includes('win')) { return `${baseUrl}files/app/win`; } - if (platform.includes('linux')) { + if (userAgent.includes('linux')) { return `${baseUrl}files/app/linux`; } return null; @@ -60,7 +64,7 @@ const LandingPage: React.FC = () => { onClick={handleAppClick} className="btn btn-disabled text-2xl font-bold" > - Download Unavailable + Unsupported OS )} diff --git a/infra/main-apps.bicep b/infra/main-apps.bicep deleted file mode 100644 index f37c902..0000000 --- a/infra/main-apps.bicep +++ /dev/null @@ -1,66 +0,0 @@ -param location string = resourceGroup().location -param caeId string -param caeName string -param defaultDomain string -param acrLoginServer string -param acrName string -param imageTag string -param environmentName string -param postgresFqdn string -param postgresAdminUsername string -@secure() -param postgresAdminPassword string -param logLevel string = 'Debug' -param customDomainName string = '' -param tags object = { - Environment: environmentName - Project: 'RaysonCV' -} - -var apiAppName = 'ca-api-${environmentName}' -var uiAppName = 'ca-ui-${environmentName}' -var uiFqdn = '${uiAppName}.${defaultDomain}' -var apiBaseUrl = 'https://${apiAppName}.${defaultDomain}' - -module api 'modules/api-container.bicep' = { - name: 'api-container-${environmentName}' - params: { - location: location - caeId: caeId - environmentName: environmentName - containerAppName: apiAppName - acrLoginServer: acrLoginServer - acrName: acrName - imageTag: imageTag - uiFqdn: uiFqdn - ollamaFqdn: 'http://localhost:11434' - postgresFqdn: postgresFqdn - postgresUsername: postgresAdminUsername - postgresPassword: postgresAdminPassword - postgresDatabase: 'raysoncv' - logLevel: logLevel - tags: tags - } -} - -module ui 'modules/ui-container.bicep' = { - name: 'ui-container-${environmentName}' - params: { - location: location - containerAppName: uiAppName - acrLoginServer: acrLoginServer - acrName: acrName - imageTag: imageTag - apiHealthUrl: 'http://${apiAppName}.internal.${defaultDomain}:8080/health' - apiBaseUrl: apiBaseUrl - customDomainName: customDomainName - tags: tags - caeName: caeName - } - dependsOn: [ - api - ] -} - -output apiFqdn string = api.outputs.fqdn -output uiFqdn string = ui.outputs.fqdn diff --git a/infra/main-core.bicep b/infra/main-core.bicep deleted file mode 100644 index 926c8a5..0000000 --- a/infra/main-core.bicep +++ /dev/null @@ -1,78 +0,0 @@ -targetScope = 'subscription' - -param location string -param resourceGroupName string -@allowed(['staging', 'production']) -param environmentName string -param acrName string -param postgresAdminUsername string -@secure() -param postgresAdminPassword string - -param tags object = { - Environment: environmentName - Project: 'RaysonCV' -} - -resource rg 'Microsoft.Resources/resourceGroups@2023-07-01' = { - name: resourceGroupName - location: location - tags: tags -} - -module acr 'modules/container-registry.bicep' = { - name: 'container-registry-${environmentName}' - scope: rg - params: { - location: location - acrName: acrName - tags: tags - } -} - -module storage 'modules/storage.bicep' = { - name: 'storage-${environmentName}' - scope: rg - params: { - location: location - environmentName: environmentName - tags: tags - } -} - -module containerAppsEnv 'modules/container-apps-environment.bicep' = { - name: 'container-apps-environment-${environmentName}' - dependsOn:[ - storage - ] - scope: rg - params: { - location: location - environmentName: environmentName - tags: tags - storageAccountName: storage.outputs.storageAccountName - storageAccountKey: storage.outputs.storageAccountKey - storageShareName: 'ollama-models' - } -} - -module postgres 'modules/postgres-service.bicep' = { - name: 'postgres-service-${environmentName}' - scope: rg - params: { - environmentName: environmentName - location: location - postgresAdminUsername: postgresAdminUsername - postgresAdminPassword: postgresAdminPassword - tags: tags - } -} - -output acrLoginServer string = acr.outputs.acrLoginServer -output acrName string = acr.outputs.acrName -output containerAppEnvId string = containerAppsEnv.outputs.containerAppEnvId -output containerAppEnvName string = containerAppsEnv.outputs.containerAppEnvName -output defaultDomain string = containerAppsEnv.outputs.defaultDomain -output storageAccountName string = storage.outputs.storageAccountName -output storageAccountKey string = storage.outputs.storageAccountKey -output postgresFqdn string = postgres.outputs.postgresFqdn diff --git a/infra/modules/api-container.bicep b/infra/modules/api-container.bicep deleted file mode 100644 index 0c1567a..0000000 --- a/infra/modules/api-container.bicep +++ /dev/null @@ -1,142 +0,0 @@ -param environmentName string -param location string = resourceGroup().location -param caeId string -param containerAppName string -param acrLoginServer string -param acrName string -param imageTag string = 'latest' -param uiFqdn string -param ollamaFqdn string -param postgresFqdn string -param postgresUsername string -@secure() -param postgresPassword string -param postgresDatabase string -param logLevel string = 'Debug' -param tags object = {} - -var localCorsOrigins = 'null,http://localhost,http://localhost:3000,http://localhost:5173' -var corsOrigins = environmentName == 'production' - ? '${localCorsOrigins},https://${uiFqdn},https://rayson.dev,https://www.rayson.dev' - : '${localCorsOrigins},https://${uiFqdn}' - -resource acr 'Microsoft.ContainerRegistry/registries@2023-07-01' existing = { - name: acrName -} - -resource apiContainer 'Microsoft.App/containerApps@2023-05-01' = { - name: containerAppName - location: location - properties: { - managedEnvironmentId: caeId - configuration: { - secrets: [ - { - name: 'acr-password' - value: acr.listCredentials().passwords[0].value - } - { - name: 'postgres-password' - value: postgresPassword - } - ] - activeRevisionsMode: 'Single' - ingress: { - external: true - targetPort: 8080 - transport: 'auto' - } - registries: [ - { - server: acrLoginServer - username: acrName - passwordSecretRef: 'acr-password' - } - ] - } - template: { - containers: [ - { - name: 'api' - image: '${acrLoginServer}/raysoncv-api:${imageTag}' - env: [ - { - name: 'ASPNETCORE_ENVIRONMENT' - value: environmentName - } - { - name: 'ASPNETCORE_URLS' - value: 'http://+:8080' - } - { - name: 'Cors__AllowedOrigins' - value: corsOrigins - } - { - name: 'LOG_LEVEL' - value: logLevel - } - { - name: 'OLLAMA__BASEURL' - value: ollamaFqdn - } - { - name: 'POSTGRES_HOST' - value: postgresFqdn - } - { - name: 'POSTGRES_PORT' - value: '5432' - } - { - name: 'POSTGRES_USERNAME' - value: postgresUsername - } - { - name: 'POSTGRES_PASSWORD' - secretRef: 'postgres-password' - } - { - name: 'POSTGRES_DATABASE' - value: postgresDatabase - } - ] - resources: { - cpu: json('0.5') - memory: '1.0Gi' - } - } - { - name: 'ollama' - image: '${acrLoginServer}/raysoncv-ollama:${imageTag}' - command: ['/bin/sh', '/ollama-startup.sh'] - resources: { - cpu: json('1.5') - memory: '3.0Gi' - } - volumeMounts: [ - { - volumeName: 'ollama-models' - mountPath: '/root/.ollama' - } - ] - } - ] - volumes: [ - { - name: 'ollama-models' - storageType: 'AzureFile' - storageName: 'ollama-models' - } - ] - scale: { - minReplicas: 1 - maxReplicas: 3 - } - } - } - tags: tags -} - -output containerAppName string = apiContainer.name -output fqdn string = apiContainer.properties.configuration.ingress.fqdn diff --git a/infra/modules/container-apps-environment.bicep b/infra/modules/container-apps-environment.bicep deleted file mode 100644 index ac0549c..0000000 --- a/infra/modules/container-apps-environment.bicep +++ /dev/null @@ -1,31 +0,0 @@ -param location string = resourceGroup().location -param environmentName string -param tags object = {} -param storageAccountName string = '' -@secure() -param storageAccountKey string = '' -param storageShareName string = '' - -resource containerAppsEnv 'Microsoft.App/managedEnvironments@2023-05-01' = { - name: 'cae-raysoncv-${environmentName}' - location: location - properties: {} - tags: tags -} - -resource ollamaStorage 'Microsoft.App/managedEnvironments/storages@2023-05-01' = if (storageAccountName != '') { - name: 'ollama-models' - parent: containerAppsEnv - properties: { - azureFile: { - accessMode: 'ReadWrite' - accountName: storageAccountName - shareName: storageShareName - accountKey: storageAccountKey - } - } -} - -output containerAppEnvId string = containerAppsEnv.id -output containerAppEnvName string = containerAppsEnv.name -output defaultDomain string = containerAppsEnv.properties.defaultDomain diff --git a/infra/modules/container-registry.bicep b/infra/modules/container-registry.bicep deleted file mode 100644 index 3d26eca..0000000 --- a/infra/modules/container-registry.bicep +++ /dev/null @@ -1,19 +0,0 @@ -param location string = resourceGroup().location -param acrName string -param tags object = {} - -resource acr 'Microsoft.ContainerRegistry/registries@2023-07-01' = { - name: acrName - location: location - sku: { - name: 'Basic' - } - properties: { - adminUserEnabled: true - } - tags: tags -} - -output acrId string = acr.id -output acrName string = acr.name -output acrLoginServer string = acr.properties.loginServer diff --git a/infra/modules/ollama-container.bicep b/infra/modules/ollama-container.bicep deleted file mode 100644 index 37a9134..0000000 --- a/infra/modules/ollama-container.bicep +++ /dev/null @@ -1,80 +0,0 @@ -param location string = resourceGroup().location -param environmentId string -param containerAppName string -param acrLoginServer string -param acrName string -param imageTag string = 'latest' -param tags object = {} - -resource acr 'Microsoft.ContainerRegistry/registries@2023-07-01' existing = { - name: acrName -} - -resource ollamaContainer 'Microsoft.App/containerApps@2023-05-01' = { - name: containerAppName - location: location - properties: { - managedEnvironmentId: environmentId - configuration: { - secrets: [ - { - name: 'acr-password' - value: acr.listCredentials().passwords[0].value - } - ] - activeRevisionsMode: 'Single' - registries: [ - { - server: acrLoginServer - username: acrName - passwordSecretRef: 'acr-password' - } - ] - ingress: { - external: false - targetPort: 11434 - transport: 'auto' - } - } - template: { - containers: [ - { - name: 'ollama' - image: '${acrLoginServer}/raysoncv-ollama:${imageTag}' - env: [ - { - name: 'OLLAMA_HOST' - value: '0.0.0.0:11434' - } - ] - resources: { - cpu: json('2.0') - memory: '4.0Gi' - } - volumeMounts: [ - { - volumeName: 'ollama-models' - mountPath: '/root/.ollama' - } - ] - } - ] - scale: { - minReplicas: 1 - maxReplicas: 1 - } - volumes: [ - { - name: 'ollama-models' - storageType: 'AzureFile' - storageName: 'ollama-models' - } - ] - } - } - tags: tags -} - -output containerAppName string = ollamaContainer.name -output fqdn string = ollamaContainer.properties.configuration.ingress.fqdn -output serviceId string = ollamaContainer.id diff --git a/infra/modules/postgres-service.bicep b/infra/modules/postgres-service.bicep deleted file mode 100644 index 2951391..0000000 --- a/infra/modules/postgres-service.bicep +++ /dev/null @@ -1,61 +0,0 @@ -param environmentName string -param location string = resourceGroup().location -param postgresAdminUsername string -@secure() -param postgresAdminPassword string -param tags object = {} - -var serverName = 'pg-${environmentName}-${uniqueString(resourceGroup().id)}' -var databaseName = 'raysoncv' - -resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2026-01-01-preview' = { - name: serverName - location: location - tags: tags - sku: { - name: 'Standard_B1ms' - tier: 'Burstable' - } - properties: { - version: '16' - storage: { - storageSizeGB: 32 - autoGrow: 'Disabled' - } - administratorLogin: postgresAdminUsername - administratorLoginPassword: postgresAdminPassword - highAvailability: { - mode: 'Disabled' - } - } -} - -resource postgresExtensions 'Microsoft.DBforPostgreSQL/flexibleServers/configurations@2026-01-01-preview' = { - parent: postgresServer - name: 'azure.extensions' - properties: { - value: 'vector' - source: 'user-override' - } -} - -resource database 'Microsoft.DBforPostgreSQL/flexibleServers/databases@2026-01-01-preview' = { - parent: postgresServer - name: databaseName - properties: { - charset: 'utf8' - collation: 'en_US.utf8' - } -} - -resource firewallRule 'Microsoft.DBforPostgreSQL/flexibleServers/firewallRules@2026-01-01-preview' = { - parent: postgresServer - name: 'allow-container-apps' - properties: { - startIpAddress: '0.0.0.0' - endIpAddress: '0.0.0.0' - } -} - -output postgresFqdn string = postgresServer.properties.fullyQualifiedDomainName -output databaseName string = databaseName diff --git a/infra/modules/storage.bicep b/infra/modules/storage.bicep deleted file mode 100644 index 674e950..0000000 --- a/infra/modules/storage.bicep +++ /dev/null @@ -1,35 +0,0 @@ -param location string -param environmentName string -param tags object = {} - -var storageAccountName = 'strraysoncv${environmentName}' - -resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = { - name: storageAccountName - location: location - sku: { - name: 'Standard_LRS' - } - kind: 'StorageV2' - properties: { - accessTier: 'Hot' - allowBlobPublicAccess: false - } - tags: tags -} - -resource fileService 'Microsoft.Storage/storageAccounts/fileServices@2023-01-01' = { - parent: storageAccount - name: 'default' -} - -resource ollamaModels 'Microsoft.Storage/storageAccounts/fileServices/shares@2023-01-01' = { - parent: fileService - name: 'ollama-models' - properties: { - shareQuota: 10 - } -} - -output storageAccountName string = storageAccount.name -output storageAccountKey string = storageAccount.listKeys().keys[0].value diff --git a/infra/modules/ui-container.bicep b/infra/modules/ui-container.bicep deleted file mode 100644 index 5776240..0000000 --- a/infra/modules/ui-container.bicep +++ /dev/null @@ -1,101 +0,0 @@ -param location string = resourceGroup().location -param containerAppName string -param acrLoginServer string -param acrName string -param imageTag string = 'latest' -param apiHealthUrl string -param apiBaseUrl string -param customDomainName string = '' -param tags object = {} -param caeName string - -resource acr 'Microsoft.ContainerRegistry/registries@2023-07-01' existing = { - name: acrName -} - -resource containerAppEnvironment 'Microsoft.App/managedEnvironments@2023-05-01' existing = { - name: caeName -} - -resource uiContainer 'Microsoft.App/containerApps@2025-07-01' = { - name: containerAppName - location: location - tags: tags - - properties: { - managedEnvironmentId: containerAppEnvironment.id - - configuration: { - activeRevisionsMode: 'Single' - - secrets: [ - { - name: 'acr-password' - value: acr.listCredentials().passwords[0].value - } - ] - - registries: [ - { - server: acrLoginServer - username: acrName - passwordSecretRef: 'acr-password' - } - ] - - ingress: { - external: true - targetPort: 3000 - transport: 'auto' - - customDomains: customDomainName != '' ? [ - { - name: customDomainName - bindingType: 'Auto' - } - { - name: 'www.${customDomainName}' - bindingType: 'Auto' - } - ] : [] - } - } - - template: { - containers: [ - { - name: 'ui' - image: '${acrLoginServer}/raysoncv-ui:${imageTag}' - - env: [ - { - name: 'API_HEALTH_URL' - value: apiHealthUrl - } - { - name: 'VITE_API_BASE_URL' - value: apiBaseUrl - } - { - name: 'LOG_LEVEL' - value: 'info' - } - ] - - resources: { - cpu: json('0.25') - memory: '0.5Gi' - } - } - ] - - scale: { - minReplicas: 1 - maxReplicas: 1 - } - } - } -} - -output containerAppName string = uiContainer.name -output fqdn string = uiContainer.properties.configuration.ingress.fqdn