diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 42b2bdcbb..56b647c8d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,6 +5,15 @@ on: types: [published] workflow_dispatch: inputs: + version_tag: + description: 'Version tag for Docker images (e.g., 0.26.2). Auto-detected from release/* branch name if empty.' + required: false + type: string + tag_latest: + description: 'Also tag images as :latest' + required: false + default: false + type: boolean build_browser_extensions: description: 'Build browser extensions' required: false @@ -51,8 +60,95 @@ jobs: echo "✅ Tag is on a release branch" - upload-install-script: + # Detect version from input, branch name, or release tag + detect-version: + if: always() && (github.event_name != 'release' || needs.valid-release.result == 'success') needs: [valid-release] + runs-on: ubuntu-latest + outputs: + version: ${{ steps.detect.outputs.version }} + tag_latest: ${{ steps.detect.outputs.tag_latest }} + all_in_one_exists: ${{ steps.check-images.outputs.all_in_one_exists }} + multi_container_exists: ${{ steps.check-images.outputs.multi_container_exists }} + steps: + - name: Detect version + id: detect + run: | + # Priority: 1) input version_tag, 2) release tag, 3) branch name pattern + if [ -n "${{ inputs.version_tag }}" ]; then + VERSION="${{ inputs.version_tag }}" + echo "Using input version_tag: $VERSION" + elif [ "${{ github.event_name }}" = "release" ]; then + VERSION="${{ github.event.release.tag_name }}" + echo "Using release tag: $VERSION" + elif [[ "${{ github.ref_name }}" =~ ^release/(.+)$ ]]; then + VERSION="${BASH_REMATCH[1]}" + echo "Extracted version from branch name: $VERSION" + else + VERSION="" + echo "No version detected, will use branch-based tags" + fi + + # Normalize version: strip leading 'v' if present for consistent Docker tags + VERSION="${VERSION#v}" + echo "Normalized version: $VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT + + # Determine if we should tag as latest + if [ "${{ github.event_name }}" = "release" ] && [ "${{ github.event.release.prerelease }}" = "false" ]; then + echo "tag_latest=true" >> $GITHUB_OUTPUT + elif [ "${{ inputs.tag_latest }}" = "true" ]; then + echo "tag_latest=true" >> $GITHUB_OUTPUT + else + echo "tag_latest=false" >> $GITHUB_OUTPUT + fi + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Check if Docker images already exist + id: check-images + run: | + VERSION="${{ steps.detect.outputs.version }}" + if [ -z "$VERSION" ]; then + echo "all_in_one_exists=false" >> $GITHUB_OUTPUT + echo "multi_container_exists=false" >> $GITHUB_OUTPUT + echo "No version specified, images will be built" + exit 0 + fi + + # Check all-in-one image + if docker manifest inspect ghcr.io/aliasvault/aliasvault:${VERSION} > /dev/null 2>&1; then + echo "all_in_one_exists=true" >> $GITHUB_OUTPUT + echo "✅ All-in-one image ghcr.io/aliasvault/aliasvault:${VERSION} exists" + else + echo "all_in_one_exists=false" >> $GITHUB_OUTPUT + echo "❌ All-in-one image does not exist" + fi + + # Check multi-container images (check a few key ones) + MULTI_IMAGES=( + "ghcr.io/aliasvault/api" + "ghcr.io/aliasvault/client" + "ghcr.io/aliasvault/admin" + ) + MULTI_EXISTS=true + for IMAGE in "${MULTI_IMAGES[@]}"; do + if ! docker manifest inspect ${IMAGE}:${VERSION} > /dev/null 2>&1; then + echo "❌ Multi-container image ${IMAGE}:${VERSION} does not exist" + MULTI_EXISTS=false + break + fi + echo "✅ ${IMAGE}:${VERSION} exists" + done + echo "multi_container_exists=${MULTI_EXISTS}" >> $GITHUB_OUTPUT + + upload-install-script: + needs: [valid-release, detect-version] if: always() && (github.event_name != 'release' || needs.valid-release.result == 'success') runs-on: ubuntu-latest permissions: @@ -137,8 +233,8 @@ jobs: ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} build-and-push-docker-multi-container: - needs: [valid-release] - if: always() && (github.event_name != 'release' || needs.valid-release.result == 'success') && (github.event_name == 'release' || inputs.build_multi_container) + needs: [valid-release, detect-version] + if: always() && (github.event_name != 'release' || needs.valid-release.result == 'success') && (github.event_name == 'release' || inputs.build_multi_container) && needs.detect-version.outputs.multi_container_exists != 'true' runs-on: ubuntu-latest permissions: contents: read @@ -169,11 +265,10 @@ jobs: with: images: ghcr.io/aliasvault/postgres tags: | - type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }} - type=semver,pattern={{version}},enable=${{ github.event_name == 'release' || github.ref_type == 'tag' }} - type=ref,event=tag,enable=${{ github.ref_type == 'tag' }} - type=ref,event=branch,enable=${{ github.ref_type == 'branch' }} - type=raw,value={{branch}}-{{sha}},enable=${{ github.ref_type == 'branch' }} + type=raw,value=${{ needs.detect-version.outputs.version }},enable=${{ needs.detect-version.outputs.version != '' }} + type=raw,value=latest,enable=${{ needs.detect-version.outputs.tag_latest == 'true' }} + type=ref,event=branch,enable=${{ needs.detect-version.outputs.version == '' }} + type=raw,value={{branch}}-{{sha}},enable=${{ needs.detect-version.outputs.version == '' }} labels: | org.opencontainers.image.title=AliasVault PostgreSQL org.opencontainers.image.description=PostgreSQL database for AliasVault. Part of multi-container setup and can be deployed via install.sh (see docs.aliasvault.net) @@ -188,11 +283,10 @@ jobs: with: images: ghcr.io/aliasvault/api tags: | - type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }} - type=semver,pattern={{version}},enable=${{ github.event_name == 'release' || github.ref_type == 'tag' }} - type=ref,event=tag,enable=${{ github.ref_type == 'tag' }} - type=ref,event=branch,enable=${{ github.ref_type == 'branch' }} - type=raw,value={{branch}}-{{sha}},enable=${{ github.ref_type == 'branch' }} + type=raw,value=${{ needs.detect-version.outputs.version }},enable=${{ needs.detect-version.outputs.version != '' }} + type=raw,value=latest,enable=${{ needs.detect-version.outputs.tag_latest == 'true' }} + type=ref,event=branch,enable=${{ needs.detect-version.outputs.version == '' }} + type=raw,value={{branch}}-{{sha}},enable=${{ needs.detect-version.outputs.version == '' }} labels: | org.opencontainers.image.title=AliasVault API org.opencontainers.image.description=REST API backend for AliasVault. Part of multi-container setup and can be deployed via install.sh (see docs.aliasvault.net) @@ -207,11 +301,10 @@ jobs: with: images: ghcr.io/aliasvault/client tags: | - type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }} - type=semver,pattern={{version}},enable=${{ github.event_name == 'release' || github.ref_type == 'tag' }} - type=ref,event=tag,enable=${{ github.ref_type == 'tag' }} - type=ref,event=branch,enable=${{ github.ref_type == 'branch' }} - type=raw,value={{branch}}-{{sha}},enable=${{ github.ref_type == 'branch' }} + type=raw,value=${{ needs.detect-version.outputs.version }},enable=${{ needs.detect-version.outputs.version != '' }} + type=raw,value=latest,enable=${{ needs.detect-version.outputs.tag_latest == 'true' }} + type=ref,event=branch,enable=${{ needs.detect-version.outputs.version == '' }} + type=raw,value={{branch}}-{{sha}},enable=${{ needs.detect-version.outputs.version == '' }} labels: | org.opencontainers.image.title=AliasVault Client org.opencontainers.image.description=Blazor WebAssembly client UI for AliasVault. Part of multi-container setup and can be deployed via install.sh (see docs.aliasvault.net) @@ -226,11 +319,10 @@ jobs: with: images: ghcr.io/aliasvault/admin tags: | - type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }} - type=semver,pattern={{version}},enable=${{ github.event_name == 'release' || github.ref_type == 'tag' }} - type=ref,event=tag,enable=${{ github.ref_type == 'tag' }} - type=ref,event=branch,enable=${{ github.ref_type == 'branch' }} - type=raw,value={{branch}}-{{sha}},enable=${{ github.ref_type == 'branch' }} + type=raw,value=${{ needs.detect-version.outputs.version }},enable=${{ needs.detect-version.outputs.version != '' }} + type=raw,value=latest,enable=${{ needs.detect-version.outputs.tag_latest == 'true' }} + type=ref,event=branch,enable=${{ needs.detect-version.outputs.version == '' }} + type=raw,value={{branch}}-{{sha}},enable=${{ needs.detect-version.outputs.version == '' }} labels: | org.opencontainers.image.title=AliasVault Admin org.opencontainers.image.description=Admin portal for AliasVault. Part of multi-container setup and can be deployed via install.sh (see docs.aliasvault.net) @@ -245,11 +337,10 @@ jobs: with: images: ghcr.io/aliasvault/reverse-proxy tags: | - type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }} - type=semver,pattern={{version}},enable=${{ github.event_name == 'release' || github.ref_type == 'tag' }} - type=ref,event=tag,enable=${{ github.ref_type == 'tag' }} - type=ref,event=branch,enable=${{ github.ref_type == 'branch' }} - type=raw,value={{branch}}-{{sha}},enable=${{ github.ref_type == 'branch' }} + type=raw,value=${{ needs.detect-version.outputs.version }},enable=${{ needs.detect-version.outputs.version != '' }} + type=raw,value=latest,enable=${{ needs.detect-version.outputs.tag_latest == 'true' }} + type=ref,event=branch,enable=${{ needs.detect-version.outputs.version == '' }} + type=raw,value={{branch}}-{{sha}},enable=${{ needs.detect-version.outputs.version == '' }} labels: | org.opencontainers.image.title=AliasVault Reverse Proxy org.opencontainers.image.description=Nginx reverse proxy for AliasVault. Part of multi-container setup and can be deployed via install.sh (see docs.aliasvault.net) @@ -264,11 +355,10 @@ jobs: with: images: ghcr.io/aliasvault/smtp tags: | - type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }} - type=semver,pattern={{version}},enable=${{ github.event_name == 'release' || github.ref_type == 'tag' }} - type=ref,event=tag,enable=${{ github.ref_type == 'tag' }} - type=ref,event=branch,enable=${{ github.ref_type == 'branch' }} - type=raw,value={{branch}}-{{sha}},enable=${{ github.ref_type == 'branch' }} + type=raw,value=${{ needs.detect-version.outputs.version }},enable=${{ needs.detect-version.outputs.version != '' }} + type=raw,value=latest,enable=${{ needs.detect-version.outputs.tag_latest == 'true' }} + type=ref,event=branch,enable=${{ needs.detect-version.outputs.version == '' }} + type=raw,value={{branch}}-{{sha}},enable=${{ needs.detect-version.outputs.version == '' }} labels: | org.opencontainers.image.title=AliasVault SMTP Service org.opencontainers.image.description=SMTP service for AliasVault. Part of multi-container setup and can be deployed via install.sh (see docs.aliasvault.net) @@ -283,11 +373,10 @@ jobs: with: images: ghcr.io/aliasvault/task-runner tags: | - type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }} - type=semver,pattern={{version}},enable=${{ github.event_name == 'release' || github.ref_type == 'tag' }} - type=ref,event=tag,enable=${{ github.ref_type == 'tag' }} - type=ref,event=branch,enable=${{ github.ref_type == 'branch' }} - type=raw,value={{branch}}-{{sha}},enable=${{ github.ref_type == 'branch' }} + type=raw,value=${{ needs.detect-version.outputs.version }},enable=${{ needs.detect-version.outputs.version != '' }} + type=raw,value=latest,enable=${{ needs.detect-version.outputs.tag_latest == 'true' }} + type=ref,event=branch,enable=${{ needs.detect-version.outputs.version == '' }} + type=raw,value={{branch}}-{{sha}},enable=${{ needs.detect-version.outputs.version == '' }} labels: | org.opencontainers.image.title=AliasVault TaskRunner org.opencontainers.image.description=Background task runner for AliasVault. Part of multi-container setup and can be deployed via install.sh (see docs.aliasvault.net) @@ -302,11 +391,10 @@ jobs: with: images: ghcr.io/aliasvault/installcli tags: | - type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }} - type=semver,pattern={{version}},enable=${{ github.event_name == 'release' || github.ref_type == 'tag' }} - type=ref,event=tag,enable=${{ github.ref_type == 'tag' }} - type=ref,event=branch,enable=${{ github.ref_type == 'branch' }} - type=raw,value={{branch}}-{{sha}},enable=${{ github.ref_type == 'branch' }} + type=raw,value=${{ needs.detect-version.outputs.version }},enable=${{ needs.detect-version.outputs.version != '' }} + type=raw,value=latest,enable=${{ needs.detect-version.outputs.tag_latest == 'true' }} + type=ref,event=branch,enable=${{ needs.detect-version.outputs.version == '' }} + type=raw,value={{branch}}-{{sha}},enable=${{ needs.detect-version.outputs.version == '' }} labels: | org.opencontainers.image.title=AliasVault Install CLI org.opencontainers.image.description=Installation and configuration CLI for AliasVault. Used by install.sh for setup and configuration, not deployed as part of the application stack @@ -403,8 +491,8 @@ jobs: annotations: ${{ steps.installcli-meta.outputs.annotations }} build-and-push-docker-all-in-one: - needs: [valid-release] - if: always() && (github.event_name != 'release' || needs.valid-release.result == 'success') && (github.event_name == 'release' || inputs.build_all_in_one) + needs: [valid-release, detect-version] + if: always() && (github.event_name != 'release' || needs.valid-release.result == 'success') && (github.event_name == 'release' || inputs.build_all_in_one) && needs.detect-version.outputs.all_in_one_exists != 'true' runs-on: ubuntu-latest permissions: contents: read @@ -443,15 +531,10 @@ jobs: ghcr.io/aliasvault/aliasvault aliasvault/aliasvault tags: | - # For release events with latest tag (only for non-prerelease) - type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }} - # semver tags for releases (works for prerelease and normal release) - type=semver,pattern={{version}},enable=${{ github.event_name == 'release' || github.ref_type == 'tag' }} - # For tags, use tag name - type=ref,event=tag,enable=${{ github.ref_type == 'tag' }} - # For branches, use branch name and branch name + short SHA for uniqueness - type=ref,event=branch,enable=${{ github.ref_type == 'branch' }} - type=raw,value={{branch}}-{{sha}},enable=${{ github.ref_type == 'branch' }} + type=raw,value=${{ needs.detect-version.outputs.version }},enable=${{ needs.detect-version.outputs.version != '' }} + type=raw,value=latest,enable=${{ needs.detect-version.outputs.tag_latest == 'true' }} + type=ref,event=branch,enable=${{ needs.detect-version.outputs.version == '' }} + type=raw,value={{branch}}-{{sha}},enable=${{ needs.detect-version.outputs.version == '' }} labels: | org.opencontainers.image.title=AliasVault All-in-One org.opencontainers.image.description=Self-contained AliasVault server including web app, with all services bundled using s6-overlay. Single container solution for easy deployment (see docs.aliasvault.net). @@ -468,3 +551,60 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} annotations: ${{ steps.meta.outputs.annotations }} + + # Tag existing images as :latest without rebuilding (for release events when images already exist) + tag-docker-images-latest: + needs: [valid-release, detect-version] + if: github.event_name == 'release' && needs.valid-release.result == 'success' && needs.detect-version.outputs.tag_latest == 'true' + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Tag all-in-one images as latest + if: needs.detect-version.outputs.all_in_one_exists == 'true' + run: | + VERSION="${{ needs.detect-version.outputs.version }}" + echo "Tagging ghcr.io/aliasvault/aliasvault:${VERSION} as latest..." + docker buildx imagetools create -t ghcr.io/aliasvault/aliasvault:latest ghcr.io/aliasvault/aliasvault:${VERSION} + docker buildx imagetools create -t aliasvault/aliasvault:latest aliasvault/aliasvault:${VERSION} + + - name: Tag multi-container images as latest + if: needs.detect-version.outputs.multi_container_exists == 'true' + run: | + VERSION="${{ needs.detect-version.outputs.version }}" + IMAGES=( + "ghcr.io/aliasvault/postgres" + "ghcr.io/aliasvault/api" + "ghcr.io/aliasvault/client" + "ghcr.io/aliasvault/admin" + "ghcr.io/aliasvault/reverse-proxy" + "ghcr.io/aliasvault/smtp" + "ghcr.io/aliasvault/task-runner" + "ghcr.io/aliasvault/installcli" + ) + for IMAGE in "${IMAGES[@]}"; do + if docker manifest inspect ${IMAGE}:${VERSION} > /dev/null 2>&1; then + echo "Tagging ${IMAGE}:${VERSION} as latest..." + docker buildx imagetools create -t ${IMAGE}:latest ${IMAGE}:${VERSION} + else + echo "⚠️ Skipping ${IMAGE}:${VERSION} (does not exist)" + fi + done