name: Deploy Academy LMS to Hetzner on: # Manual trigger workflow_dispatch: inputs: force_rebuild: description: 'Force rebuild all images' required: false default: 'false' type: boolean environment: description: 'Deployment environment' required: false default: 'test' type: choice options: - production - test # Webhook triggers from watched repositories repository_dispatch: types: [academy-lms-updated, academy-ai-tutor-updated, academy-langchain-updated] # Push to master branch of this repo push: branches: [ master ] paths: - 'compose.yaml' - 'images/**' - '.github/workflows/**' - 'nginx/**' env: REGISTRY: ghcr.io HETZNER_HOST: 188.245.211.114 HETZNER_USER: ignis_academy_lms DEPLOY_PATH: /opt/frappe-deployment jobs: build-and-deploy: runs-on: ubuntu-latest # Use environment for secrets management environment: ${{ github.event.inputs.environment || 'production' }} permissions: contents: read packages: write steps: - name: Checkout repository uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata for Frappe image id: meta-frappe uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/exarlabs/ignis-academy-lms tags: | type=ref,event=branch type=ref,event=pr type=sha,prefix={{branch}}- type=raw,value=latest,enable={{is_default_branch}} - name: Build and push Frappe image uses: docker/build-push-action@v5 with: context: . file: ./images/custom/Containerfile push: true tags: ${{ steps.meta-frappe.outputs.tags }} labels: ${{ steps.meta-frappe.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max build-args: | LMS_REPO_URL=https://github.com/ExarLabs/academy-lms AI_TUTOR_REPO_URL=https://github.com/ExarLabs/academy-ai-tutor-chat - name: Clone and prepare LangChain service run: | # Clone private repository using PAT (Personal Access Token) git clone https://${{ secrets.ACADEMY_DOCKER_PAT }}@github.com/ExarLabs/academy-LangChain.git langchain-temp - name: Extract metadata for LangChain image id: meta-langchain uses: docker/metadata-action@v5 with: images: ${{ env.REGISTRY }}/exarlabs/academy-langchain tags: | type=ref,event=branch type=ref,event=pr type=sha,prefix={{branch}}- type=raw,value=latest,enable={{is_default_branch}} - name: Build and push LangChain image uses: docker/build-push-action@v5 with: context: ./langchain-temp push: true tags: ${{ steps.meta-langchain.outputs.tags }} labels: ${{ steps.meta-langchain.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - name: Setup SSH key uses: webfactory/ssh-agent@v0.8.0 with: ssh-private-key: ${{ secrets.HETZNER_SSH_KEY }} - name: Add Hetzner server to known hosts run: | ssh-keyscan -H ${{ env.HETZNER_HOST }} >> ~/.ssh/known_hosts - name: Deploy to Hetzner run: | # Copy deployment files to server scp -r compose.yaml nginx/ scripts/ ${{ env.HETZNER_USER }}@${{ env.HETZNER_HOST }}:${{ env.DEPLOY_PATH }}/ # Copy environment file if it doesn't exist ssh ${{ env.HETZNER_USER }}@${{ env.HETZNER_HOST }} " cd ${{ env.DEPLOY_PATH }} if [ ! -f .env ]; then cp .env.example .env echo 'Please update .env file with your configuration' fi " # Login to private registry ssh ${{ env.HETZNER_USER }}@${{ env.HETZNER_HOST }} " echo '${{ secrets.GITHUB_TOKEN }}' | docker login ${{ env.REGISTRY }} -u ${{ github.actor }} --password-stdin " - name: Update and restart services env: MARIADB_ROOT_PASSWORD: ${{ secrets.MARIADB_ROOT_PASSWORD }} run: | ssh ${{ env.HETZNER_USER }}@${{ env.HETZNER_HOST }} " cd ${{ env.DEPLOY_PATH }} # Pull latest images docker compose pull # Stop services gracefully docker compose down --timeout 30 # Start services docker compose up -d # Wait for services to be ready sleep 30 # Run migrations on all sites ./scripts/migrate-all-sites.sh # Show status docker compose ps " - name: Health check run: | # Wait a bit more for services to fully start sleep 60 # Check if all docker container are up and running ssh ${{ env.HETZNER_USER }}@${{ env.HETZNER_HOST }} " count=$(docker container ls --format "{{.Status}}" | grep -v "^Up" | wc -l) if [ $count -gt 0 ]; then echo "❌ $count containers are not running properly" cd ${{ env.DEPLOY_PATH }} docker compose logs --tail=50 exit 1 else echo "✅ All containers are running" fi " - name: Notify deployment status if: always() run: | if [ "${{ job.status }}" == "success" ]; then echo "🚀 Deployment to Hetzner completed successfully!" echo "🌐 Access your application at: http://${{ env.HETZNER_HOST }}" else echo "💥 Deployment failed. Check the logs above for details." fi