diff --git a/.github/actions/image-build/action.yml b/.github/actions/image-build/action.yml
new file mode 100644
index 0000000000..ee23bd8ba8
--- /dev/null
+++ b/.github/actions/image-build/action.yml
@@ -0,0 +1,118 @@
+name: 'Single arch image build'
+description: 'Build single-arch image on platform appropriate runner'
+inputs:
+ image:
+ description: 'Name of the image to build'
+ required: true
+ ghcr-token:
+ description: 'GitHub Container Registry token'
+ required: true
+ platform:
+ description: 'Platform to build for'
+ required: true
+ artifact-key-base:
+ description: 'Base key for artifact name'
+ required: true
+ context:
+ description: 'Path to build context'
+ required: true
+ dockerfile:
+ description: 'Path to Dockerfile'
+ required: true
+ build-args:
+ description: 'Docker build arguments'
+ required: false
+runs:
+ using: 'composite'
+ steps:
+ - name: Prepare
+ id: prepare
+ shell: bash
+ env:
+ PLATFORM: ${{ inputs.platform }}
+ run: |
+ echo "platform-pair=${PLATFORM//\//-}" >> $GITHUB_OUTPUT
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
+
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
+ if: ${{ !github.event.pull_request.head.repo.fork }}
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ inputs.ghcr-token }}
+
+ - name: Generate cache key suffix
+ id: cache-key-suffix
+ shell: bash
+ env:
+ REF: ${{ github.ref_name }}
+ run: |
+ if [[ "${{ github.event_name }}" == "pull_request" ]]; then
+ echo "cache-key-suffix=pr-${{ github.event.number }}" >> $GITHUB_OUTPUT
+ else
+ SUFFIX=$(echo "${REF}" | sed 's/[^a-zA-Z0-9]/-/g')
+ echo "suffix=${SUFFIX}" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Generate cache target
+ id: cache-target
+ shell: bash
+ env:
+ BUILD_ARGS: ${{ inputs.build-args }}
+ IMAGE: ${{ inputs.image }}
+ SUFFIX: ${{ steps.cache-key-suffix.outputs.suffix }}
+ PLATFORM_PAIR: ${{ steps.prepare.outputs.platform-pair }}
+ run: |
+ HASH=$(sha256sum <<< "${BUILD_ARGS}" | cut -d' ' -f1)
+ CACHE_KEY="${PLATFORM_PAIR}-${HASH}"
+ echo "cache-key-base=${CACHE_KEY}" >> $GITHUB_OUTPUT
+ if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then
+ # Essentially just ignore the cache output (forks can't write to registry cache)
+ echo "cache-to=type=local,dest=/tmp/discard,ignore-error=true" >> $GITHUB_OUTPUT
+ else
+ echo "cache-to=type=registry,ref=${IMAGE}-build-cache:${CACHE_KEY}-${SUFFIX},mode=max,compression=zstd" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Generate docker image tags
+ id: meta
+ uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
+ env:
+ DOCKER_METADATA_PR_HEAD_SHA: 'true'
+
+ - name: Build and push image
+ id: build
+ uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
+ with:
+ context: ${{ inputs.context }}
+ file: ${{ inputs.dockerfile }}
+ platforms: ${{ inputs.platform }}
+ labels: ${{ steps.meta.outputs.labels }}
+ cache-to: ${{ steps.cache-target.outputs.cache-to }}
+ cache-from: |
+ type=registry,ref=${{ inputs.image }}-build-cache:${{ steps.cache-target.outputs.cache-key-base }}-${{ steps.cache-key-suffix.outputs.suffix }}
+ type=registry,ref=${{ inputs.image }}-build-cache:${{ steps.cache-target.outputs.cache-key-base }}-main
+ outputs: type=image,"name=${{ inputs.image }}",push-by-digest=true,name-canonical=true,push=${{ !github.event.pull_request.head.repo.fork }}
+ build-args: |
+ BUILD_ID=${{ github.run_id }}
+ BUILD_IMAGE=${{ github.event_name == 'release' && github.ref_name || steps.meta.outputs.tags }}
+ BUILD_SOURCE_REF=${{ github.ref_name }}
+ BUILD_SOURCE_COMMIT=${{ github.sha }}
+ ${{ inputs.build-args }}
+
+ - name: Export digest
+ shell: bash
+ run: | # zizmor: ignore[template-injection]
+ mkdir -p ${{ runner.temp }}/digests
+ digest="${{ steps.build.outputs.digest }}"
+ touch "${{ runner.temp }}/digests/${digest#sha256:}"
+
+ - name: Upload digest
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ with:
+ name: ${{ inputs.artifact-key-base }}-${{ steps.cache-target.outputs.cache-key-base }}
+ path: ${{ runner.temp }}/digests/*
+ if-no-files-found: error
+ retention-days: 1
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 1746c93bc7..056aa7e6f4 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -40,6 +40,8 @@ jobs:
- 'machine-learning/**'
workflow:
- '.github/workflows/docker.yml'
+ - '.github/workflows/multi-runner-build.yml'
+ - '.github/actions/image-build'
- name: Check if we should force jobs to run
id: should_force
@@ -103,429 +105,74 @@ jobs:
docker buildx imagetools create -t "${REGISTRY_NAME}/${REPOSITORY}:${TAG_PR}" "${REGISTRY_NAME}/${REPOSITORY}:${TAG_OLD}"
docker buildx imagetools create -t "${REGISTRY_NAME}/${REPOSITORY}:${TAG_COMMIT}" "${REGISTRY_NAME}/${REPOSITORY}:${TAG_OLD}"
- build_and_push_ml:
+ machine-learning:
name: Build and Push ML
needs: pre-job
- permissions:
- contents: read
- packages: write
if: ${{ needs.pre-job.outputs.should_run_ml == 'true' }}
- runs-on: ${{ matrix.runner }}
- env:
- image: immich-machine-learning
- context: machine-learning
- file: machine-learning/Dockerfile
- GHCR_REPO: ghcr.io/${{ github.repository_owner }}/immich-machine-learning
strategy:
- # Prevent a failure in one image from stopping the other builds
fail-fast: false
- matrix:
- include:
- - platform: linux/amd64
- runner: ubuntu-latest
- device: cpu
-
- - platform: linux/arm64
- runner: ubuntu-24.04-arm
- device: cpu
-
- - platform: linux/amd64
- runner: ubuntu-latest
- device: cuda
- suffix: -cuda
-
- - platform: linux/amd64
- runner: mich
- device: rocm
- suffix: -rocm
-
- - platform: linux/amd64
- runner: ubuntu-latest
- device: openvino
- suffix: -openvino
-
- - platform: linux/arm64
- runner: ubuntu-24.04-arm
- device: armnn
- suffix: -armnn
-
- - platform: linux/arm64
- runner: ubuntu-24.04-arm
- device: rknn
- suffix: -rknn
-
- steps:
- - name: Prepare
- run: |
- platform=${{ matrix.platform }}
- echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
-
- - name: Checkout
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- with:
- persist-credentials: false
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
-
- - name: Login to GitHub Container Registry
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
- if: ${{ !github.event.pull_request.head.repo.fork }}
- with:
- registry: ghcr.io
- username: ${{ github.repository_owner }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Generate cache key suffix
- env:
- REF: ${{ github.ref_name }}
- run: |
- if [[ "${{ github.event_name }}" == "pull_request" ]]; then
- echo "CACHE_KEY_SUFFIX=pr-${{ github.event.number }}" >> $GITHUB_ENV
- else
- SUFFIX=$(echo "${REF}" | sed 's/[^a-zA-Z0-9]/-/g')
- echo "CACHE_KEY_SUFFIX=${SUFFIX}" >> $GITHUB_ENV
- fi
-
- - name: Generate cache target
- id: cache-target
- run: |
- if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then
- # Essentially just ignore the cache output (forks can't write to registry cache)
- echo "cache-to=type=local,dest=/tmp/discard,ignore-error=true" >> $GITHUB_OUTPUT
- else
- echo "cache-to=type=registry,ref=${GHCR_REPO}-build-cache:${PLATFORM_PAIR}-${{ matrix.device }}-${CACHE_KEY_SUFFIX},mode=max,compression=zstd" >> $GITHUB_OUTPUT
- fi
-
- - name: Generate docker image tags
- id: meta
- uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
- env:
- DOCKER_METADATA_PR_HEAD_SHA: 'true'
-
- - name: Build and push image
- id: build
- uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
- with:
- context: ${{ env.context }}
- file: ${{ env.file }}
- platforms: ${{ matrix.platforms }}
- labels: ${{ steps.meta.outputs.labels }}
- cache-to: ${{ steps.cache-target.outputs.cache-to }}
- cache-from: |
- type=registry,ref=${{ env.GHCR_REPO }}-build-cache:${{ env.PLATFORM_PAIR }}-${{ matrix.device }}-${{ env.CACHE_KEY_SUFFIX }}
- type=registry,ref=${{ env.GHCR_REPO }}-build-cache:${{ env.PLATFORM_PAIR }}-${{ matrix.device }}-main
- outputs: type=image,"name=${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=${{ !github.event.pull_request.head.repo.fork }}
- build-args: |
- DEVICE=${{ matrix.device }}
- BUILD_ID=${{ github.run_id }}
- BUILD_IMAGE=${{ github.event_name == 'release' && github.ref_name || steps.metadata.outputs.tags }}
- BUILD_SOURCE_REF=${{ github.ref_name }}
- BUILD_SOURCE_COMMIT=${{ github.sha }}
-
- - name: Export digest
- run: | # zizmor: ignore[template-injection]
- mkdir -p ${{ runner.temp }}/digests
- digest="${{ steps.build.outputs.digest }}"
- touch "${{ runner.temp }}/digests/${digest#sha256:}"
-
- - name: Upload digest
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
- with:
- name: ml-digests-${{ matrix.device }}-${{ env.PLATFORM_PAIR }}
- path: ${{ runner.temp }}/digests/*
- if-no-files-found: error
- retention-days: 1
-
- merge_ml:
- name: Merge & Push ML
- runs-on: ubuntu-latest
- permissions:
- contents: read
- actions: read
- packages: write
- if: ${{ needs.pre-job.outputs.should_run_ml == 'true' && !github.event.pull_request.head.repo.fork }}
- env:
- GHCR_REPO: ghcr.io/${{ github.repository_owner }}/immich-machine-learning
- DOCKER_REPO: altran1502/immich-machine-learning
- strategy:
matrix:
include:
- device: cpu
+ tag-suffix: ''
- device: cuda
- suffix: -cuda
- - device: rocm
- suffix: -rocm
+ tag-suffix: '-cuda'
+ platforms: linux/amd64
- device: openvino
- suffix: -openvino
+ tag-suffix: '-openvino'
+ platforms: linux/amd64
- device: armnn
- suffix: -armnn
+ tag-suffix: '-armnn'
+ platforms: linux/arm64
- device: rknn
- suffix: -rknn
- needs:
- - build_and_push_ml
- steps:
- - name: Download digests
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
- with:
- path: ${{ runner.temp }}/digests
- pattern: ml-digests-${{ matrix.device }}-*
- merge-multiple: true
-
- - name: Login to Docker Hub
- if: ${{ github.event_name == 'release' }}
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
- with:
- username: ${{ secrets.DOCKERHUB_USERNAME }}
- password: ${{ secrets.DOCKERHUB_TOKEN }}
-
- - name: Login to GHCR
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
- with:
- registry: ghcr.io
- username: ${{ github.repository_owner }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
-
- - name: Generate docker image tags
- id: meta
- uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
- env:
- DOCKER_METADATA_PR_HEAD_SHA: 'true'
- with:
- flavor: |
- # Disable latest tag
- latest=false
- suffix=${{ matrix.suffix }}
- images: |
- name=${{ env.GHCR_REPO }}
- name=${{ env.DOCKER_REPO }},enable=${{ github.event_name == 'release' }}
- tags: |
- # Tag with branch name
- type=ref,event=branch
- # Tag with pr-number
- type=ref,event=pr
- # Tag with long commit sha hash
- type=sha,format=long,prefix=commit-
- # Tag with git tag on release
- type=ref,event=tag
- type=raw,value=release,enable=${{ github.event_name == 'release' }}
-
- - name: Create manifest list and push
- working-directory: ${{ runner.temp }}/digests
- run: |
- # Process annotations
- declare -a ANNOTATIONS=()
- if [[ -n "$DOCKER_METADATA_OUTPUT_JSON" ]]; then
- while IFS= read -r annotation; do
- # Extract key and value by removing the manifest: prefix
- if [[ "$annotation" =~ ^manifest:(.+)=(.+)$ ]]; then
- key="${BASH_REMATCH[1]}"
- value="${BASH_REMATCH[2]}"
- # Use array to properly handle arguments with spaces
- ANNOTATIONS+=(--annotation "index:$key=$value")
- fi
- done < <(jq -r '.annotations[]' <<< "$DOCKER_METADATA_OUTPUT_JSON")
- fi
-
- TAGS=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
- SOURCE_ARGS=$(printf "${GHCR_REPO}@sha256:%s " *)
-
- docker buildx imagetools create $TAGS "${ANNOTATIONS[@]}" $SOURCE_ARGS
-
- build_and_push_server:
- name: Build and Push Server
- runs-on: ${{ matrix.runner }}
- permissions:
- contents: read
- packages: write
- needs: pre-job
- if: ${{ needs.pre-job.outputs.should_run_server == 'true' }}
- env:
- image: immich-server
- context: .
- file: server/Dockerfile
- GHCR_REPO: ghcr.io/${{ github.repository_owner }}/immich-server
- strategy:
- fail-fast: false
- matrix:
- include:
- - platform: linux/amd64
- runner: ubuntu-latest
- - platform: linux/arm64
- runner: ubuntu-24.04-arm
- steps:
- - name: Prepare
- run: |
- platform=${{ matrix.platform }}
- echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
-
- - name: Checkout
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- with:
- persist-credentials: false
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
-
- - name: Login to GitHub Container Registry
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
- if: ${{ !github.event.pull_request.head.repo.fork }}
- with:
- registry: ghcr.io
- username: ${{ github.repository_owner }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Generate cache key suffix
- env:
- REF: ${{ github.ref_name }}
- run: |
- if [[ "${{ github.event_name }}" == "pull_request" ]]; then
- echo "CACHE_KEY_SUFFIX=pr-${{ github.event.number }}" >> $GITHUB_ENV
- else
- SUFFIX=$(echo "${REF}" | sed 's/[^a-zA-Z0-9]/-/g')
- echo "CACHE_KEY_SUFFIX=${SUFFIX}" >> $GITHUB_ENV
- fi
-
- - name: Generate cache target
- id: cache-target
- run: |
- if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then
- # Essentially just ignore the cache output (forks can't write to registry cache)
- echo "cache-to=type=local,dest=/tmp/discard,ignore-error=true" >> $GITHUB_OUTPUT
- else
- echo "cache-to=type=registry,ref=${GHCR_REPO}-build-cache:${PLATFORM_PAIR}-${CACHE_KEY_SUFFIX},mode=max,compression=zstd" >> $GITHUB_OUTPUT
- fi
-
- - name: Generate docker image tags
- id: meta
- uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
- env:
- DOCKER_METADATA_PR_HEAD_SHA: 'true'
-
- - name: Build and push image
- id: build
- uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
- with:
- context: ${{ env.context }}
- file: ${{ env.file }}
- platforms: ${{ matrix.platform }}
- labels: ${{ steps.meta.outputs.labels }}
- cache-to: ${{ steps.cache-target.outputs.cache-to }}
- cache-from: |
- type=registry,ref=${{ env.GHCR_REPO }}-build-cache:${{ env.PLATFORM_PAIR }}-${{ env.CACHE_KEY_SUFFIX }}
- type=registry,ref=${{ env.GHCR_REPO }}-build-cache:${{ env.PLATFORM_PAIR }}-main
- outputs: type=image,"name=${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=${{ !github.event.pull_request.head.repo.fork }}
- build-args: |
- DEVICE=cpu
- BUILD_ID=${{ github.run_id }}
- BUILD_IMAGE=${{ github.event_name == 'release' && github.ref_name || steps.metadata.outputs.tags }}
- BUILD_SOURCE_REF=${{ github.ref_name }}
- BUILD_SOURCE_COMMIT=${{ github.sha }}
-
- - name: Export digest
- run: | # zizmor: ignore[template-injection]
- mkdir -p ${{ runner.temp }}/digests
- digest="${{ steps.build.outputs.digest }}"
- touch "${{ runner.temp }}/digests/${digest#sha256:}"
-
- - name: Upload digest
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
- with:
- name: server-digests-${{ env.PLATFORM_PAIR }}
- path: ${{ runner.temp }}/digests/*
- if-no-files-found: error
- retention-days: 1
-
- merge_server:
- name: Merge & Push Server
- runs-on: ubuntu-latest
+ tag-suffix: '-rknn'
+ platforms: linux/arm64
+ - device: rocm
+ tag-suffix: '-rocm'
+ platforms: linux/amd64
+ runner-mapping: '{"linux/amd64": "mich"}'
+ uses: ./.github/workflows/multi-runner-build.yml
permissions:
contents: read
actions: read
packages: write
- if: ${{ needs.pre-job.outputs.should_run_server == 'true' && !github.event.pull_request.head.repo.fork }}
- env:
- GHCR_REPO: ghcr.io/${{ github.repository_owner }}/immich-server
- DOCKER_REPO: altran1502/immich-server
- needs:
- - build_and_push_server
- steps:
- - name: Download digests
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
- with:
- path: ${{ runner.temp }}/digests
- pattern: server-digests-*
- merge-multiple: true
+ secrets:
+ DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
+ DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
+ with:
+ image: immich-machine-learning
+ context: machine-learning
+ dockerfile: machine-learning/Dockerfile
+ platforms: ${{ matrix.platforms }}
+ runner-mapping: ${{ matrix.runner-mapping }}
+ tag-suffix: ${{ matrix.tag-suffix }}
+ dockerhub-push: ${{ github.event_name == 'release' }}
+ build-args: |
+ DEVICE=${{ matrix.device }}
- - name: Login to Docker Hub
- if: ${{ github.event_name == 'release' }}
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
- with:
- username: ${{ secrets.DOCKERHUB_USERNAME }}
- password: ${{ secrets.DOCKERHUB_TOKEN }}
-
- - name: Login to GHCR
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
- with:
- registry: ghcr.io
- username: ${{ github.repository_owner }}
- password: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
-
- - name: Generate docker image tags
- id: meta
- uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
- env:
- DOCKER_METADATA_PR_HEAD_SHA: 'true'
- with:
- flavor: |
- # Disable latest tag
- latest=false
- suffix=${{ matrix.suffix }}
- images: |
- name=${{ env.GHCR_REPO }}
- name=${{ env.DOCKER_REPO }},enable=${{ github.event_name == 'release' }}
- tags: |
- # Tag with branch name
- type=ref,event=branch
- # Tag with pr-number
- type=ref,event=pr
- # Tag with long commit sha hash
- type=sha,format=long,prefix=commit-
- # Tag with git tag on release
- type=ref,event=tag
- type=raw,value=release,enable=${{ github.event_name == 'release' }}
-
- - name: Create manifest list and push
- working-directory: ${{ runner.temp }}/digests
- run: |
- # Process annotations
- declare -a ANNOTATIONS=()
- if [[ -n "$DOCKER_METADATA_OUTPUT_JSON" ]]; then
- while IFS= read -r annotation; do
- # Extract key and value by removing the manifest: prefix
- if [[ "$annotation" =~ ^manifest:(.+)=(.+)$ ]]; then
- key="${BASH_REMATCH[1]}"
- value="${BASH_REMATCH[2]}"
- # Use array to properly handle arguments with spaces
- ANNOTATIONS+=(--annotation "index:$key=$value")
- fi
- done < <(jq -r '.annotations[]' <<< "$DOCKER_METADATA_OUTPUT_JSON")
- fi
-
- TAGS=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
- SOURCE_ARGS=$(printf "${GHCR_REPO}@sha256:%s " *)
-
- docker buildx imagetools create $TAGS "${ANNOTATIONS[@]}" $SOURCE_ARGS
+ server:
+ name: Build and Push Server
+ needs: pre-job
+ if: ${{ needs.pre-job.outputs.should_run_server == 'true' }}
+ uses: ./.github/workflows/multi-runner-build.yml
+ permissions:
+ contents: read
+ actions: read
+ packages: write
+ secrets:
+ DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
+ DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
+ with:
+ image: immich-server
+ context: .
+ dockerfile: server/Dockerfile
+ dockerhub-push: ${{ github.event_name == 'release' }}
+ build-args: |
+ DEVICE=cpu
success-check-server:
name: Docker Build & Push Server Success
- needs: [merge_server, retag_server]
+ needs: [server, retag_server]
permissions: {}
runs-on: ubuntu-latest
if: always()
@@ -540,7 +187,7 @@ jobs:
success-check-ml:
name: Docker Build & Push ML Success
- needs: [merge_ml, retag_ml]
+ needs: [machine-learning, retag_ml]
permissions: {}
runs-on: ubuntu-latest
if: always()
diff --git a/.github/workflows/multi-runner-build.yml b/.github/workflows/multi-runner-build.yml
new file mode 100644
index 0000000000..17eceb7e8f
--- /dev/null
+++ b/.github/workflows/multi-runner-build.yml
@@ -0,0 +1,185 @@
+name: 'Multi-runner container image build'
+on:
+ workflow_call:
+ inputs:
+ image:
+ description: 'Name of the image'
+ type: string
+ required: true
+ context:
+ description: 'Path to build context'
+ type: string
+ required: true
+ dockerfile:
+ description: 'Path to Dockerfile'
+ type: string
+ required: true
+ tag-suffix:
+ description: 'Suffix to append to the image tag'
+ type: string
+ default: ''
+ dockerhub-push:
+ description: 'Push to Docker Hub'
+ type: boolean
+ default: false
+ build-args:
+ description: 'Docker build arguments'
+ type: string
+ required: false
+ platforms:
+ description: 'Platforms to build for'
+ type: string
+ runner-mapping:
+ description: 'Mapping from platforms to runners'
+ type: string
+ secrets:
+ DOCKERHUB_USERNAME:
+ required: false
+ DOCKERHUB_TOKEN:
+ required: false
+
+env:
+ GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ inputs.image }}
+ DOCKERHUB_IMAGE: altran1502/${{ inputs.image }}
+
+jobs:
+ matrix:
+ name: 'Generate matrix'
+ runs-on: ubuntu-latest
+ outputs:
+ matrix: ${{ steps.matrix.outputs.matrix }}
+ key: ${{ steps.artifact-key.outputs.base }}
+ steps:
+ - name: Generate build matrix
+ id: matrix
+ shell: bash
+ env:
+ PLATFORMS: ${{ inputs.platforms || 'linux/amd64,linux/arm64' }}
+ RUNNER_MAPPING: ${{ inputs.runner-mapping || '{"linux/amd64":"ubuntu-latest","linux/arm64":"ubuntu-24.04-arm"}' }}
+ run: |
+ matrix=$(jq -R -c \
+ --argjson runner_mapping "${RUNNER_MAPPING}" \
+ 'split(",") | map({platform: ., runner: $runner_mapping[.]})' \
+ <<< "${PLATFORMS}")
+ echo "${matrix}"
+ echo "matrix=${matrix}" >> $GITHUB_OUTPUT
+
+ - name: Determine artifact key
+ id: artifact-key
+ shell: bash
+ env:
+ IMAGE: ${{ inputs.image }}
+ SUFFIX: ${{ inputs.tag-suffix }}
+ run: |
+ if [[ -n "${SUFFIX}" ]]; then
+ base="${IMAGE}${SUFFIX}-digests"
+ else
+ base="${IMAGE}-digests"
+ fi
+ echo "${base}"
+ echo "base=${base}" >> $GITHUB_OUTPUT
+
+ build:
+ needs: matrix
+ runs-on: ${{ matrix.runner }}
+ permissions:
+ contents: read
+ packages: write
+ strategy:
+ fail-fast: false
+ matrix:
+ include: ${{ fromJson(needs.matrix.outputs.matrix) }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
+ with:
+ persist-credentials: false
+
+ - uses: ./.github/actions/image-build
+ with:
+ context: ${{ inputs.context }}
+ dockerfile: ${{ inputs.dockerfile }}
+ image: ${{ env.GHCR_IMAGE }}
+ ghcr-token: ${{ secrets.GITHUB_TOKEN }}
+ platform: ${{ matrix.platform }}
+ artifact-key-base: ${{ needs.matrix.outputs.key }}
+ build-args: ${{ inputs.build-args }}
+
+ merge:
+ needs: [matrix, build]
+ runs-on: ubuntu-latest
+ if: ${{ !github.event.pull_request.head.repo.fork }}
+ permissions:
+ contents: read
+ actions: read
+ packages: write
+ steps:
+ - name: Download digests
+ uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4
+ with:
+ path: ${{ runner.temp }}/digests
+ pattern: ${{ needs.matrix.outputs.key }}-*
+ merge-multiple: true
+
+ - name: Login to Docker Hub
+ if: ${{ inputs.dockerhub-push }}
+ uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Login to GHCR
+ uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3
+
+ - name: Generate docker image tags
+ id: meta
+ uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5
+ env:
+ DOCKER_METADATA_PR_HEAD_SHA: 'true'
+ with:
+ flavor: |
+ # Disable latest tag
+ latest=false
+ suffix=${{ inputs.tag-suffix }}
+ images: |
+ name=${{ env.GHCR_IMAGE }}
+ name=${{ env.DOCKERHUB_IMAGE }},enable=${{ inputs.dockerhub-push }}
+ tags: |
+ # Tag with branch name
+ type=ref,event=branch
+ # Tag with pr-number
+ type=ref,event=pr
+ # Tag with long commit sha hash
+ type=sha,format=long,prefix=commit-
+ # Tag with git tag on release
+ type=ref,event=tag
+ type=raw,value=release,enable=${{ github.event_name == 'release' }}
+
+ - name: Create manifest list and push
+ working-directory: ${{ runner.temp }}/digests
+ run: |
+ # Process annotations
+ declare -a ANNOTATIONS=()
+ if [[ -n "$DOCKER_METADATA_OUTPUT_JSON" ]]; then
+ while IFS= read -r annotation; do
+ # Extract key and value by removing the manifest: prefix
+ if [[ "$annotation" =~ ^manifest:(.+)=(.+)$ ]]; then
+ key="${BASH_REMATCH[1]}"
+ value="${BASH_REMATCH[2]}"
+ # Use array to properly handle arguments with spaces
+ ANNOTATIONS+=(--annotation "index:$key=$value")
+ fi
+ done < <(jq -r '.annotations[]' <<< "$DOCKER_METADATA_OUTPUT_JSON")
+ fi
+
+ TAGS=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
+ SOURCE_ARGS=$(printf "${GHCR_IMAGE}@sha256:%s " *)
+
+ docker buildx imagetools create $TAGS "${ANNOTATIONS[@]}" $SOURCE_ARGS
diff --git a/cli/package-lock.json b/cli/package-lock.json
index d1fc6ec59b..22fc4473f0 100644
--- a/cli/package-lock.json
+++ b/cli/package-lock.json
@@ -697,9 +697,9 @@
}
},
"node_modules/@eslint/js": {
- "version": "9.25.1",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.25.1.tgz",
- "integrity": "sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==",
+ "version": "9.26.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.26.0.tgz",
+ "integrity": "sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -935,6 +935,28 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@modelcontextprotocol/sdk": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.1.tgz",
+ "integrity": "sha512-9LfmxKTb1v+vUS1/emSk1f5ePmTLkb9Le9AxOB5T0XM59EUumwcS45z05h7aiZx3GI0Bl7mjb3FMEglYj+acuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "cors": "^2.8.5",
+ "cross-spawn": "^7.0.3",
+ "eventsource": "^3.0.2",
+ "express": "^5.0.1",
+ "express-rate-limit": "^7.5.0",
+ "pkce-challenge": "^5.0.0",
+ "raw-body": "^3.0.0",
+ "zod": "^3.23.8",
+ "zod-to-json-schema": "^3.24.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1718,6 +1740,20 @@
"url": "https://opencollective.com/vitest"
}
},
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/acorn": {
"version": "8.14.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
@@ -1808,6 +1844,27 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/body-parser": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
+ "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.0",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.6.3",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.0",
+ "raw-body": "^3.0.0",
+ "type-is": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -1895,6 +1952,16 @@
}
}
},
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/cac": {
"version": "6.7.14",
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
@@ -1905,6 +1972,37 @@
"node": ">=8"
}
},
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -2084,6 +2182,49 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/content-disposition": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
+ "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
"node_modules/core-js-compat": {
"version": "3.41.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.41.0.tgz",
@@ -2098,6 +2239,20 @@
"url": "https://opencollective.com/core-js"
}
},
+ "node_modules/cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -2148,6 +2303,31 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -2155,6 +2335,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/electron-to-chromium": {
"version": "1.5.137",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.137.tgz",
@@ -2169,6 +2356,36 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/es-module-lexer": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
@@ -2176,6 +2393,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/esbuild": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz",
@@ -2227,6 +2457,13 @@
"node": ">=6"
}
},
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -2241,9 +2478,9 @@
}
},
"node_modules/eslint": {
- "version": "9.25.1",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.25.1.tgz",
- "integrity": "sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==",
+ "version": "9.26.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.26.0.tgz",
+ "integrity": "sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2253,11 +2490,12 @@
"@eslint/config-helpers": "^0.2.1",
"@eslint/core": "^0.13.0",
"@eslint/eslintrc": "^3.3.1",
- "@eslint/js": "9.25.1",
+ "@eslint/js": "9.26.0",
"@eslint/plugin-kit": "^0.2.8",
"@humanfs/node": "^0.16.6",
"@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.4.2",
+ "@modelcontextprotocol/sdk": "^1.8.0",
"@types/estree": "^1.0.6",
"@types/json-schema": "^7.0.15",
"ajv": "^6.12.4",
@@ -2281,7 +2519,8 @@
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
- "optionator": "^0.9.3"
+ "optionator": "^0.9.3",
+ "zod": "^3.24.2"
},
"bin": {
"eslint": "bin/eslint.js"
@@ -2315,9 +2554,9 @@
}
},
"node_modules/eslint-plugin-prettier": {
- "version": "5.2.6",
- "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.6.tgz",
- "integrity": "sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==",
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.3.1.tgz",
+ "integrity": "sha512-vad9VWgEm9xaVXRNmb4aeOt0PWDc61IAdzghkbYQ2wavgax148iKoX1rNJcgkBGCipzLzOnHYVgL7xudM9yccQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2496,6 +2735,39 @@
"node": ">=0.10.0"
}
},
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/eventsource": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
+ "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eventsource-parser": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/eventsource-parser": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz",
+ "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"node_modules/expect-type": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz",
@@ -2506,6 +2778,65 @@
"node": ">=12.0.0"
}
},
+ "node_modules/express": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
+ "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.0",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express-rate-limit": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz",
+ "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
+ },
+ "peerDependencies": {
+ "express": "^4.11 || 5 || ^5.0.0-beta.1"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -2596,6 +2927,24 @@
"node": ">=8"
}
},
+ "node_modules/finalhandler": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
+ "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -2664,6 +3013,26 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -2679,6 +3048,55 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
@@ -2759,6 +3177,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
@@ -2776,6 +3207,32 @@
"node": ">=8"
}
},
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/hosted-git-info": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz",
@@ -2796,6 +3253,36 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -2859,6 +3346,23 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/is-builtin-module": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-4.0.0.tgz",
@@ -2915,6 +3419,13 @@
"node": ">=0.12.0"
}
},
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -3151,6 +3662,39 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -3173,6 +3717,29 @@
"node": ">=8.6"
}
},
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
+ "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/min-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
@@ -3249,6 +3816,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/node-releases": {
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
@@ -3271,6 +3848,52 @@
"node": "^16.14.0 || >=18.0.0"
}
},
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -3359,6 +3982,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -3396,6 +4029,16 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/path-to-regexp": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
+ "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ }
+ },
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
@@ -3432,6 +4075,16 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
+ "node_modules/pkce-challenge": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz",
+ "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.20.0"
+ }
+ },
"node_modules/pluralize": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
@@ -3527,6 +4180,20 @@
}
}
},
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -3537,6 +4204,22 @@
"node": ">=6"
}
},
+ "node_modules/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -3557,6 +4240,32 @@
],
"license": "MIT"
},
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
+ "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.6.3",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/read-package-up": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz",
@@ -3704,6 +4413,23 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -3727,6 +4453,34 @@
"queue-microtask": "^1.2.2"
}
},
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/semver": {
"version": "7.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
@@ -3740,6 +4494,52 @@
"node": ">=10"
}
},
+ "node_modules/send": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
+ "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.5",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "mime-types": "^3.0.1",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
+ "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -3763,6 +4563,82 @@
"node": ">=8"
}
},
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/siginfo": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
@@ -3836,6 +4712,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/std-env": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz",
@@ -4102,6 +4988,16 @@
"node": ">=8.0"
}
},
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
"node_modules/ts-api-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
@@ -4169,6 +5065,21 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/type-is": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
+ "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^1.0.5",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
@@ -4226,6 +5137,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/update-browserslist-db": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
@@ -4278,10 +5199,20 @@
"spdx-expression-parse": "^3.0.0"
}
},
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/vite": {
- "version": "6.3.4",
- "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.4.tgz",
- "integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==",
+ "version": "6.3.5",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
+ "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4655,6 +5586,13 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/yaml": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
@@ -4680,6 +5618,26 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zod": {
+ "version": "3.24.4",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz",
+ "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-to-json-schema": {
+ "version": "3.24.5",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz",
+ "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==",
+ "dev": true,
+ "license": "ISC",
+ "peerDependencies": {
+ "zod": "^3.24.1"
+ }
}
}
}
diff --git a/docs/docs/features/libraries.md b/docs/docs/features/libraries.md
index 9eb05383b3..9f094509f4 100644
--- a/docs/docs/features/libraries.md
+++ b/docs/docs/features/libraries.md
@@ -72,7 +72,7 @@ In rare cases, the library watcher can hang, preventing Immich from starting up.
### Nightly job
-There is an automatic scan job that is scheduled to run once a day. This job also cleans up any libraries stuck in deletion. It is possible to trigger the cleanup by clicking "Scan all libraries" in the library managment page.
+There is an automatic scan job that is scheduled to run once a day. This job also cleans up any libraries stuck in deletion. It is possible to trigger the cleanup by clicking "Scan all libraries" in the library management page.
## Usage
diff --git a/docs/docs/features/searching.md b/docs/docs/features/searching.md
index 0ee1d01000..f6bfac6e7a 100644
--- a/docs/docs/features/searching.md
+++ b/docs/docs/features/searching.md
@@ -92,7 +92,7 @@ Memory and execution time estimates were obtained without acceleration on a 7800
**Execution Time (ms)**: After warming up the model with one pass, the mean execution time of 100 passes with the same input.
-**Memory (MiB)**: The peak RSS usage of the process afer performing the above timing benchmark. Does not include image decoding, concurrent processing, the web server, etc., which are relatively constant factors.
+**Memory (MiB)**: The peak RSS usage of the process after performing the above timing benchmark. Does not include image decoding, concurrent processing, the web server, etc., which are relatively constant factors.
**Recall (%)**: Evaluated on Crossmodal-3600, the average of the recall@1, recall@5 and recall@10 results for zeroshot image retrieval. Chinese (Simplified), English, French, German, Italian, Japanese, Korean, Polish, Russian, Spanish and Turkish are additionally tested on XTD-10. Chinese (Simplified) and English are additionally tested on Flickr30k. The recall metrics are the average across all tested datasets.
diff --git a/docs/docs/install/docker-compose.mdx b/docs/docs/install/docker-compose.mdx
index fe5c043d01..7a0b566f5d 100644
--- a/docs/docs/install/docker-compose.mdx
+++ b/docs/docs/install/docker-compose.mdx
@@ -2,53 +2,13 @@
sidebar_position: 30
---
-import CodeBlock from '@theme/CodeBlock';
-import ExampleEnv from '!!raw-loader!../../../docker/example.env';
-
# Docker Compose [Recommended]
Docker Compose is the recommended method to run Immich in production. Below are the steps to deploy Immich with Docker Compose.
-## Step 1 - Download the required files
+import DockerComposeSteps from '/docs/partials/_docker-compose-install-steps.mdx';
-Create a directory of your choice (e.g. `./immich-app`) to hold the `docker-compose.yml` and `.env` files.
-
-```bash title="Move to the directory you created"
-mkdir ./immich-app
-cd ./immich-app
-```
-
-Download [`docker-compose.yml`][compose-file] and [`example.env`][env-file] by running the following commands:
-
-```bash title="Get docker-compose.yml file"
-wget -O docker-compose.yml https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
-```
-
-```bash title="Get .env file"
-wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env
-```
-
-You can alternatively download these two files from your browser and move them to the directory that you created, in which case ensure that you rename `example.env` to `.env`.
-
-## Step 2 - Populate the .env file with custom values
-
-
- {ExampleEnv}
-
-
-- Populate `UPLOAD_LOCATION` with your preferred location for storing backup assets. It should be a new directory on the server with enough free space.
-- Consider changing `DB_PASSWORD` to a custom value. Postgres is not publicly exposed, so this password is only used for local authentication.
- To avoid issues with Docker parsing this value, it is best to use only the characters `A-Za-z0-9`. `pwgen` is a handy utility for this.
-- Set your timezone by uncommenting the `TZ=` line.
-- Populate custom database information if necessary.
-
-## Step 3 - Start the containers
-
-From the directory you created in Step 1 (which should now contain your customized `docker-compose.yml` and `.env` files), run the following command to start Immich as a background service:
-
-```bash title="Start the containers"
-docker compose up -d
-```
+
:::info Docker version
If you get an error such as `unknown shorthand flag: 'd' in -d` or `open : permission denied`, you are probably running the wrong Docker version. (This happens, for example, with the docker.io package in Ubuntu 22.04.3 LTS.) You can correct the problem by following the complete [Docker Engine install](https://docs.docker.com/engine/install/) procedure for your distribution, crucially the "Uninstall old versions" and "Install using the apt/rpm repository" sections. These replace the distro's Docker packages with Docker's official ones.
@@ -70,6 +30,3 @@ If you get an error `can't set healthcheck.start_interval as feature require Doc
## Next Steps
Read the [Post Installation](/docs/install/post-install.mdx) steps and [upgrade instructions](/docs/install/upgrading.md).
-
-[compose-file]: https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
-[env-file]: https://github.com/immich-app/immich/releases/latest/download/example.env
diff --git a/docs/docs/install/truenas.md b/docs/docs/install/truenas.md
index 3cd772de63..bd0dc7af5f 100644
--- a/docs/docs/install/truenas.md
+++ b/docs/docs/install/truenas.md
@@ -2,7 +2,7 @@
sidebar_position: 80
---
-# TrueNAS SCALE [Community]
+# TrueNAS [Community]
:::note
This is a community contribution and not officially supported by the Immich team, but included here for convenience.
@@ -12,17 +12,17 @@ Community support can be found in the dedicated channel on the [Discord Server](
**Please report app issues to the corresponding [Github Repository](https://github.com/truenas/charts/tree/master/community/immich).**
:::
-Immich can easily be installed on TrueNAS SCALE via the **Community** train application.
-Consider reviewing the TrueNAS [Apps tutorial](https://www.truenas.com/docs/scale/scaletutorials/apps/) if you have not previously configured applications on your system.
+Immich can easily be installed on TrueNAS Community Edition via the **Community** train application.
+Consider reviewing the TrueNAS [Apps resources](https://apps.truenas.com/getting-started/) if you have not previously configured applications on your system.
-TrueNAS SCALE makes installing and updating Immich easy, but you must use the Immich web portal and mobile app to configure accounts and access libraries.
+TrueNAS Community Edition makes installing and updating Immich easy, but you must use the Immich web portal and mobile app to configure accounts and access libraries.
## First Steps
-The Immich app in TrueNAS SCALE installs, completes the initial configuration, then starts the Immich web portal.
-When updates become available, SCALE alerts and provides easy updates.
+The Immich app in TrueNAS Community Edition installs, completes the initial configuration, then starts the Immich web portal.
+When updates become available, TrueNAS alerts and provides easy updates.
-Before installing the Immich app in SCALE, review the [Environment Variables](#environment-variables) documentation to see if you want to configure any during installation.
+Before installing the Immich app in TrueNAS, review the [Environment Variables](#environment-variables) documentation to see if you want to configure any during installation.
You may also configure environment variables at any time after deploying the application.
### Setting up Storage Datasets
@@ -126,9 +126,9 @@ className="border rounded-xl"
Accept the default port `30041` in **WebUI Port** or enter a custom port number.
:::info Allowed Port Numbers
-Only numbers within the range 9000-65535 may be used on SCALE versions below TrueNAS Scale 24.10 Electric Eel.
+Only numbers within the range 9000-65535 may be used on TrueNAS versions below TrueNAS Community Edition 24.10 Electric Eel.
-Regardless of version, to avoid port conflicts, don't use [ports on this list](https://www.truenas.com/docs/references/defaultports/).
+Regardless of version, to avoid port conflicts, don't use [ports on this list](https://www.truenas.com/docs/solutions/optimizations/security/#truenas-default-ports).
:::
### Storage Configuration
@@ -173,7 +173,7 @@ className="border rounded-xl"
You may configure [External Libraries](/docs/features/libraries) by mounting them using **Additional Storage**.
The **Mount Path** is the location you will need to copy and paste into the External Library settings within Immich.
-The **Host Path** is the location on the TrueNAS SCALE server where your external library is located.
+The **Host Path** is the location on the TrueNAS Community Edition server where your external library is located.
@@ -188,17 +188,17 @@ className="border rounded-xl"
Accept the default **CPU** limit of `2` threads or specify the number of threads (CPUs with Multi-/Hyper-threading have 2 threads per core).
-Accept the default **Memory** limit of `4096` MB or specify the number of MB of RAM. If you're using Machine Learning you should probably set this above 8000 MB.
+Specify the **Memory** limit in MB of RAM. Immich recommends at least 6000 MB (6GB). If you selected **Enable Machine Learning** in **Immich Configuration**, you should probably set this above 8000 MB.
-:::info Older SCALE Versions
-Before TrueNAS SCALE version 24.10 Electric Eel:
+:::info Older TrueNAS Versions
+Before TrueNAS Community Edition version 24.10 Electric Eel:
The **CPU** value was specified in a different format with a default of `4000m` which is 4 threads.
The **Memory** value was specified in a different format with a default of `8Gi` which is 8 GiB of RAM. The value was specified in bytes or a number with a measurement suffix. Examples: `129M`, `123Mi`, `1000000000`
:::
-Enable **GPU Configuration** options if you have a GPU that you will use for [Hardware Transcoding](/docs/features/hardware-transcoding) and/or [Hardware-Accelerated Machine Learning](/docs/features/ml-hardware-acceleration.md). More info: [GPU Passthrough Docs for TrueNAS Apps](https://www.truenas.com/docs/truenasapps/#gpu-passthrough)
+Enable **GPU Configuration** options if you have a GPU that you will use for [Hardware Transcoding](/docs/features/hardware-transcoding) and/or [Hardware-Accelerated Machine Learning](/docs/features/ml-hardware-acceleration.md). More info: [GPU Passthrough Docs for TrueNAS Apps](https://apps.truenas.com/managing-apps/installing-apps/#gpu-passthrough)
### Install
@@ -240,7 +240,7 @@ className="border rounded-xl"
/>
:::info
-Some Environment Variables are not available for the TrueNAS SCALE app. This is mainly because they can be configured through GUI options in the [Edit Immich screen](#edit-app-settings).
+Some Environment Variables are not available for the TrueNAS Community Edition app. This is mainly because they can be configured through GUI options in the [Edit Immich screen](#edit-app-settings).
Some examples are: `IMMICH_VERSION`, `UPLOAD_LOCATION`, `DB_DATA_LOCATION`, `TZ`, `IMMICH_LOG_LEVEL`, `DB_PASSWORD`, `REDIS_PASSWORD`.
:::
@@ -251,7 +251,7 @@ Some examples are: `IMMICH_VERSION`, `UPLOAD_LOCATION`, `DB_DATA_LOCATION`, `TZ`
Make sure to read the general [upgrade instructions](/docs/install/upgrading.md).
:::
-When updates become available, SCALE alerts and provides easy updates.
+When updates become available, TrueNAS alerts and provides easy updates.
To update the app to the latest version:
- Go to the **Installed Applications** screen and select Immich from the list of installed applications.
diff --git a/docs/docs/overview/comparison.md b/docs/docs/overview/comparison.md
index 5d5e68f839..b843562c6e 100644
--- a/docs/docs/overview/comparison.md
+++ b/docs/docs/overview/comparison.md
@@ -1,5 +1,5 @@
---
-sidebar_position: 2
+sidebar_position: 3
---
# Comparison
diff --git a/docs/docs/overview/img/social-preview-light.webp b/docs/docs/overview/img/social-preview-light.webp
new file mode 100644
index 0000000000..3d088f6522
Binary files /dev/null and b/docs/docs/overview/img/social-preview-light.webp differ
diff --git a/docs/docs/overview/quick-start.mdx b/docs/docs/overview/quick-start.mdx
index 872c0a42a2..28cee15007 100644
--- a/docs/docs/overview/quick-start.mdx
+++ b/docs/docs/overview/quick-start.mdx
@@ -1,5 +1,5 @@
---
-sidebar_position: 3
+sidebar_position: 2
---
# Quick start
@@ -10,11 +10,20 @@ to install and use it.
## Requirements
-Check the [requirements page](/docs/install/requirements) to get started.
+- A system with at least 4GB of RAM and 2 CPU cores.
+- [Docker](https://docs.docker.com/engine/install/)
+
+> For a more detailed list of requirements, see the [requirements page](/docs/install/requirements).
+
+---
## Set up the server
-Follow the [Docker Compose (Recommended)](/docs/install/docker-compose) instructions to install the server.
+import DockerComposeSteps from '/docs/partials/_docker-compose-install-steps.mdx';
+
+
+
+---
## Try the web app
@@ -26,6 +35,8 @@ Try uploading a picture from your browser.
+---
+
## Try the mobile app
### Download the Mobile App
@@ -56,6 +67,8 @@ You can select the **Jobs** tab to see Immich processing your photos.
+---
+
## Review the database backup and restore process
Immich has built-in database backups. You can refer to the
@@ -65,6 +78,8 @@ Immich has built-in database backups. You can refer to the
The database only contains metadata and user information. You must setup manual backups of the images and videos stored in `UPLOAD_LOCATION`.
:::
+---
+
## Where to go from here?
You may decide you'd like to install the server a different way; the Install category on the left menu provides many options.
diff --git a/docs/docs/overview/introduction.mdx b/docs/docs/overview/welcome.mdx
similarity index 90%
rename from docs/docs/overview/introduction.mdx
rename to docs/docs/overview/welcome.mdx
index e09aac6ebf..93ce705369 100644
--- a/docs/docs/overview/introduction.mdx
+++ b/docs/docs/overview/welcome.mdx
@@ -2,9 +2,13 @@
sidebar_position: 1
---
-# Introduction
+# Welcome to Immich
-
+
## Welcome!
diff --git a/docs/docs/partials/_docker-compose-install-steps.mdx b/docs/docs/partials/_docker-compose-install-steps.mdx
new file mode 100644
index 0000000000..c538a6051e
--- /dev/null
+++ b/docs/docs/partials/_docker-compose-install-steps.mdx
@@ -0,0 +1,43 @@
+import CodeBlock from '@theme/CodeBlock';
+import ExampleEnv from '!!raw-loader!../../../docker/example.env';
+
+### Step 1 - Download the required files
+
+Create a directory of your choice (e.g. `./immich-app`) to hold the `docker-compose.yml` and `.env` files.
+
+```bash title="Move to the directory you created"
+mkdir ./immich-app
+cd ./immich-app
+```
+
+Download [`docker-compose.yml`](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml) and [`example.env`](https://github.com/immich-app/immich/releases/latest/download/example.env) by running the following commands:
+
+```bash title="Get docker-compose.yml file"
+wget -O docker-compose.yml https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
+```
+
+```bash title="Get .env file"
+wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env
+```
+
+You can alternatively download these two files from your browser and move them to the directory that you created, in which case ensure that you rename `example.env` to `.env`.
+
+### Step 2 - Populate the .env file with custom values
+
+
+ {ExampleEnv}
+
+
+- Populate `UPLOAD_LOCATION` with your preferred location for storing backup assets. It should be a new directory on the server with enough free space.
+- Consider changing `DB_PASSWORD` to a custom value. Postgres is not publicly exposed, so this password is only used for local authentication.
+ To avoid issues with Docker parsing this value, it is best to use only the characters `A-Za-z0-9`. `pwgen` is a handy utility for this.
+- Set your timezone by uncommenting the `TZ=` line.
+- Populate custom database information if necessary.
+
+### Step 3 - Start the containers
+
+From the directory you created in Step 1 (which should now contain your customized `docker-compose.yml` and `.env` files), run the following command to start Immich as a background service:
+
+```bash title="Start the containers"
+docker compose up -d
+```
diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js
index 7166611a2e..d612dda253 100644
--- a/docs/docusaurus.config.js
+++ b/docs/docusaurus.config.js
@@ -95,7 +95,7 @@ const config = {
position: 'right',
},
{
- to: '/docs/overview/introduction',
+ to: '/docs/overview/welcome',
position: 'right',
label: 'Docs',
},
@@ -124,6 +124,12 @@ const config = {
label: 'Discord',
position: 'right',
},
+ {
+ type: 'html',
+ position: 'right',
+ value:
+ '',
+ },
],
},
footer: {
@@ -134,7 +140,7 @@ const config = {
items: [
{
label: 'Welcome',
- to: '/docs/overview/introduction',
+ to: '/docs/overview/welcome',
},
{
label: 'Installation',
diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css
index fd3f199ce8..7f8c6d5761 100644
--- a/docs/src/css/custom.css
+++ b/docs/src/css/custom.css
@@ -7,14 +7,22 @@
@tailwind components;
@tailwind utilities;
-@import url('https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
-
-body {
- font-family: 'Be Vietnam Pro', serif;
- font-optical-sizing: auto;
- /* font-size: 1.125rem;
+@font-face {
+ font-family: 'Overpass';
+ src: url('/fonts/overpass/Overpass.ttf') format('truetype-variations');
+ font-weight: 1 999;
+ font-style: normal;
ascent-override: 106.25%;
- size-adjust: 106.25%; */
+ size-adjust: 106.25%;
+}
+
+@font-face {
+ font-family: 'Overpass Mono';
+ src: url('/fonts/overpass/OverpassMono.ttf') format('truetype-variations');
+ font-weight: 1 999;
+ font-style: normal;
+ ascent-override: 106.25%;
+ size-adjust: 106.25%;
}
.breadcrumbs__link {
@@ -29,6 +37,7 @@ img {
/* You can override the default Infima variables here. */
:root {
+ font-family: 'Overpass', sans-serif;
--ifm-color-primary: #4250af;
--ifm-color-primary-dark: #4250af;
--ifm-color-primary-darker: #4250af;
@@ -59,14 +68,12 @@ div[class^='announcementBar_'] {
}
.menu__link {
- padding: 10px;
- padding-left: 16px;
+ padding: 10px 10px 10px 16px;
border-radius: 24px;
margin-right: 16px;
}
.menu__list-item-collapsible {
- border-radius: 10px;
margin-right: 16px;
border-radius: 24px;
}
@@ -83,3 +90,12 @@ div[class*='navbar__items'] > li:has(a[class*='version-switcher-34ab39']) {
code {
font-weight: 600;
}
+
+.buy-button {
+ padding: 8px 14px;
+ border: 1px solid transparent;
+ font-family: 'Overpass', sans-serif;
+ font-weight: 500;
+ cursor: pointer;
+ box-shadow: 0 0 5px 2px rgba(181, 206, 254, 0.4);
+}
diff --git a/docs/src/pages/index.tsx b/docs/src/pages/index.tsx
index db299271aa..277a1d0b46 100644
--- a/docs/src/pages/index.tsx
+++ b/docs/src/pages/index.tsx
@@ -4,7 +4,7 @@ import Layout from '@theme/Layout';
import { discordPath, discordViewBox } from '@site/src/components/svg-paths';
import ThemedImage from '@theme/ThemedImage';
import Icon from '@mdi/react';
-import { mdiAndroid } from '@mdi/js';
+
function HomepageHeader() {
return (
@@ -13,11 +13,14 @@ function HomepageHeader() {
-
+
+
+
+
Self-hosted{' '}
@@ -28,7 +31,7 @@ function HomepageHeader() {
solution
-
+
Easily back up, organize, and manage your photos on your own server. Immich helps you
browse, search and organize your photos and videos with ease, without
sacrificing your privacy.
@@ -36,27 +39,21 @@ function HomepageHeader() {
- Get started
+ Get Started
- Demo
-
-
-
- Buy Merch
+ Open Demo
-
+
+
This project is available under GNU AGPL v3 license.