Added a Setting to set the Model Download Folder #977
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Classify command test | |
| on: | |
| pull_request: | |
| paths: | |
| - 'lib/**' | |
| - 'src/**' | |
| - 'package.json' | |
| - 'composer.lock' | |
| push: | |
| branches: | |
| - main | |
| - stable* | |
| paths: | |
| - 'lib/**' | |
| - 'src/**' | |
| - 'package.json' | |
| - 'composer.lock' | |
| env: | |
| APP_NAME: recognize | |
| jobs: | |
| php: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| # do not stop on another job's failure | |
| fail-fast: false | |
| matrix: | |
| php-versions: ['8.3'] | |
| databases: ['sqlite'] | |
| server-versions: ['master'] | |
| pure-js-mode: ['false'] | |
| imagenet-enabled: ['true'] | |
| faces-enabled: ['true'] | |
| landmarks-enabled: ['true'] | |
| musicnn-enabled: ['true'] | |
| movinet-enabled: ['true'] | |
| include: | |
| # test pure-js once | |
| - server-versions: master | |
| databases: sqlite | |
| php-versions: 8.3 | |
| pure-js-mode: true | |
| imagenet-enabled: true | |
| faces-enabled: true | |
| landmarks-enabled: true | |
| musicnn-enabled: true | |
| movinet-enabled: false | |
| name: Test classify command on ${{ matrix.databases }}-${{ matrix.server-versions }} wasm:${{ matrix.pure-js-mode }},imagenet:${{ matrix.imagenet-enabled }},faces:${{ matrix.faces-enabled }},landmarks:${{ matrix.landmarks-enabled }},movinet:${{ matrix.movinet-enabled }},musicnn:${{ matrix.musicnn-enabled }} | |
| env: | |
| MYSQL_PORT: 4444 | |
| PGSQL_PORT: 4445 | |
| timeout-minutes: 36 # max + 3*std over the last 212 successful runs | |
| services: | |
| mysql: | |
| image: mariadb:10.5 | |
| ports: | |
| - 4444:3306/tcp | |
| env: | |
| MYSQL_ROOT_PASSWORD: rootpassword | |
| options: --health-cmd="mysqladmin ping" --health-interval 5s --health-timeout 2s --health-retries 5 | |
| postgres: | |
| image: postgres | |
| ports: | |
| - 4445:5432/tcp | |
| env: | |
| POSTGRES_USER: root | |
| POSTGRES_PASSWORD: rootpassword | |
| POSTGRES_DB: nextcloud | |
| options: --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5 | |
| steps: | |
| - name: Checkout server | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| with: | |
| repository: nextcloud/server | |
| ref: ${{ matrix.server-versions }} | |
| - name: Checkout submodules | |
| shell: bash | |
| run: | | |
| auth_header="$(git config --local --get http.https://github.com/.extraheader)" | |
| git submodule sync --recursive | |
| git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 | |
| - name: Set up php ${{ matrix.php-versions }} | |
| uses: shivammathur/setup-php@cf4cade2721270509d5b1c766ab3549210a39a2a # v2.33.0 | |
| with: | |
| php-version: ${{ matrix.php-versions }} | |
| tools: phpunit | |
| extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_mysql, pdo_sqlite, pgsql, pdo_pgsql, gd, zip | |
| - name: Checkout app | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| with: | |
| path: apps/${{ env.APP_NAME }} | |
| - name: Read package.json node and npm engines version | |
| uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3 | |
| id: versions | |
| with: | |
| path: apps/${{ env.APP_NAME }} | |
| fallbackNode: '^12' | |
| fallbackNpm: '^6' | |
| - name: Set up node ${{ steps.versions.outputs.nodeVersion }} | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 | |
| with: | |
| node-version: ${{ steps.versions.outputs.nodeVersion }} | |
| - name: Set up npm ${{ steps.versions.outputs.npmVersion }} | |
| run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}" | |
| - name: Install app | |
| working-directory: apps/${{ env.APP_NAME }} | |
| run: | | |
| composer install --no-dev | |
| make all | |
| make remove-binaries | |
| rm -rf models # Make it download from github | |
| wget https://github.com/nextcloud/recognize/releases/download/v3.4.0/test-files.zip | |
| unzip test-files.zip -d tests/res/ | |
| - name: Set up Nextcloud and install app | |
| if: ${{ matrix.databases != 'pgsql'}} | |
| run: | | |
| sleep 25 | |
| mkdir data | |
| ./occ maintenance:install --verbose --database=${{ matrix.databases }} --database-name=nextcloud --database-host=127.0.0.1 --database-port=$MYSQL_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password | |
| ./occ app:enable -vvv -f ${{ env.APP_NAME }} | |
| php -S localhost:8080 & | |
| - name: Set up Nextcloud and install app | |
| if: ${{ matrix.databases == 'pgsql'}} | |
| run: | | |
| sleep 25 | |
| mkdir data | |
| ./occ maintenance:install --verbose --database=${{ matrix.databases }} --database-name=nextcloud --database-host=127.0.0.1 --database-port=$PGSQL_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password | |
| ./occ app:enable -vvv -f ${{ env.APP_NAME }} | |
| php -S localhost:8080 & | |
| - name: Install | |
| run: | | |
| ./occ app:enable -vvv ${{ env.APP_NAME }} | |
| - name: Upload photos | |
| run: | | |
| find apps/${{ env.APP_NAME }}/tests/res/ -type f -exec curl -u 'admin:password' -T "{}" 'http://localhost:8080/remote.php/webdav/' \; | |
| - name: Set pure-js mode | |
| run: | | |
| ./occ config:app:set --lazy --value ${{ matrix.pure-js-mode }} recognize tensorflow.purejs | |
| - name: Set imagenet.enabled | |
| run: | | |
| ./occ config:app:set --value ${{ matrix.imagenet-enabled }} recognize imagenet.enabled | |
| - name: Set faces.enabled | |
| run: | | |
| ./occ config:app:set --value ${{ matrix.faces-enabled }} recognize faces.enabled | |
| - name: Set landmarks.enabled | |
| run: | | |
| ./occ config:app:set --value ${{ matrix.landmarks-enabled }} recognize landmarks.enabled | |
| - name: Set musicnn.enabled | |
| run: | | |
| ./occ config:app:set --value ${{ matrix.musicnn-enabled }} recognize musicnn.enabled | |
| - name: Set movinet.enabled | |
| run: | | |
| ./occ config:app:set --value ${{ matrix.movinet-enabled }} recognize movinet.enabled | |
| - name: Run classifier | |
| env: | |
| GITHUB_REF: ${{ github.ref }} | |
| run: | | |
| ./occ recognize:classify | |
| ./occ recognize:cluster-faces | |
| - name: Install cadaver | |
| if: ${{ matrix.faces-enabled }} | |
| run: | | |
| sudo apt -y install cadaver | |
| - name: Test webdav access | |
| if: ${{ matrix.faces-enabled }} | |
| run: | | |
| cat > ~/.netrc <<EOF | |
| machine localhost | |
| login admin | |
| password password | |
| EOF | |
| cadaver http://localhost:8080/remote.php/dav/recognize/admin/ <<EOF | |
| mv unassigned-faces/* faces/0/ | |
| mkdir faces/toot | |
| mv faces/0/* faces/toot/ | |
| mv faces/toot/ faces/blah/ | |
| EOF | |
| - name: Print logs | |
| if: always() | |
| run: | | |
| cat data/nextcloud.log | |
| taskprocessing: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| # do not stop on another job's failure | |
| fail-fast: false | |
| matrix: | |
| php-versions: ['8.3'] | |
| databases: ['sqlite'] | |
| # recognize_backend requires Nextcloud >= 32, so only test against master here | |
| server-versions: ['master'] | |
| imagenet-enabled: ['true'] | |
| faces-enabled: ['true'] | |
| musicnn-enabled: ['true'] | |
| movinet-enabled: ['true'] | |
| name: Test classify in taskprocessing mode on ${{ matrix.databases }}-${{ matrix.server-versions }} imagenet:${{ matrix.imagenet-enabled }},faces:${{ matrix.faces-enabled }},movinet:${{ matrix.movinet-enabled }},musicnn:${{ matrix.musicnn-enabled }} | |
| env: | |
| MYSQL_PORT: 4444 | |
| PGSQL_PORT: 4445 | |
| # recognize_backend ExApp (deployed via AppAPI's manual_install deploy daemon) | |
| PYTHONUNBUFFERED: 1 | |
| APP_HOST: 0.0.0.0 | |
| APP_ID: recognize_backend | |
| APP_PORT: 9031 | |
| APP_SECRET: 12345 | |
| APP_VERSION: 1.0.0 | |
| NEXTCLOUD_URL: http://localhost:8080 | |
| # No NVIDIA driver on the runner; the backend falls back to CPU automatically | |
| COMPUTE_DEVICE: CPU | |
| timeout-minutes: 90 # model download + CPU inference is slow on the first (uncached) run | |
| services: | |
| mysql: | |
| image: mariadb:10.5 | |
| ports: | |
| - 4444:3306/tcp | |
| env: | |
| MYSQL_ROOT_PASSWORD: rootpassword | |
| options: --health-cmd="mysqladmin ping" --health-interval 5s --health-timeout 2s --health-retries 5 | |
| postgres: | |
| image: postgres | |
| ports: | |
| - 4445:5432/tcp | |
| env: | |
| POSTGRES_USER: root | |
| POSTGRES_PASSWORD: rootpassword | |
| POSTGRES_DB: nextcloud | |
| options: --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5 | |
| steps: | |
| - name: Checkout server | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| with: | |
| repository: nextcloud/server | |
| ref: ${{ matrix.server-versions }} | |
| - name: Checkout submodules | |
| shell: bash | |
| run: | | |
| auth_header="$(git config --local --get http.https://github.com/.extraheader)" | |
| git submodule sync --recursive | |
| git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 | |
| - name: Set up php ${{ matrix.php-versions }} | |
| uses: shivammathur/setup-php@cf4cade2721270509d5b1c766ab3549210a39a2a # v2.33.0 | |
| with: | |
| php-version: ${{ matrix.php-versions }} | |
| tools: phpunit | |
| extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_mysql, pdo_sqlite, pgsql, pdo_pgsql, gd, zip | |
| - name: Checkout app | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| with: | |
| path: apps/${{ env.APP_NAME }} | |
| - name: Read package.json node and npm engines version | |
| uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3 | |
| id: versions | |
| with: | |
| path: apps/${{ env.APP_NAME }} | |
| fallbackNode: '^12' | |
| fallbackNpm: '^6' | |
| - name: Set up node ${{ steps.versions.outputs.nodeVersion }} | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 | |
| with: | |
| node-version: ${{ steps.versions.outputs.nodeVersion }} | |
| - name: Set up npm ${{ steps.versions.outputs.npmVersion }} | |
| run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}" | |
| - name: Install app | |
| working-directory: apps/${{ env.APP_NAME }} | |
| run: | | |
| composer install --no-dev | |
| make all | |
| make remove-binaries | |
| rm -rf models # Make it download from github | |
| wget https://github.com/nextcloud/recognize/releases/download/v3.4.0/test-files.zip | |
| unzip test-files.zip -d tests/res/ | |
| - name: Set up Nextcloud and install app | |
| run: | | |
| sleep 25 | |
| mkdir data | |
| ./occ maintenance:install --verbose --database=${{ matrix.databases }} --database-name=nextcloud --database-host=127.0.0.1 --database-port=$MYSQL_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password | |
| ./occ app:enable -vvv -f ${{ env.APP_NAME }} | |
| # 4 workers by default | |
| composer run serve & | |
| - name: Enable SQLite WAL mode | |
| if: ${{ matrix.databases == 'sqlite' }} | |
| run: | | |
| # WAL lets the ExApp's readers and cron's writers proceed concurrently | |
| # instead of serializing on SQLite's single-writer lock, which otherwise | |
| # stalls task scheduling for many minutes per cron run. The mode is | |
| # persisted in the database header, so it survives across connections. | |
| sqlite3 data/nextcloud.db "PRAGMA journal_mode=WAL;" | |
| - name: Checkout app_api | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| with: | |
| path: apps/app_api | |
| repository: nextcloud/app_api | |
| ref: ${{ matrix.server-versions == 'master' && 'main' || matrix.server-versions }} | |
| - name: Enable app_api | |
| run: ./occ app:enable -vvv -f app_api | |
| - name: Checkout recognize_backend | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| with: | |
| path: recognize_backend | |
| repository: nextcloud/recognize_backend | |
| ref: main | |
| - name: Set up python 3.11 | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: '3.11' | |
| cache: 'pip' | |
| cache-dependency-path: recognize_backend/requirements.txt | |
| - name: Install recognize_backend dependencies | |
| working-directory: recognize_backend | |
| run: | | |
| # onnxruntime-gpu needs CUDA libs at import time; swap in the CPU build for the runner | |
| sed -i 's/^onnxruntime-gpu.*/onnxruntime/' requirements.txt | |
| # The default PyPI torch wheels bundle ~5GB of NVIDIA CUDA libraries and | |
| # blow up the runner disk. Install the CPU-only build first so the torch | |
| # constraints in requirements.txt are already satisfied. | |
| pip install --index-url https://download.pytorch.org/whl/cpu torch torchvision torchaudio | |
| pip install -r requirements.txt | |
| - name: Cache recognize_backend models | |
| uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 | |
| id: models-cache-restore | |
| with: | |
| path: recognize_backend-persistent_storage/ | |
| key: ${{ runner.os }}-recognize-backend-models-v1 | |
| - name: Run recognize_backend | |
| working-directory: recognize_backend/lib | |
| env: | |
| APP_PERSISTENT_STORAGE: ${{ github.workspace }}/recognize_backend-persistent_storage | |
| HF_HOME: ${{ github.workspace }}/recognize_backend-persistent_storage/huggingface | |
| INSIGHTFACE_HOME: ${{ github.workspace }}/recognize_backend-persistent_storage/insightface | |
| run: | | |
| mkdir -p "$APP_PERSISTENT_STORAGE" | |
| python3 main.py > "$GITHUB_WORKSPACE/backend_logs" 2>&1 & | |
| - name: Register recognize_backend with AppAPI | |
| run: | | |
| ./occ app_api:daemon:register --net host manual_install "Manual Install" manual-install http localhost http://localhost:8080 | |
| ./occ app_api:app:register ${{ env.APP_ID }} manual_install --json-info \ | |
| "{\"appid\":\"${{ env.APP_ID }}\",\"name\":\"Recognize Backend\",\"daemon_config_name\":\"manual_install\",\"version\":\"${{ env.APP_VERSION }}\",\"secret\":\"${{ env.APP_SECRET }}\",\"port\":${{ env.APP_PORT }},\"scopes\":[\"TASK_PROCESSING\",\"FILES\"]}" \ | |
| --force-scopes --wait-finish | |
| - name: Upload photos | |
| run: | | |
| find apps/${{ env.APP_NAME }}/tests/res/ -type f -exec curl -u 'admin:password' -T "{}" 'http://localhost:8080/remote.php/webdav/' \; | |
| - name: Enable taskprocessing mode | |
| run: | | |
| ./occ config:app:set --value true recognize taskprocessing.enabled | |
| ./occ config:app:set --value ${{ matrix.imagenet-enabled }} recognize imagenet.enabled | |
| ./occ config:app:set --value ${{ matrix.faces-enabled }} recognize faces.enabled | |
| ./occ config:app:set --value ${{ matrix.musicnn-enabled }} recognize musicnn.enabled | |
| ./occ config:app:set --value ${{ matrix.movinet-enabled }} recognize movinet.enabled | |
| - name: Run classification via TaskProcessing | |
| id: classify | |
| run: | | |
| # Probe available disk so we can tell whether runs die on ENOSPC. | |
| disk_free() { df -h / | awk 'NR==2 {print $4" free ("$5" used)"}'; } | |
| # Query the DB with a busy timeout so a transient WAL lock (the ExApp and | |
| # cron write concurrently) makes the poll wait instead of failing with | |
| # "database is locked" (exit 5), which would otherwise kill the step. | |
| sq() { sqlite3 -cmd ".timeout 60000" data/nextcloud.db "$1"; } | |
| echo "disk before classification: $(disk_free)" | |
| ./occ upgrade # in case server master has new migrations in the meantime | |
| ./occ files:scan admin | |
| # Kick off a full classification run: SchedulerJob -> StorageCrawlJob | |
| # fills the faces queue and ClassifyFacesJob hands each batch to the | |
| # recognize_backend ExApp as a TaskProcessing task. Results are written | |
| # back asynchronously by the TaskResultListener when the ExApp reports. | |
| ./occ recognize:recrawl | |
| # Drive the background jobs by running cron in a loop until the faces | |
| # queue is drained and no crawl/scheduler jobs remain. | |
| for i in $(seq 1 180); do | |
| # Clustering is done explicitly in the "Run clustering" step below, so | |
| # drop these jobs before each cron run to keep classification cron fast. | |
| sq "delete from oc_jobs where class like '%ClusterFacesJob';" || true | |
| php cron.php || true | |
| QUEUE=$(sq "select count(*) from oc_recognize_queue_faces;") | |
| CRAWL=$(sq "select count(*) from oc_jobs where class like '%StorageCrawlJob' or class like '%SchedulerJob';") | |
| echo "round $i: faces queue=$QUEUE, pending crawl/scheduler jobs=$CRAWL, disk=$(disk_free)" | |
| if [ "$QUEUE" -eq 0 ] && [ "$CRAWL" -eq 0 ] && [ "$i" -gt 3 ]; then break; fi | |
| sleep 10 | |
| done | |
| # Wait for the ExApp to finish processing all scheduled TaskProcessing | |
| # tasks (status 0=unknown, 1=scheduled, 2=running are still pending). | |
| for i in $(seq 1 240); do | |
| PENDING=$(sq "select count(*) from oc_taskprocessing_tasks where app_id = 'recognize' and status in (0, 1, 2);") | |
| echo "wait $i: pending recognize taskprocessing tasks=$PENDING, disk=$(disk_free)" | |
| if [ "$PENDING" -eq 0 ]; then break; fi | |
| sleep 30 | |
| done | |
| echo "disk after classification: $(disk_free)" | |
| # Whether the run actually finished. The faces queue emptying is not | |
| # enough: files leave the queue when their TaskProcessing task is | |
| # *scheduled*, not when its results are written back, so a drained queue | |
| # with tasks still pending means those photos have no detections at all. | |
| # Detection counts cannot be used here - a photo with no face in it | |
| # legitimately produces no rows - so completeness is queue + task state. | |
| QUEUE=$(sq "select count(*) from oc_recognize_queue_faces;") | |
| PENDING=$(sq "select count(*) from oc_taskprocessing_tasks where app_id = 'recognize' and status in (0, 1, 2);") | |
| FAILED=$(sq "select count(*) from oc_taskprocessing_tasks where app_id = 'recognize' and status = 4;") | |
| TOTAL=$(sq "select count(*) from oc_taskprocessing_tasks where app_id = 'recognize';") | |
| echo "final: faces queue=$QUEUE, pending tasks=$PENDING, failed tasks=$FAILED, total tasks=$TOTAL" | |
| echo "photos on disk: $(find data/admin/files -type f | wc -l)" | |
| DETECTIONS=$(sq "select count(*) from oc_recognize_face_detections;") | |
| echo "photos with detections: $(sq "select count(distinct file_id) from oc_recognize_face_detections where user_id = 'admin';")" | |
| echo "face detections: $DETECTIONS" | |
| if [ "$QUEUE" -eq 0 ] && [ "$PENDING" -eq 0 ] && [ "$FAILED" -eq 0 ]; then | |
| echo "complete=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "complete=false" >> "$GITHUB_OUTPUT" | |
| echo "::error title=Incomplete classification run::faces queue=$QUEUE, pending tasks=$PENDING, failed tasks=$FAILED of $TOTAL. Each pending task covers up to 500 photos, whose detections were never written. The detection DB will NOT be cached; the metrics below score only the photos that made it through." | |
| fi | |
| # Hard signal for this job: the TaskProcessing pipeline must actually | |
| # persist face detections. | |
| [ "$DETECTIONS" -gt 0 ] | |
| - name: Save recognize_backend models cache | |
| if: always() && steps.models-cache-restore.outputs.cache-hit != 'true' | |
| uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 | |
| with: | |
| path: recognize_backend-persistent_storage/ | |
| key: ${{ steps.models-cache-restore.outputs.cache-primary-key }} | |
| - name: Cluster faces | |
| run: | | |
| ./occ recognize:cluster-faces | |
| - name: Install cadaver | |
| if: ${{ matrix.faces-enabled }} | |
| run: | | |
| sudo apt -y install cadaver | |
| - name: Test webdav access | |
| if: ${{ matrix.faces-enabled }} | |
| run: | | |
| cat > ~/.netrc <<EOF | |
| machine localhost | |
| login admin | |
| password password | |
| EOF | |
| cadaver http://localhost:8080/remote.php/dav/recognize/admin/ <<EOF | |
| mv unassigned-faces/* faces/0/ | |
| mkdir faces/toot | |
| mv faces/0/* faces/toot/ | |
| mv faces/toot/ faces/blah/ | |
| EOF | |
| - name: Print logs | |
| if: always() | |
| run: | | |
| cat data/nextcloud.log | |
| echo '-------------------- recognize_backend logs --------------------' | |
| cat "$GITHUB_WORKSPACE/backend_logs" || echo "No backend logs" |