name: Prepare new release on: workflow_dispatch: inputs: branch: description: 'Branch to release from (must be main or release/*)' required: true default: 'main' type: string serverBump: description: 'Bump server version (only patch allowed on release/* branches)' required: true default: 'false' type: choice options: - 'false' - major - minor - patch mobileBump: description: 'Bump mobile build number' required: false type: boolean skipTranslations: description: 'Skip translations' required: false type: boolean concurrency: group: ${{ github.workflow }}-${{ github.ref }}-root cancel-in-progress: true permissions: {} jobs: validate_inputs: runs-on: ubuntu-latest permissions: {} steps: - name: Validate branch and bump combination env: BRANCH: ${{ inputs.branch }} SERVER_BUMP: ${{ inputs.serverBump }} run: | set -euo pipefail if [[ "$BRANCH" != "main" && "$BRANCH" != release/* ]]; then echo "::error::branch must be 'main' or start with 'release/' (got '$BRANCH')" exit 1 fi if [[ "$BRANCH" != "main" && "$SERVER_BUMP" != "false" && "$SERVER_BUMP" != "patch" ]]; then echo "::error::only 'patch' (or 'false') serverBump is allowed on '$BRANCH'" exit 1 fi merge_translations: needs: [validate_inputs] uses: ./.github/workflows/merge-translations.yml with: # Weblate tracks main only, so skip translations when releasing from a release/* branch. skip: ${{ inputs.skipTranslations || inputs.branch != 'main' }} permissions: pull-requests: write secrets: PUSH_O_MATIC_APP_ID: ${{ secrets.PUSH_O_MATIC_APP_ID }} PUSH_O_MATIC_APP_KEY: ${{ secrets.PUSH_O_MATIC_APP_KEY }} WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }} bump_version: runs-on: ubuntu-latest needs: [merge_translations] outputs: ref: ${{ steps.push-tag.outputs.commit_long_sha }} version: ${{ steps.output.outputs.version }} permissions: {} # No job-level permissions are needed because it uses the app-token steps: - name: Generate a token id: generate-token uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 with: app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: token: ${{ steps.generate-token.outputs.token }} persist-credentials: true ref: ${{ inputs.branch }} - name: Install uv uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 - name: Setup pnpm uses: pnpm/action-setup@08c4be7e2e672a47d11bd04269e27e5f3e8529cb # v6.0.0 - name: Setup Node uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 with: node-version-file: './server/.nvmrc' cache: 'pnpm' cache-dependency-path: '**/pnpm-lock.yaml' - name: Bump version env: SERVER_BUMP: ${{ inputs.serverBump }} MOBILE_BUMP: ${{ inputs.mobileBump }} run: misc/release/pump-version.sh -s "${SERVER_BUMP}" -m "${MOBILE_BUMP}" - id: output run: echo "version=$IMMICH_VERSION" >> $GITHUB_OUTPUT - name: Commit and tag id: push-tag uses: EndBug/add-and-commit@290ea2c423ad77ca9c62ae0f5b224379612c0321 # v10.0.0 with: default_author: github_actions message: 'chore: version ${{ steps.output.outputs.version }}' tag: ${{ steps.output.outputs.version }} push: true build_mobile: # Mobile build numbers are monotonic per store; releasing from a release/* branch # would collide with build numbers already shipped from main. Skip mobile on patch # releases — handle mobile patches on main instead. if: ${{ inputs.branch == 'main' }} uses: ./.github/workflows/build-mobile.yml needs: bump_version permissions: contents: read secrets: KEY_JKS: ${{ secrets.KEY_JKS }} ALIAS: ${{ secrets.ALIAS }} ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }} # iOS secrets APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }} APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ISSUER_ID }} APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }} IOS_CERTIFICATE_P12: ${{ secrets.IOS_CERTIFICATE_P12 }} IOS_CERTIFICATE_PASSWORD: ${{ secrets.IOS_CERTIFICATE_PASSWORD }} FASTLANE_TEAM_ID: ${{ secrets.FASTLANE_TEAM_ID }} with: ref: ${{ needs.bump_version.outputs.ref }} environment: production prepare_release: runs-on: ubuntu-latest needs: [build_mobile, bump_version] # Run even when build_mobile is skipped (patch release from release/* branch). if: ${{ always() && needs.bump_version.result == 'success' && (needs.build_mobile.result == 'success' || needs.build_mobile.result == 'skipped') }} permissions: actions: read # To download the app artifact # No content permissions are needed because it uses the app-token steps: - name: Generate a token id: generate-token uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 with: app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: token: ${{ steps.generate-token.outputs.token }} persist-credentials: false ref: ${{ needs.bump_version.outputs.ref }} - name: Download APK if: ${{ needs.build_mobile.result == 'success' }} uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: release-apk-signed github-token: ${{ steps.generate-token.outputs.token }} - name: Assemble release assets id: assets env: HAS_APK: ${{ needs.build_mobile.result == 'success' }} run: | { echo 'files<> "$GITHUB_OUTPUT" - name: Create draft release uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2 with: draft: true tag_name: ${{ needs.bump_version.outputs.version }} target_commitish: ${{ inputs.branch }} token: ${{ steps.generate-token.outputs.token }} generate_release_notes: true body_path: misc/release/notes.tmpl files: ${{ steps.assets.outputs.files }} backport_archived_versions: # When releasing from a release/* branch, the archived-versions.json update # lives on that branch only. Open a PR to mirror the new entry onto main so # main's docs keep a complete archive list. if: ${{ inputs.branch != 'main' && needs.bump_version.result == 'success' }} runs-on: ubuntu-latest needs: [bump_version, prepare_release] permissions: {} # uses the app token steps: - name: Generate a token id: generate-token uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 with: app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }} private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }} - name: Checkout main uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: token: ${{ steps.generate-token.outputs.token }} persist-credentials: false ref: main - name: Update archived versions on main env: VERSION: ${{ needs.bump_version.outputs.version }} run: ./misc/release/archive-version.js "${VERSION#v}" - name: Open backport PR uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 with: token: ${{ steps.generate-token.outputs.token }} branch: backport/archived-versions-${{ needs.bump_version.outputs.version }} base: main commit-message: 'chore(docs): archive ${{ needs.bump_version.outputs.version }}' title: 'chore(docs): archive ${{ needs.bump_version.outputs.version }}' body: | Backports the `archived-versions.json` entry for ${{ needs.bump_version.outputs.version }}, released from `${{ inputs.branch }}`, so main's docs archive list stays complete. add-paths: docs/static/archived-versions.json delete-branch: true