name: Push Release Images to Docker Hub and GitHub Container Registry on: push: branches: - "release" workflow_dispatch: inputs: publish_android_to_store: description: 'Publish Android app to Google Play Store' required: false type: boolean default: false publish_ios_to_store: description: 'Publish iOS app to App Store' required: false type: boolean default: false jobs: generate-build-number: runs-on: ubuntu-latest outputs: build_number: ${{ steps.buildnumber.outputs.build_number }} steps: - name: Generate build number id: buildnumber uses: onyxmueller/build-tag-number@v1.0.2 with: token: ${{secrets.github_token}} - run: echo "Build number is ${{ steps.buildnumber.outputs.build_number }}" read-version: runs-on: ubuntu-latest permissions: contents: read outputs: major_minor: ${{ steps.determine.outputs.semver_base }} semver_base: ${{ steps.determine.outputs.semver_base }} major: ${{ steps.determine.outputs.major }} minor: ${{ steps.determine.outputs.minor }} patch: ${{ steps.determine.outputs.patch }} steps: - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - name: Check version and verify not already released id: determine env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPOSITORY: ${{ github.repository }} run: | set -euo pipefail VERSION_RAW="$(tr -d ' \n' < VERSION)" if [[ -z "$VERSION_RAW" ]]; then echo "VERSION is empty" >&2 exit 1 fi echo "Version from VERSION file: $VERSION_RAW" IFS='.' read -r major minor patch <<< "$VERSION_RAW" if [[ -z "$minor" ]]; then echo "VERSION must contain major and minor components" >&2 exit 1 fi patch="${patch:-0}" for part_name in major minor patch; do part="${!part_name}" if ! [[ "$part" =~ ^[0-9]+$ ]]; then echo "Invalid ${part_name} component '$part' in VERSION" >&2 exit 1 fi done # Check if this version is already released on GitHub echo "Checking if version $VERSION_RAW is already released on GitHub..." if gh release view "$VERSION_RAW" --repo "$REPOSITORY" &>/dev/null; then echo "::error::Version $VERSION_RAW is already released on GitHub. Please update the VERSION file to a new version." exit 1 fi # Also check with 'v' prefix just in case if gh release view "v$VERSION_RAW" --repo "$REPOSITORY" &>/dev/null; then echo "::error::Version v$VERSION_RAW is already released on GitHub. Please update the VERSION file to a new version." exit 1 fi echo "✅ Version $VERSION_RAW is not yet released. Proceeding with release." echo "semver_base=${VERSION_RAW}" >> "$GITHUB_OUTPUT" echo "major=${major}" >> "$GITHUB_OUTPUT" echo "minor=${minor}" >> "$GITHUB_OUTPUT" echo "patch=${patch}" >> "$GITHUB_OUTPUT" echo "Using version: ${VERSION_RAW}" helm-chart-deploy: runs-on: ubuntu-latest needs: [generate-build-number, read-version] env: CI_COMMIT_AUTHOR: Continuous Integration steps: - name: Install Helm run: curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - name: Build and Package Helm chart run: | cd .. echo '${{ secrets.GPG_PRIVATE_KEY }}' > private.key gpg --import private.key || true rm private.key echo "GPG key imported successfully" gpg --export-secret-keys >~/.gnupg/secring.gpg echo "GPG key exported successfully" eval `ssh-agent -s` ssh-add - <<< '${{ secrets.HELM_CHART_GITHUB_REPO_DEPLOY_KEY }}' git clone git@github.com:OneUptime/helm-chart.git cd oneuptime/HelmChart/Public helm lint oneuptime helm template oneuptime --values oneuptime/values.yaml helm package --sign --key 'key@oneuptime.com' --keyring ~/.gnupg/secring.gpg oneuptime --version ${{needs.read-version.outputs.major_minor}} --app-version ${{needs.read-version.outputs.major_minor}} echo "Helm Chart Package created successfully" cd .. ls echo "Copying the package to helm-chart repo" rm -r ../../helm-chart/oneuptime cp -r ./Public/* ../../helm-chart echo "Package copied successfully" cd .. && cd .. && cd helm-chart echo "Updating helm-chart repo" git config --global user.name "${{ env.CI_COMMIT_AUTHOR }}" git config --global user.email "hello@oneuptime.com" echo "Git config set successfully" echo "Adding the package to helm-chart repo" helm repo index . git add -A git commit -m "Helm Chart Release ${{needs.read-version.outputs.major_minor}}" git push origin master nginx-docker-image-deploy: needs: [generate-build-number, read-version] runs-on: ubuntu-latest env: QEMU_CPU: max steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main with: tool-cache: false android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - name: Docker Meta id: meta uses: docker/metadata-action@v4 with: images: | oneuptime/nginx ghcr.io/oneuptime/nginx tags: | type=raw,value=release,enable=true type=semver,value=${{needs.read-version.outputs.major_minor}},pattern={{version}},enable=true - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - uses: actions/setup-node@v4 with: node-version: latest - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: image: tonistiigi/binfmt:qemu-v10.0.4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Generate Dockerfile from Dockerfile.tpl run: npm run prerun # Build and deploy nginx. - name: Login to Docker Hub run: | echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - name: Login to GitHub Container Registry run: | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin - name: Build and push run: | bash ./Scripts/GHA/build_docker_images.sh \ --image nginx \ --version "${{needs.read-version.outputs.major_minor}}" \ --dockerfile ./Nginx/Dockerfile \ --context . \ --platforms linux/amd64,linux/arm64 \ --git-sha "${{ github.sha }}" e2e-docker-image-deploy: needs: [generate-build-number, read-version] runs-on: ubuntu-latest env: QEMU_CPU: max steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main with: tool-cache: false android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - name: Docker Meta id: meta uses: docker/metadata-action@v4 with: images: | oneuptime/e2e ghcr.io/oneuptime/e2e tags: | type=raw,value=release,enable=true type=semver,value=${{needs.read-version.outputs.major_minor}},pattern={{version}},enable=true - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - uses: actions/setup-node@v4 with: node-version: latest - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: image: tonistiigi/binfmt:qemu-v10.0.4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Generate Dockerfile from Dockerfile.tpl run: npm run prerun # Build and deploy e2e. - name: Login to Docker Hub run: | echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - name: Login to GitHub Container Registry run: | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin - name: Build and push run: | bash ./Scripts/GHA/build_docker_images.sh \ --image e2e \ --version "${{needs.read-version.outputs.major_minor}}" \ --dockerfile ./E2E/Dockerfile \ --context . \ --platforms linux/amd64,linux/arm64 \ --git-sha "${{ github.sha }}" home-docker-image-deploy: needs: [generate-build-number, read-version] runs-on: ubuntu-latest env: QEMU_CPU: max steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main with: tool-cache: false android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - name: Docker Meta id: meta uses: docker/metadata-action@v4 with: images: | oneuptime/home ghcr.io/oneuptime/home tags: | type=raw,value=release,enable=true type=semver,value=${{needs.read-version.outputs.major_minor}},pattern={{version}},enable=true - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - uses: actions/setup-node@v4 with: node-version: latest - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: image: tonistiigi/binfmt:qemu-v10.0.4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Generate Dockerfile from Dockerfile.tpl run: npm run prerun # Build and deploy home. - name: Login to Docker Hub run: | echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - name: Login to GitHub Container Registry run: | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin - name: Build and push run: | bash ./Scripts/GHA/build_docker_images.sh \ --image home \ --version "${{needs.read-version.outputs.major_minor}}" \ --dockerfile ./Home/Dockerfile \ --context . \ --platforms linux/amd64,linux/arm64 \ --git-sha "${{ github.sha }}" test-server-docker-image-deploy: needs: [generate-build-number, read-version] runs-on: ubuntu-latest env: QEMU_CPU: max steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main with: tool-cache: false android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - name: Docker Meta id: meta uses: docker/metadata-action@v4 with: images: | oneuptime/test-server ghcr.io/oneuptime/test-server tags: | type=raw,value=release,enable=true type=semver,value=${{needs.read-version.outputs.major_minor}},pattern={{version}},enable=true - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - uses: actions/setup-node@v4 with: node-version: latest - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: image: tonistiigi/binfmt:qemu-v10.0.4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Generate Dockerfile from Dockerfile.tpl run: npm run prerun # Build and deploy test-server. - name: Login to Docker Hub run: | echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - name: Login to GitHub Container Registry run: | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin - name: Build and push run: | bash ./Scripts/GHA/build_docker_images.sh \ --image test-server \ --version "${{needs.read-version.outputs.major_minor}}" \ --dockerfile ./TestServer/Dockerfile \ --context . \ --platforms linux/amd64,linux/arm64 \ --git-sha "${{ github.sha }}" otel-collector-docker-image-deploy: needs: [generate-build-number, read-version] runs-on: ubuntu-latest env: QEMU_CPU: max steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main with: tool-cache: false android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - name: Docker Meta id: meta uses: docker/metadata-action@v4 with: images: | oneuptime/otel-collector ghcr.io/oneuptime/otel-collector tags: | type=raw,value=release,enable=true type=semver,value=${{needs.read-version.outputs.major_minor}},pattern={{version}},enable=true - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - uses: actions/setup-node@v4 with: node-version: latest - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: image: tonistiigi/binfmt:qemu-v10.0.4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Generate Dockerfile from Dockerfile.tpl run: npm run prerun # Build and deploy otel-collector. - name: Login to Docker Hub run: | echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - name: Login to GitHub Container Registry run: | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin - name: Build and push run: | bash ./Scripts/GHA/build_docker_images.sh \ --image otel-collector \ --version "${{needs.read-version.outputs.major_minor}}" \ --dockerfile ./OTelCollector/Dockerfile \ --context . \ --platforms linux/amd64,linux/arm64 \ --git-sha "${{ github.sha }}" status-page-docker-image-deploy: needs: [generate-build-number, read-version] runs-on: ubuntu-latest env: QEMU_CPU: max steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main with: tool-cache: false android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - name: Docker Meta id: meta uses: docker/metadata-action@v4 with: images: | oneuptime/status-page ghcr.io/oneuptime/status-page tags: | type=raw,value=release,enable=true type=semver,value=${{needs.read-version.outputs.major_minor}},pattern={{version}},enable=true - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - uses: actions/setup-node@v4 with: node-version: latest - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: image: tonistiigi/binfmt:qemu-v10.0.4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Generate Dockerfile from Dockerfile.tpl run: npm run prerun # Build and deploy status-page. - name: Login to Docker Hub run: | echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - name: Login to GitHub Container Registry run: | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin - name: Build and push run: | bash ./Scripts/GHA/build_docker_images.sh \ --image status-page \ --version "${{needs.read-version.outputs.major_minor}}" \ --dockerfile ./StatusPage/Dockerfile \ --context . \ --platforms linux/amd64,linux/arm64 \ --git-sha "${{ github.sha }}" test-docker-image-deploy: needs: [generate-build-number, read-version] runs-on: ubuntu-latest env: QEMU_CPU: max steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main with: tool-cache: false android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - name: Docker Meta id: meta uses: docker/metadata-action@v4 with: images: | oneuptime/test ghcr.io/oneuptime/test tags: | type=raw,value=release,enable=true type=semver,value=${{needs.read-version.outputs.major_minor}},pattern={{version}},enable=true - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - uses: actions/setup-node@v4 with: node-version: latest - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: image: tonistiigi/binfmt:qemu-v10.0.4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Generate Dockerfile from Dockerfile.tpl run: npm run prerun # Build and deploy test. - name: Login to Docker Hub run: | echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - name: Login to GitHub Container Registry run: | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin - name: Build and push run: | bash ./Scripts/GHA/build_docker_images.sh \ --image test \ --version "${{needs.read-version.outputs.major_minor}}" \ --dockerfile ./Tests/Dockerfile \ --context . \ --platforms linux/amd64,linux/arm64 \ --git-sha "${{ github.sha }}" telemetry-docker-image-deploy: needs: [generate-build-number, read-version] runs-on: ubuntu-latest env: QEMU_CPU: max steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main with: tool-cache: false android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - name: Docker Meta id: meta uses: docker/metadata-action@v4 with: images: | oneuptime/telemetry ghcr.io/oneuptime/telemetry tags: | type=raw,value=release,enable=true type=semver,value=${{needs.read-version.outputs.major_minor}},pattern={{version}},enable=true - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - uses: actions/setup-node@v4 with: node-version: latest - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: image: tonistiigi/binfmt:qemu-v10.0.4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Generate Dockerfile from Dockerfile.tpl run: npm run prerun # Build and deploy telemetry. - name: Login to Docker Hub run: | echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - name: Login to GitHub Container Registry run: | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin - name: Build and push run: | bash ./Scripts/GHA/build_docker_images.sh \ --image telemetry \ --version "${{needs.read-version.outputs.major_minor}}" \ --dockerfile ./Telemetry/Dockerfile \ --context . \ --platforms linux/amd64,linux/arm64 \ --git-sha "${{ github.sha }}" probe-docker-image-deploy: needs: [generate-build-number, read-version] runs-on: ubuntu-latest env: QEMU_CPU: max steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main with: tool-cache: false android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - name: Docker Meta id: meta uses: docker/metadata-action@v4 with: images: | oneuptime/probe ghcr.io/oneuptime/probe tags: | type=raw,value=release,enable=true type=semver,value=${{needs.read-version.outputs.major_minor}},pattern={{version}},enable=true - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - uses: actions/setup-node@v4 with: node-version: latest - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: image: tonistiigi/binfmt:qemu-v10.0.4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Generate Dockerfile from Dockerfile.tpl run: npm run prerun # Build and deploy probe. - name: Login to Docker Hub run: | echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - name: Login to GitHub Container Registry run: | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin - name: Build and push run: | bash ./Scripts/GHA/build_docker_images.sh \ --image probe \ --version "${{needs.read-version.outputs.major_minor}}" \ --dockerfile ./Probe/Dockerfile \ --context . \ --platforms linux/amd64,linux/arm64 \ --git-sha "${{ github.sha }}" admin-dashboard-docker-image-deploy: needs: [generate-build-number, read-version] runs-on: ubuntu-latest env: QEMU_CPU: max steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main with: tool-cache: false android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - name: Docker Meta id: meta uses: docker/metadata-action@v4 with: images: | oneuptime/admin-dashboard ghcr.io/oneuptime/admin-dashboard tags: | type=raw,value=release,enable=true type=semver,value=${{needs.read-version.outputs.major_minor}},pattern={{version}},enable=true - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - uses: actions/setup-node@v4 with: node-version: latest - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: image: tonistiigi/binfmt:qemu-v10.0.4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Generate Dockerfile from Dockerfile.tpl run: npm run prerun # Build and deploy admin-dashboard. - name: Login to Docker Hub run: | echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - name: Login to GitHub Container Registry run: | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin - name: Build and push run: | bash ./Scripts/GHA/build_docker_images.sh \ --image admin-dashboard \ --version "${{needs.read-version.outputs.major_minor}}" \ --dockerfile ./AdminDashboard/Dockerfile \ --context . \ --platforms linux/amd64,linux/arm64 \ --git-sha "${{ github.sha }}" dashboard-docker-image-deploy: needs: [generate-build-number, read-version] runs-on: ubuntu-latest env: QEMU_CPU: max steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main with: tool-cache: false android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - name: Docker Meta id: meta uses: docker/metadata-action@v4 with: images: | oneuptime/dashboard ghcr.io/oneuptime/dashboard tags: | type=raw,value=release,enable=true type=semver,value=${{needs.read-version.outputs.major_minor}},pattern={{version}},enable=true - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - uses: actions/setup-node@v4 with: node-version: latest - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: image: tonistiigi/binfmt:qemu-v10.0.4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Generate Dockerfile from Dockerfile.tpl run: npm run prerun # Build and deploy dashboard. - name: Login to Docker Hub run: | echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - name: Login to GitHub Container Registry run: | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin - name: Build and push run: | bash ./Scripts/GHA/build_docker_images.sh \ --image dashboard \ --version "${{needs.read-version.outputs.major_minor}}" \ --dockerfile ./Dashboard/Dockerfile \ --context . \ --platforms linux/amd64,linux/arm64 \ --git-sha "${{ github.sha }}" app-docker-image-deploy: needs: [generate-build-number, read-version] runs-on: ubuntu-latest env: QEMU_CPU: max steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main with: tool-cache: false android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - name: Docker Meta id: meta uses: docker/metadata-action@v4 with: images: | oneuptime/app ghcr.io/oneuptime/app tags: | type=raw,value=release,enable=true type=semver,value=${{needs.read-version.outputs.major_minor}},pattern={{version}},enable=true - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - uses: actions/setup-node@v4 with: node-version: latest - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: image: tonistiigi/binfmt:qemu-v10.0.4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Generate Dockerfile from Dockerfile.tpl run: npm run prerun # Build and deploy app. - name: Login to Docker Hub run: | echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - name: Login to GitHub Container Registry run: | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin - name: Build and push run: | bash ./Scripts/GHA/build_docker_images.sh \ --image app \ --version "${{needs.read-version.outputs.major_minor}}" \ --dockerfile ./App/Dockerfile \ --context . \ --platforms linux/amd64,linux/arm64 \ --git-sha "${{ github.sha }}" accounts-docker-image-deploy: needs: [generate-build-number, read-version] runs-on: ubuntu-latest env: QEMU_CPU: max steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main with: tool-cache: false android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - name: Docker Meta id: meta uses: docker/metadata-action@v4 with: images: | oneuptime/accounts ghcr.io/oneuptime/accounts tags: | type=raw,value=release,enable=true type=semver,value=${{needs.read-version.outputs.major_minor}},pattern={{version}},enable=true - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - uses: actions/setup-node@v4 with: node-version: latest - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: image: tonistiigi/binfmt:qemu-v10.0.4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Generate Dockerfile from Dockerfile.tpl run: npm run prerun # Build and deploy accounts. - name: Login to Docker Hub run: | echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - name: Login to GitHub Container Registry run: | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin - name: Build and push run: | bash ./Scripts/GHA/build_docker_images.sh \ --image accounts \ --version "${{needs.read-version.outputs.major_minor}}" \ --dockerfile ./Accounts/Dockerfile \ --context . \ --platforms linux/amd64,linux/arm64 \ --git-sha "${{ github.sha }}" ai-agent-docker-image-deploy: needs: [generate-build-number, read-version] runs-on: ubuntu-latest env: QEMU_CPU: max steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main with: tool-cache: false android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - name: Docker Meta id: meta uses: docker/metadata-action@v4 with: images: | oneuptime/ai-agent ghcr.io/oneuptime/ai-agent tags: | type=raw,value=release,enable=true type=semver,value=${{needs.read-version.outputs.major_minor}},pattern={{version}},enable=true - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - uses: actions/setup-node@v4 with: node-version: latest - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: image: tonistiigi/binfmt:qemu-v10.0.4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Generate Dockerfile from Dockerfile.tpl run: npm run prerun # Build and deploy ai-agent. - name: Login to Docker Hub run: | echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - name: Login to GitHub Container Registry run: | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin - name: Build and push run: | bash ./Scripts/GHA/build_docker_images.sh \ --image ai-agent \ --version "${{needs.read-version.outputs.major_minor}}" \ --dockerfile ./AIAgent/Dockerfile \ --context . \ --platforms linux/amd64,linux/arm64 \ --git-sha "${{ github.sha }}" publish-npm-packages: runs-on: ubuntu-latest needs: [generate-build-number, read-version] permissions: contents: read id-token: write # Required for npm OIDC trusted publishing env: CI_PIPELINE_ID: ${{github.run_number}} PACKAGE_VERSION: ${{needs.read-version.outputs.major_minor}} steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: latest registry-url: 'https://registry.npmjs.org' - name: Update npm for OIDC support run: npm install -g npm@latest - name: Preinstall run: npm run prerun - name: Publish NPM Packages run: bash ./Scripts/NPM/PublishAllPackages.sh docs-docker-image-deploy: needs: [generate-build-number, read-version] runs-on: ubuntu-latest env: QEMU_CPU: max steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main with: tool-cache: false android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - name: Docker Meta id: meta uses: docker/metadata-action@v4 with: images: | oneuptime/docs ghcr.io/oneuptime/docs tags: | type=raw,value=release,enable=true type=semver,value=${{needs.read-version.outputs.major_minor}},pattern={{version}},enable=true - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - uses: actions/setup-node@v4 with: node-version: latest - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: image: tonistiigi/binfmt:qemu-v10.0.4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Generate Dockerfile from Dockerfile.tpl run: npm run prerun # Build and deploy nginx. - name: Login to Docker Hub run: | echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - name: Login to GitHub Container Registry run: | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin - name: Build and push run: | bash ./Scripts/GHA/build_docker_images.sh \ --image docs \ --version "${{needs.read-version.outputs.major_minor}}" \ --dockerfile ./Docs/Dockerfile \ --context . \ --platforms linux/amd64,linux/arm64 \ --git-sha "${{ github.sha }}" worker-docker-image-deploy: needs: [generate-build-number, read-version] runs-on: ubuntu-latest env: QEMU_CPU: max steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main with: tool-cache: false android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - name: Docker Meta id: meta uses: docker/metadata-action@v4 with: images: | oneuptime/worker ghcr.io/oneuptime/worker tags: | type=raw,value=release,enable=true type=semver,value=${{needs.read-version.outputs.major_minor}},pattern={{version}},enable=true - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - uses: actions/setup-node@v4 with: node-version: latest - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: image: tonistiigi/binfmt:qemu-v10.0.4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Generate Dockerfile from Dockerfile.tpl run: npm run prerun # Build and deploy nginx. - name: Login to Docker Hub run: | echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - name: Login to GitHub Container Registry run: | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin - name: Build and push run: | bash ./Scripts/GHA/build_docker_images.sh \ --image worker \ --version "${{needs.read-version.outputs.major_minor}}" \ --dockerfile ./Worker/Dockerfile \ --context . \ --platforms linux/amd64,linux/arm64 \ --git-sha "${{ github.sha }}" workflow-docker-image-deploy: needs: [generate-build-number, read-version] runs-on: ubuntu-latest env: QEMU_CPU: max steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main with: tool-cache: false android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - name: Docker Meta id: meta uses: docker/metadata-action@v4 with: images: | oneuptime/workflow ghcr.io/oneuptime/workflow tags: | type=raw,value=release,enable=true type=semver,value=${{needs.read-version.outputs.major_minor}},pattern={{version}},enable=true - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - uses: actions/setup-node@v4 with: node-version: latest - name: Set up QEMU uses: docker/setup-qemu-action@v3 with: image: tonistiigi/binfmt:qemu-v10.0.4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Generate Dockerfile from Dockerfile.tpl run: npm run prerun # Build and deploy nginx. - name: Login to Docker Hub run: | echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - name: Login to GitHub Container Registry run: | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin - name: Build and push run: | bash ./Scripts/GHA/build_docker_images.sh \ --image workflow \ --version "${{needs.read-version.outputs.major_minor}}" \ --dockerfile ./Workflow/Dockerfile \ --context . \ --platforms linux/amd64,linux/arm64 \ --git-sha "${{ github.sha }}" publish-terraform-provider: runs-on: ubuntu-latest needs: [generate-build-number, read-version] env: CI_PIPELINE_ID: ${{github.run_number}} GITHUB_TOKEN: ${{ secrets.SIMLARSEN_GITHUB_PAT }} GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} TERRAFORM_PROVIDER_GITHUB_REPO_DEPLOY_KEY: ${{ secrets.TERRAFORM_PROVIDER_GITHUB_REPO_DEPLOY_KEY }} permissions: contents: write # For creating releases packages: write # For publishing packages steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 # Full history for changelog generation - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 'latest' cache: 'npm' - name: Setup Go uses: actions/setup-go@v5 with: go-version: 'stable' cache: true - name: Install GoReleaser uses: goreleaser/goreleaser-action@v5 with: install-only: true - name: Determine version id: version run: | VERSION="${{needs.read-version.outputs.major_minor}}" echo "version=$VERSION" >> $GITHUB_OUTPUT echo "Publishing Terraform provider version: $VERSION" - name: Install dependencies run: | npm install if [ -d "Common" ]; then cd Common && npm install && cd ..; fi if [ -d "Scripts" ]; then cd Scripts && npm install && cd ..; fi - name: Import GPG key run: | echo '${{ secrets.GPG_PRIVATE_KEY }}' > private.key gpg --import private.key || true rm private.key echo "GPG key imported successfully" gpg --export-secret-keys >~/.gnupg/secring.gpg echo "GPG key exported successfully" - name: Generate Terraform provider run: npm run publish-terraform-provider -- --version "${{ steps.version.outputs.version }}" --github-token "${{ secrets.SIMLARSEN_GITHUB_PAT }}" --github-repo-deploy-key "${{ secrets.TERRAFORM_PROVIDER_GITHUB_REPO_DEPLOY_KEY }}" push-release-tags: name: Push release tags before GitHub release needs: - read-version - generate-build-number - nginx-docker-image-deploy - e2e-docker-image-deploy - home-docker-image-deploy - test-server-docker-image-deploy - otel-collector-docker-image-deploy - status-page-docker-image-deploy - test-docker-image-deploy - telemetry-docker-image-deploy - probe-docker-image-deploy - admin-dashboard-docker-image-deploy - dashboard-docker-image-deploy - app-docker-image-deploy - accounts-docker-image-deploy - ai-agent-docker-image-deploy - docs-docker-image-deploy - worker-docker-image-deploy - workflow-docker-image-deploy - test-e2e-release-saas - test-e2e-release-self-hosted runs-on: ubuntu-latest strategy: fail-fast: false matrix: image: [ "mcp", "nginx", "e2e", "home", "test-server", "otel-collector", "status-page", "test", "telemetry", "probe", "admin-dashboard", "dashboard", "app", "accounts", "ai-agent", "docs", "worker", "workflow" ] steps: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub run: | echo "${{ secrets.DOCKERHUB_PASSWORD }}" | docker login --username "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin - name: Login to GitHub Container Registry run: | echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username "${{ github.repository_owner }}" --password-stdin - name: Create Docker Hub release tag from version run: | VERSION="${{needs.read-version.outputs.major_minor}}" docker buildx imagetools create \ --tag oneuptime/${{ matrix.image }}:release \ ghcr.io/oneuptime/${{ matrix.image }}:${VERSION} - name: Create GHCR release tag from version run: | VERSION="${{needs.read-version.outputs.major_minor}}" docker buildx imagetools create \ --tag ghcr.io/oneuptime/${{ matrix.image }}:release \ ghcr.io/oneuptime/${{ matrix.image }}:${VERSION} - name: Create Docker Hub enterprise release tag from version run: | VERSION="${{needs.read-version.outputs.major_minor}}" docker buildx imagetools create \ --tag oneuptime/${{ matrix.image }}:enterprise-release \ ghcr.io/oneuptime/${{ matrix.image }}:enterprise-${VERSION} - name: Create GHCR enterprise release tag from version run: | VERSION="${{needs.read-version.outputs.major_minor}}" docker buildx imagetools create \ --tag ghcr.io/oneuptime/${{ matrix.image }}:enterprise-release \ ghcr.io/oneuptime/${{ matrix.image }}:enterprise-${VERSION} test-e2e-release-saas: runs-on: ubuntu-latest needs: [telemetry-docker-image-deploy, docs-docker-image-deploy, workflow-docker-image-deploy, accounts-docker-image-deploy, ai-agent-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, dashboard-docker-image-deploy, home-docker-image-deploy, worker-docker-image-deploy, otel-collector-docker-image-deploy, probe-docker-image-deploy, status-page-docker-image-deploy, test-docker-image-deploy, test-server-docker-image-deploy, publish-npm-packages, e2e-docker-image-deploy, helm-chart-deploy, generate-build-number, read-version, nginx-docker-image-deploy] env: CI_PIPELINE_ID: ${{github.run_number}} steps: # Aggressively free disk space before anything else - name: Aggressive Disk Cleanup run: | echo "=== Disk space BEFORE cleanup ===" df -h / # Remove pre-installed software not needed for this job sudo rm -rf /usr/share/dotnet || true sudo rm -rf /usr/local/lib/android || true sudo rm -rf /opt/ghc || true sudo rm -rf /opt/hostedtoolcache || true sudo rm -rf /usr/local/share/boost || true sudo rm -rf /usr/local/graalvm/ || true sudo rm -rf /usr/local/share/powershell || true sudo rm -rf /usr/local/share/chromium || true sudo rm -rf /usr/local/lib/node_modules || true sudo rm -rf /usr/share/swift || true sudo rm -rf /usr/share/miniconda || true sudo rm -rf /usr/lib/google-cloud-sdk || true sudo rm -rf /usr/lib/jvm || true sudo rm -rf /usr/lib/firefox || true sudo rm -rf /usr/lib/heroku || true sudo rm -rf /usr/local/julia* || true sudo rm -rf /opt/az || true sudo rm -rf /opt/microsoft || true sudo rm -rf /opt/pipx || true sudo rm -rf /opt/actionarchivecache || true sudo rm -rf /imagegeneration || true sudo rm -rf /usr/share/az_* || true sudo rm -rf /usr/share/sbt || true sudo rm -rf /usr/share/gradle* || true sudo rm -rf /usr/share/kotlinc || true sudo rm -rf /usr/share/ri || true sudo rm -rf /usr/local/.ghcup || true # Clean apt cache sudo apt-get clean || true sudo rm -rf /var/lib/apt/lists/* || true # Clean temp files sudo rm -rf /tmp/* || true # Docker cleanup docker system prune -af --volumes || true echo "=== Disk space AFTER aggressive cleanup ===" df -h / - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main with: tool-cache: true android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - name: Final Disk Space Check run: | echo "=== Disk space after all cleanup ===" df -h / - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: latest - name: Preinstall and enable billing run: | set -euo pipefail npm run prerun bash ./Tests/Scripts/enable-billing-env-var.sh - name: Pin APP_TAG to versioned release run: | VERSION="${{needs.read-version.outputs.major_minor}}" SANITIZED_VERSION="${VERSION//+/-}" if [ -f config.env ]; then if grep -q '^APP_TAG=' config.env; then sed -i "s/^APP_TAG=.*/APP_TAG=${SANITIZED_VERSION}/" config.env else echo "APP_TAG=${SANITIZED_VERSION}" >> config.env fi else echo "APP_TAG=${SANITIZED_VERSION}" > config.env fi - name: Start Server with version tag run: | export $(grep -v '^#' config.env | xargs) export APP_TAG=${{needs.read-version.outputs.major_minor}} docker compose up --remove-orphans -d npm run status-check - name: Wait for server to start run: bash ./Tests/Scripts/status-check.sh http://localhost - name: Pull E2E test image run: | set -euo pipefail export $(grep -v '^#' config.env | xargs) export APP_TAG=${{needs.read-version.outputs.major_minor}} docker compose -f docker-compose.e2e.yml pull e2e - name: Run E2E Tests. Run docker container e2e in docker compose file run: | set -euo pipefail export $(grep -v '^#' config.env | xargs) export APP_TAG=${{needs.read-version.outputs.major_minor}} trap 'docker compose -f docker-compose.e2e.yml down -v || true' EXIT if ! docker compose -f docker-compose.e2e.yml up --exit-code-from e2e --abort-on-container-exit e2e; then docker compose -f docker-compose.e2e.yml logs e2e exit 1 fi - name: Upload test results uses: actions/upload-artifact@v4 # Run this on failure if: failure() with: # Name of the artifact to upload. # Optional. Default is 'artifact' name: test-results-${{ github.job }}-${{ github.run_attempt }} # A file, directory or wildcard pattern that describes what to upload # Required. path: | ./E2E # Duration after which artifact will expire in days. 0 means using default retention. # Minimum 1 day. # Maximum 90 days unless changed from the repository settings page. # Optional. Defaults to repository settings. retention-days: 7 test-e2e-release-self-hosted: runs-on: ubuntu-latest # After all the jobs runs needs: [telemetry-docker-image-deploy, docs-docker-image-deploy, workflow-docker-image-deploy, accounts-docker-image-deploy, ai-agent-docker-image-deploy, admin-dashboard-docker-image-deploy, app-docker-image-deploy, dashboard-docker-image-deploy, home-docker-image-deploy, worker-docker-image-deploy, otel-collector-docker-image-deploy, probe-docker-image-deploy, status-page-docker-image-deploy, test-docker-image-deploy, test-server-docker-image-deploy, publish-npm-packages, e2e-docker-image-deploy, helm-chart-deploy, generate-build-number, read-version, nginx-docker-image-deploy] env: CI_PIPELINE_ID: ${{github.run_number}} steps: # Aggressively free disk space before anything else - name: Aggressive Disk Cleanup run: | echo "=== Disk space BEFORE cleanup ===" df -h / # Remove pre-installed software not needed for this job sudo rm -rf /usr/share/dotnet || true sudo rm -rf /usr/local/lib/android || true sudo rm -rf /opt/ghc || true sudo rm -rf /opt/hostedtoolcache || true sudo rm -rf /usr/local/share/boost || true sudo rm -rf /usr/local/graalvm/ || true sudo rm -rf /usr/local/share/powershell || true sudo rm -rf /usr/local/share/chromium || true sudo rm -rf /usr/local/lib/node_modules || true sudo rm -rf /usr/share/swift || true sudo rm -rf /usr/share/miniconda || true sudo rm -rf /usr/lib/google-cloud-sdk || true sudo rm -rf /usr/lib/jvm || true sudo rm -rf /usr/lib/firefox || true sudo rm -rf /usr/lib/heroku || true sudo rm -rf /usr/local/julia* || true sudo rm -rf /opt/az || true sudo rm -rf /opt/microsoft || true sudo rm -rf /opt/pipx || true sudo rm -rf /opt/actionarchivecache || true sudo rm -rf /imagegeneration || true sudo rm -rf /usr/share/az_* || true sudo rm -rf /usr/share/sbt || true sudo rm -rf /usr/share/gradle* || true sudo rm -rf /usr/share/kotlinc || true sudo rm -rf /usr/share/ri || true sudo rm -rf /usr/local/.ghcup || true # Clean apt cache sudo apt-get clean || true sudo rm -rf /var/lib/apt/lists/* || true # Clean temp files sudo rm -rf /tmp/* || true # Docker cleanup docker system prune -af --volumes || true echo "=== Disk space AFTER aggressive cleanup ===" df -h / - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main with: tool-cache: true android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - name: Final Disk Space Check run: | echo "=== Disk space after all cleanup ===" df -h / - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: latest - name: Preinstall run: | set -euo pipefail npm run prerun - name: Pin APP_TAG to versioned release run: | VERSION="${{needs.read-version.outputs.major_minor}}" SANITIZED_VERSION="${VERSION//+/-}" if [ -f config.env ]; then if grep -q '^APP_TAG=' config.env; then sed -i "s/^APP_TAG=.*/APP_TAG=${SANITIZED_VERSION}/" config.env else echo "APP_TAG=${SANITIZED_VERSION}" >> config.env fi else echo "APP_TAG=${SANITIZED_VERSION}" > config.env fi - name: Start Server with version tag run: | export $(grep -v '^#' config.env | xargs) export APP_TAG=${{needs.read-version.outputs.major_minor}} docker compose up --remove-orphans -d npm run status-check - name: Wait for server to start run: bash ./Tests/Scripts/status-check.sh http://localhost - name: Pull E2E test image run: | set -euo pipefail export $(grep -v '^#' config.env | xargs) export APP_TAG=${{needs.read-version.outputs.major_minor}} docker compose -f docker-compose.e2e.yml pull e2e - name: Run E2E Tests. Run docker container e2e in docker compose file run: | set -euo pipefail export $(grep -v '^#' config.env | xargs) export APP_TAG=${{needs.read-version.outputs.major_minor}} trap 'docker compose -f docker-compose.e2e.yml down -v || true' EXIT if ! docker compose -f docker-compose.e2e.yml up --exit-code-from e2e --abort-on-container-exit e2e; then docker compose -f docker-compose.e2e.yml logs e2e exit 1 fi - name: Upload test results uses: actions/upload-artifact@v4 # Run this on failure if: failure() with: # Name of the artifact to upload. # Optional. Default is 'artifact' name: test-results-${{ github.job }}-${{ github.run_attempt }} # A file, directory or wildcard pattern that describes what to upload # Required. path: | ./E2E # Duration after which artifact will expire in days. 0 means using default retention. # Minimum 1 day. # Maximum 90 days unless changed from the repository settings page. # Optional. Defaults to repository settings. retention-days: 7 draft-github-release: name: Create draft GitHub release needs: [test-e2e-release-saas, test-e2e-release-self-hosted, generate-build-number, read-version, push-release-tags] runs-on: ubuntu-latest if: github.ref == 'refs/heads/release' permissions: contents: write steps: - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - run: echo "${{needs.generate-build-number.outputs.build_number}}" - name: "Build Changelog" id: build_changelog uses: mikepenz/release-changelog-builder-action@v4.2.0 with: configuration: "./Scripts/Release/ChangelogConfig.json" - run: echo "Changelog:" - run: echo "${{steps.build_changelog.outputs.changelog}}" - name: Fallback to commit messages if changelog empty id: fallback_changelog shell: bash run: | set -euo pipefail CHANGELOG_CONTENT="${{steps.build_changelog.outputs.changelog}}" OLD_PLACEHOLDER="No significant changes were made. We have just fixed minor bugs for this release. You can find the detailed information in the commit history." NEW_PLACEHOLDER="(auto) No categorized pull requests. Fallback will list raw commit messages." if echo "$CHANGELOG_CONTENT" | grep -Fq "$OLD_PLACEHOLDER" || echo "$CHANGELOG_CONTENT" | grep -Fq "$NEW_PLACEHOLDER"; then echo "Detected empty placeholder changelog. Building commit list fallback." # Find previous tag (skip the most recent tag which might be for an older release). If none, include all commits. if prev_tag=$(git describe --tags --abbrev=0 $(git rev-list --tags --skip=1 --max-count=1) 2>/dev/null); then echo "Previous tag: $prev_tag" commits=$(git log --pretty=format:'- %s (%h)' "$prev_tag"..HEAD) else echo "No previous tag found; using full commit history on this branch." commits=$(git log --pretty=format:'- %s (%h)') fi # If still empty (e.g., no commits), keep placeholder to avoid empty body. if [ -z "$commits" ]; then commits="(no commits found)" fi { echo "changelog<> "$GITHUB_OUTPUT" else # Pass through original changelog { echo "changelog<> "$GITHUB_OUTPUT" fi - uses: ncipollo/release-action@v1 with: tag: "${{needs.read-version.outputs.major_minor}}" artifactErrorsFailBuild: true draft: true allowUpdates: true prerelease: false body: | ${{steps.fallback_changelog.outputs.changelog}} infrastructure-agent-deploy: needs: [draft-github-release, generate-build-number, read-version] runs-on: ubuntu-latest steps: - name: Free Disk Space (Ubuntu) uses: jlumbroso/free-disk-space@main with: tool-cache: false android: true dotnet: true haskell: true large-packages: true docker-images: true swap-storage: true - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - name: Set up Go uses: actions/setup-go@v4 - name: Install GoReleaser uses: goreleaser/goreleaser-action@v6.1.0 with: install-only: true - name: GoReleaser Version run: goreleaser -v # This tool is used to generate .rpm and .deb packages - name: Install NFPM run: go install github.com/goreleaser/nfpm/v2/cmd/nfpm@latest - name: Show GoReleaser version run: goreleaser -v - name: Run GoReleaser run: cd InfrastructureAgent && export GORELEASER_CURRENT_TAG=${{needs.read-version.outputs.major_minor}} && goreleaser release --clean --snapshot - name: Release MSI Images run: cd InfrastructureAgent && bash build-msi.sh ${{needs.read-version.outputs.major_minor}} # Upload binaries to github release - name: Release uses: softprops/action-gh-release@v2 with: files: | InfrastructureAgent/dist/* token: ${{ secrets.GITHUB_TOKEN }} draft: true prerelease: false tag_name: ${{needs.read-version.outputs.major_minor}} # Build Android release APK and attach to GitHub Release. # Required secrets setup guide: MobileApp/docs/RELEASE_SIGNING.md mobile-app-android-deploy: needs: [draft-github-release, generate-build-number, read-version] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - name: Setup Java 17 uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '17' - uses: actions/setup-node@v4 with: node-version: latest - name: Install dependencies run: cd MobileApp && npm install - name: Generate native Android project run: cd MobileApp && npx expo prebuild --platform android --no-install - name: Decode Android keystore run: | echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > /tmp/release.keystore - name: Build release APK env: ANDROID_KEYSTORE_FILE: /tmp/release.keystore ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} run: | cd MobileApp/android ./gradlew assembleRelease \ -PversionName=${{ needs.read-version.outputs.major_minor }} \ -PversionCode=${{ needs.generate-build-number.outputs.build_number }} - name: Upload APK to GitHub Release uses: softprops/action-gh-release@v2 with: files: MobileApp/android/app/build/outputs/apk/release/*.apk token: ${{ secrets.GITHUB_TOKEN }} draft: true prerelease: false tag_name: ${{ needs.read-version.outputs.major_minor }} # Build iOS release IPA and attach to GitHub Release. # Required secrets setup guide: MobileApp/docs/RELEASE_SIGNING.md mobile-app-ios-deploy: needs: [draft-github-release, generate-build-number, read-version] runs-on: macos-latest steps: - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - uses: actions/setup-node@v4 with: node-version: latest - name: Install dependencies run: cd MobileApp && npm install - name: Generate native iOS project run: cd MobileApp && npx expo prebuild --platform ios --no-install - name: Install CocoaPods dependencies run: cd MobileApp/ios && pod install - name: Import signing certificate env: IOS_DISTRIBUTION_CERTIFICATE_BASE64: ${{ secrets.IOS_DISTRIBUTION_CERTIFICATE_BASE64 }} IOS_DISTRIBUTION_CERTIFICATE_PASSWORD: ${{ secrets.IOS_DISTRIBUTION_CERTIFICATE_PASSWORD }} run: | CERTIFICATE_PATH=$RUNNER_TEMP/distribution.p12 KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db KEYCHAIN_PASSWORD=$(openssl rand -base64 32) echo "$IOS_DISTRIBUTION_CERTIFICATE_BASE64" | base64 --decode > "$CERTIFICATE_PATH" security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" security import "$CERTIFICATE_PATH" -P "$IOS_DISTRIBUTION_CERTIFICATE_PASSWORD" \ -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH" security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" security list-keychain -d user -s "$KEYCHAIN_PATH" - name: Install provisioning profile env: IOS_PROVISIONING_PROFILE_BASE64: ${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }} run: | PROFILE_PATH=$RUNNER_TEMP/profile.mobileprovision echo "$IOS_PROVISIONING_PROFILE_BASE64" | base64 --decode > "$PROFILE_PATH" mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles cp "$PROFILE_PATH" ~/Library/MobileDevice/Provisioning\ Profiles/ - name: Build archive run: | cd MobileApp xcodebuild -workspace ios/OneUptime.xcworkspace \ -scheme OneUptime \ -configuration Release \ -sdk iphoneos \ -archivePath $RUNNER_TEMP/OneUptime.xcarchive \ archive \ MARKETING_VERSION=${{ needs.read-version.outputs.major_minor }} \ CURRENT_PROJECT_VERSION=${{ needs.generate-build-number.outputs.build_number }} - name: Prepare ExportOptions.plist with team ID env: IOS_TEAM_ID: ${{ secrets.IOS_TEAM_ID }} run: | /usr/libexec/PlistBuddy -c "Add :teamID string $IOS_TEAM_ID" MobileApp/ios/ExportOptions.plist || \ /usr/libexec/PlistBuddy -c "Set :teamID $IOS_TEAM_ID" MobileApp/ios/ExportOptions.plist - name: Export IPA run: | cd MobileApp xcodebuild -exportArchive \ -archivePath $RUNNER_TEMP/OneUptime.xcarchive \ -exportOptionsPlist ios/ExportOptions.plist \ -exportPath $RUNNER_TEMP/build - name: Upload IPA to GitHub Release uses: softprops/action-gh-release@v2 with: files: ${{ runner.temp }}/build/*.ipa token: ${{ secrets.GITHUB_TOKEN }} draft: true prerelease: false tag_name: ${{ needs.read-version.outputs.major_minor }} - name: Cleanup keychain if: always() run: | security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true finalize-github-release: name: Publish GitHub release needs: [infrastructure-agent-deploy, generate-build-number, read-version] runs-on: ubuntu-latest if: github.ref == 'refs/heads/release' permissions: contents: write steps: - name: Publish release uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const tag = '${{needs.read-version.outputs.major_minor}}'; try { const releases = await github.paginate(github.rest.repos.listReleases, { owner: context.repo.owner, repo: context.repo.repo, per_page: 100, }); const release = releases.find((item) => item.tag_name === tag); if (!release) { throw new Error(`Release with tag ${tag} not found in repository ${context.repo.owner}/${context.repo.repo}`); } await github.rest.repos.updateRelease({ owner: context.repo.owner, repo: context.repo.repo, release_id: release.id, draft: false, prerelease: false, make_latest: 'true', }); console.log(`Published release for ${tag}`); } catch (error) { throw new Error(`Failed to publish release for tag ${tag}: ${error.message ?? error}`); } # Publish Android app to Google Play Store. # This job only runs when manually triggered via workflow_dispatch with publish_android_to_store=true. # Required secrets: # - GOOGLE_PLAY_SERVICE_ACCOUNT_JSON: Service account JSON key with Play Store publishing access # - ANDROID_KEYSTORE_BASE64: Base64-encoded release keystore # - ANDROID_KEYSTORE_PASSWORD: Keystore password # - ANDROID_KEY_ALIAS: Signing key alias # - ANDROID_KEY_PASSWORD: Signing key password publish-android-to-play-store: needs: [generate-build-number, read-version] if: github.event_name == 'workflow_dispatch' && github.event.inputs.publish_android_to_store == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - name: Setup Java 17 uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: '17' - uses: actions/setup-node@v4 with: node-version: latest - name: Install dependencies run: cd MobileApp && npm install - name: Generate native Android project run: cd MobileApp && npx expo prebuild --platform android --no-install - name: Decode Android keystore run: | echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 --decode > /tmp/release.keystore - name: Build release AAB for Play Store env: ANDROID_KEYSTORE_FILE: /tmp/release.keystore ANDROID_KEYSTORE_PASSWORD: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} run: | cd MobileApp/android ./gradlew bundleRelease \ -PversionName=${{ needs.read-version.outputs.major_minor }} \ -PversionCode=${{ needs.generate-build-number.outputs.build_number }} - name: Upload AAB as build artifact uses: actions/upload-artifact@v4 if: always() with: name: android-aab-${{ needs.read-version.outputs.major_minor }} path: MobileApp/android/app/build/outputs/bundle/release/*.aab retention-days: 90 - name: Upload AAB to Google Play Store uses: r0adkll/upload-google-play@v1 with: serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }} packageName: com.oneuptime.oncall releaseFiles: MobileApp/android/app/build/outputs/bundle/release/*.aab track: production status: completed # Publish iOS app to App Store. # This job only runs when manually triggered via workflow_dispatch with publish_ios_to_store=true. # Required secrets: # - IOS_DISTRIBUTION_CERTIFICATE_BASE64: Base64-encoded P12 distribution certificate # - IOS_DISTRIBUTION_CERTIFICATE_PASSWORD: Password for the P12 certificate # - IOS_PROVISIONING_PROFILE_BASE64: Base64-encoded App Store distribution provisioning profile # - APP_STORE_CONNECT_API_KEY_ID: App Store Connect API key ID # - APP_STORE_CONNECT_API_ISSUER_ID: App Store Connect API issuer ID # - APP_STORE_CONNECT_API_KEY_BASE64: Base64-encoded App Store Connect API private key (.p8) publish-ios-to-app-store: needs: [generate-build-number, read-version] if: github.event_name == 'workflow_dispatch' && github.event.inputs.publish_ios_to_store == 'true' runs-on: macos-latest steps: - uses: actions/checkout@v4 with: ref: ${{ github.ref }} - uses: actions/setup-node@v4 with: node-version: latest - name: Install dependencies run: cd MobileApp && npm install - name: Generate native iOS project run: cd MobileApp && npx expo prebuild --platform ios --no-install - name: Install CocoaPods dependencies run: cd MobileApp/ios && pod install - name: Setup Apple signing certificate and provisioning profile env: IOS_DISTRIBUTION_CERTIFICATE_BASE64: ${{ secrets.IOS_DISTRIBUTION_CERTIFICATE_BASE64 }} IOS_DISTRIBUTION_CERTIFICATE_PASSWORD: ${{ secrets.IOS_DISTRIBUTION_CERTIFICATE_PASSWORD }} IOS_PROVISIONING_PROFILE_BASE64: ${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }} run: | # Create a temporary keychain KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db KEYCHAIN_PASSWORD=$(openssl rand -base64 32) security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH security set-keychain-settings -lut 21600 $KEYCHAIN_PATH security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH # Import distribution certificate CERTIFICATE_PATH=$RUNNER_TEMP/distribution_certificate.p12 echo "$IOS_DISTRIBUTION_CERTIFICATE_BASE64" | base64 --decode > $CERTIFICATE_PATH security import $CERTIFICATE_PATH -P "$IOS_DISTRIBUTION_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH security list-keychain -d user -s $KEYCHAIN_PATH # Install provisioning profile PROFILE_PATH=$RUNNER_TEMP/distribution_profile.mobileprovision echo "$IOS_PROVISIONING_PROFILE_BASE64" | base64 --decode > $PROFILE_PATH mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles PROFILE_UUID=$(/usr/libexec/PlistBuddy -c "Print UUID" /dev/stdin <<< $(/usr/bin/security cms -D -i $PROFILE_PATH)) cp $PROFILE_PATH ~/Library/MobileDevice/Provisioning\ Profiles/$PROFILE_UUID.mobileprovision echo "KEYCHAIN_PATH=$KEYCHAIN_PATH" >> $GITHUB_ENV echo "PROFILE_UUID=$PROFILE_UUID" >> $GITHUB_ENV - name: Build iOS archive run: | cd MobileApp/ios xcodebuild archive \ -workspace OneUptimeOnCall.xcworkspace \ -scheme OneUptimeOnCall \ -configuration Release \ -archivePath $RUNNER_TEMP/OneUptimeOnCall.xcarchive \ -destination 'generic/platform=iOS' \ MARKETING_VERSION=${{ needs.read-version.outputs.major_minor }} \ CURRENT_PROJECT_VERSION=${{ needs.generate-build-number.outputs.build_number }} \ CODE_SIGN_STYLE=Manual \ OTHER_CODE_SIGN_FLAGS="--keychain ${{ env.KEYCHAIN_PATH }}" \ -allowProvisioningUpdates - name: Export IPA run: | # Create export options plist cat > $RUNNER_TEMP/ExportOptions.plist << EOF method app-store destination upload signingStyle manual provisioningProfiles com.oneuptime.oncall ${{ env.PROFILE_UUID }} uploadSymbols EOF xcodebuild -exportArchive \ -archivePath $RUNNER_TEMP/OneUptimeOnCall.xcarchive \ -exportOptionsPlist $RUNNER_TEMP/ExportOptions.plist \ -exportPath $RUNNER_TEMP/ios-export - name: Upload IPA as build artifact uses: actions/upload-artifact@v4 if: always() with: name: ios-ipa-${{ needs.read-version.outputs.major_minor }} path: ${{ runner.temp }}/ios-export/*.ipa retention-days: 90 - name: Upload to App Store Connect env: APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_ISSUER_ID }} APP_STORE_CONNECT_API_KEY_BASE64: ${{ secrets.APP_STORE_CONNECT_API_KEY_BASE64 }} run: | # Decode API key API_KEY_PATH=$RUNNER_TEMP/AuthKey_${APP_STORE_CONNECT_API_KEY_ID}.p8 echo "$APP_STORE_CONNECT_API_KEY_BASE64" | base64 --decode > $API_KEY_PATH xcrun notarytool store-credentials "appstore-credentials" \ --key $API_KEY_PATH \ --key-id $APP_STORE_CONNECT_API_KEY_ID \ --issuer $APP_STORE_CONNECT_API_ISSUER_ID 2>/dev/null || true xcrun altool --upload-app \ --type ios \ --file $(find $RUNNER_TEMP/ios-export -name '*.ipa' -print -quit) \ --apiKey $APP_STORE_CONNECT_API_KEY_ID \ --apiIssuer $APP_STORE_CONNECT_API_ISSUER_ID - name: Cleanup signing artifacts if: always() run: | security delete-keychain ${{ env.KEYCHAIN_PATH }} 2>/dev/null || true rm -f $RUNNER_TEMP/distribution_certificate.p12 rm -f $RUNNER_TEMP/distribution_profile.mobileprovision rm -f $RUNNER_TEMP/AuthKey_*.p8