From 50f792dc64f8912b7fefc9dcb0fe5a0c115fb02e Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 10:06:14 -0400 Subject: [PATCH 01/43] Add a trivial docs marker for PR/CI smoke testing. --- docs/local/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/local/README.md b/docs/local/README.md index 530a709f6..bfdd67cfa 100644 --- a/docs/local/README.md +++ b/docs/local/README.md @@ -19,3 +19,5 @@ 1. [install.md](./install.md) → [environment.md](./environment.md) 2. [run-the-app.md](./run-the-app.md) → http://localhost:3000 3. Tests only when you need them: [run-tests.md](./run-tests.md) + + From 5210447de1a516ee69abc79ee4f3a93fcb56384b Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 10:09:38 -0400 Subject: [PATCH 02/43] Limit Hyku release-label CI to PRs targeting main. Feature PRs into hyku-oob should not require patch/minor/major release labels. --- .github/workflows/verify_labels.yml | 5 ++++- docs/local/README.md | 1 + docs/local/github-actions.md | 17 +++++++++++++++++ docs/local/updating-from-hyku.md | 1 + 4 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 docs/local/github-actions.md diff --git a/.github/workflows/verify_labels.yml b/.github/workflows/verify_labels.yml index 65d72ef9c..b4a9d5912 100644 --- a/.github/workflows/verify_labels.yml +++ b/.github/workflows/verify_labels.yml @@ -1,8 +1,11 @@ +# Upstream Hyku uses this to enforce release-note labels on PRs. +# Scholar@UC feature work targets hyku-oob (and later develop), not Hyku releases. +# Only require those labels when the PR base is main. name: Verify on: pull_request: branches: - - '**' + - main types: - opened - synchronize diff --git a/docs/local/README.md b/docs/local/README.md index bfdd67cfa..a7ef40f55 100644 --- a/docs/local/README.md +++ b/docs/local/README.md @@ -13,6 +13,7 @@ | [switching-to-develop.md](./switching-to-develop.md) | Switching back to Scholar@UC `develop` | | [troubleshooting.md](./troubleshooting.md) | Common failures | | [updating-from-hyku.md](./updating-from-hyku.md) | Pulling Hyku updates with a normal `git merge` | +| [github-actions.md](./github-actions.md) | PR label checker and other Actions notes | ## Quick start diff --git a/docs/local/github-actions.md b/docs/local/github-actions.md new file mode 100644 index 000000000..63f7b0d8f --- /dev/null +++ b/docs/local/github-actions.md @@ -0,0 +1,17 @@ +# GitHub Actions / CI notes (hyku-oob) + +Team index: [docs/local/README.md](./README.md). + +## PR label checker (`Verify` / “PR has required labels”) + +Upstream Hyku requires release labels (`patch-ver`, `minor-ver`, `major-ver`, `ignore-for-release`, or `dependencies`) on PRs. + +For Scholar@UC work we **do not** use that process on feature branches. `.github/workflows/verify_labels.yml` is limited to PRs whose **base branch is `main`**. + +PRs into `hyku-oob` (or other non-`main` bases) should **not** need those labels. + +If a Hyku merge resets that workflow to `branches: ['**']`, restore the `main`-only filter (see the comments in the workflow file). + +## Other CI + +Hyku also ships workflows such as `build-test-lint.yaml`. Those are separate from the label check and may still run on your PRs depending on their `on:` triggers. diff --git a/docs/local/updating-from-hyku.md b/docs/local/updating-from-hyku.md index 6161d76da..c986bac6c 100644 --- a/docs/local/updating-from-hyku.md +++ b/docs/local/updating-from-hyku.md @@ -28,3 +28,4 @@ Resolve any conflicts in app code or Hyku-owned docs as usual. Leave root `READM - Skim Hyku’s updated `docs/*.md` / root README for new upstream guidance. - Keep UC procedures in `docs/local/` only — do not copy them into the root README (they would be wiped on the next merge). +- Check `.github/workflows/verify_labels.yml`: we keep it **main-only** so feature PRs into `hyku-oob` do not need release labels. If the merge restores `branches: ['**']`, put the main-only filter back ([github-actions.md](./github-actions.md)). From 6bc2a7e955cd83533e60d4132adfb7143f841d46 Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 10:28:15 -0400 Subject: [PATCH 03/43] Add hyku-oob GitHub Actions CI with parallel RSpec. Wire RuboCop, Brakeman, bundler-audit, and six RSpec shards against Postgres/Redis/Solr/Fedora, and keep Hyku image-publish CI off this fork. --- .github/workflows/build-test-lint.yaml | 10 +- .github/workflows/ci.yml | 213 +++++++++++++++++++++++++ Gemfile | 2 + Gemfile.lock | 9 +- docker-compose.ci.yml | 62 +++++++ docs/local/README.md | 2 +- docs/local/github-actions.md | 22 ++- 7 files changed, 315 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 docker-compose.ci.yml diff --git a/.github/workflows/build-test-lint.yaml b/.github/workflows/build-test-lint.yaml index 73f8394ae..e080285cc 100644 --- a/.github/workflows/build-test-lint.yaml +++ b/.github/workflows/build-test-lint.yaml @@ -1,5 +1,7 @@ name: "build-test-lint" run-name: Build of ${{ github.ref_name }} by @${{ github.actor }} +# Scholar@UC: this workflow publishes to ghcr.io/samvera/hyku. Only run on the +# upstream Samvera repo — feature work on uclibs/ucrate uses .github/workflows/ci.yml. on: push: branches: @@ -33,6 +35,7 @@ env: jobs: build: + if: github.repository == 'samvera/hyku' strategy: matrix: component: ["solr", "web", "worker"] @@ -88,6 +91,7 @@ jobs: tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ matrix.component }}:${{ env.TAG }}-${{ env.PLATFORM_TAG }} push: + if: github.repository == 'samvera/hyku' needs: build runs-on: ubuntu-latest permissions: write-all @@ -122,6 +126,7 @@ jobs: done lint: + if: github.repository == 'samvera/hyku' needs: push uses: notch8/actions/.github/workflows/lint.yaml@v1.0.6 with: @@ -130,6 +135,7 @@ jobs: rubocop_cmd: "bundle exec rubocop --parallel --format progress" test: + if: github.repository == 'samvera/hyku' needs: push uses: notch8/actions/.github/workflows/test.yaml@v1.0.6 with: @@ -137,6 +143,7 @@ jobs: rspec_cmd: "gem install semaphore_test_boosters && bundle && rspec_booster --job $CI_NODE_INDEX/$CI_NODE_TOTAL" single-tenant-test: + if: github.repository == 'samvera/hyku' needs: push runs-on: ubuntu-latest defaults: @@ -234,6 +241,7 @@ jobs: run: docker compose -f docker-compose.single.yml down -v --remove-orphans env-file-lint: + if: github.repository == 'samvera/hyku' # Guard against a regression class: `${VAR}` shell interpolation of any # form placed inside `.env`. It is not reliably expanded through env_file: # the `${VAR:-default}` form is only handled on newer Compose (>= ~2.24), @@ -263,6 +271,6 @@ jobs: echo "No \${...} interpolation found in .env" reports: - if: always() + if: always() && github.repository == 'samvera/hyku' needs: [test, lint, single-tenant-test] uses: notch8/actions/.github/workflows/report.yaml@v1.0.6 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..877338183 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,213 @@ +# Scholar@UC CI for hyku-oob (CircleCI-like: lint, security, parallel RSpec). +# Does not use Hyku's Docker image-publish pipeline (build-test-lint.yaml). +name: CI + +on: + push: + branches: + - hyku-oob + pull_request: + branches: + - hyku-oob + workflow_dispatch: + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + BUNDLE_JOBS: 4 + BUNDLE_RETRY: 3 + RAILS_ENV: test + RACK_ENV: test + IN_DOCKER: "true" + DB_ADAPTER: postgresql + DB_HOST: localhost + DB_PORT: "5432" + DB_NAME: hyku_test + DB_TEST_NAME: hyku_test + DB_USER: postgres + DB_PASSWORD: postgres + REDIS_HOST: localhost + REDIS_PORT: "6379" + FCREPO_HOST: localhost + FCREPO_PORT: "8080" + FCREPO_TEST_PORT: "8080" + FCREPO_BASE_PATH: /test + FCREPO_TEST_BASE_PATH: /test + SOLR_HOST: localhost + SOLR_PORT: "8983" + SOLR_URL: http://localhost:8983/solr/ + SOLR_COLLECTION_NAME: hydra-test + SOLR_COLLECTION_TEST: hydra-test + SOLR_CONFIGSET_NAME: hyku + SOLR_ENABLE_CLOUD_MODE: "yes" + CHROME_HOSTNAME: localhost + # Capybara app_host seen from the Chrome container (see docker-compose.ci.yml extra_hosts) + WEB_HOST: host.docker.internal + HYKU_MULTITENANT: "true" + HYKU_ROOT_HOST: test.host + HYKU_ADMIN_HOST: test.host + SECRET_KEY_BASE: ci-secret-key-base-not-for-production + DISABLE_REDIS_CLUSTER: "true" + +jobs: + lint: + name: RuboCop + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.3.6" + bundler-cache: true + - name: RuboCop + run: bundle exec rubocop --format progress + + security: + name: Brakeman and bundler-audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.3.6" + bundler-cache: true + - name: Brakeman + run: bundle exec brakeman -q -w 2 + - name: bundler-audit + run: bundle exec bundler-audit check --update + + test: + name: RSpec (shard ${{ matrix.ci_node_index }}/6) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + ci_node_index: [0, 1, 2, 3, 4, 5] + env: + CI_NODE_INDEX: ${{ matrix.ci_node_index }} + CI_NODE_TOTAL: "6" + steps: + - uses: actions/checkout@v4 + + - name: Start CI dependency stack + run: docker compose -f docker-compose.ci.yml up -d + + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.3.6" + bundler-cache: true + + - uses: actions/setup-node@v4 + with: + node-version: "20" + cache: yarn + + - name: Install JS dependencies + run: yarn install --frozen-lockfile || yarn install + + - name: Install wait tools + run: sudo apt-get update && sudo apt-get install -y netcat-openbsd zip curl postgresql-client redis-tools + + - name: Wait for Postgres + run: | + for i in $(seq 1 60); do + if pg_isready -h localhost -p 5432 -U postgres; then + exit 0 + fi + sleep 2 + done + echo "Postgres did not become ready" + docker compose -f docker-compose.ci.yml logs db + exit 1 + + - name: Wait for Redis + run: | + for i in $(seq 1 30); do + if redis-cli -h localhost ping | grep -q PONG; then + exit 0 + fi + sleep 2 + done + echo "Redis did not become ready" + docker compose -f docker-compose.ci.yml logs redis + exit 1 + + - name: Wait for Solr + run: | + for i in $(seq 1 60); do + if curl -sf http://localhost:8983/solr/admin/info/system >/dev/null; then + exit 0 + fi + sleep 3 + done + echo "Solr did not become ready" + docker compose -f docker-compose.ci.yml logs solr + exit 1 + + - name: Wait for Fedora + run: | + for i in $(seq 1 60); do + code=$(curl -sf -o /dev/null -w "%{http_code}" http://localhost:8080/rest || true) + if echo "$code" | grep -Eq '200|401|403|404'; then + exit 0 + fi + sleep 3 + done + echo "Fedora did not become ready" + docker compose -f docker-compose.ci.yml logs fcrepo + exit 1 + + - name: Prepare database + run: bundle exec rails db:prepare + + - name: Upload Solr configset (best effort) + run: | + chmod +x bin/solrcloud-upload-configset.sh bin/solrcloud-assign-configset.sh || true + ./bin/solrcloud-upload-configset.sh solr/conf || true + SOLR_COLLECTION_NAME=hydra-test ./bin/solrcloud-assign-configset.sh || true + + - name: Run RSpec shard + run: | + set -euo pipefail + mapfile -t ALL_SPECS < <(find spec -name '*_spec.rb' | sort) + SPECS=() + i=0 + for spec in "${ALL_SPECS[@]}"; do + if (( i % CI_NODE_TOTAL == CI_NODE_INDEX )); then + SPECS+=("$spec") + fi + i=$((i + 1)) + done + echo "Shard ${CI_NODE_INDEX}/${CI_NODE_TOTAL}: ${#SPECS[@]} specs" + if (( ${#SPECS[@]} == 0 )); then + echo "No specs for this shard" + exit 0 + fi + mkdir -p tmp/test-results + bundle exec rspec "${SPECS[@]}" \ + --tag '~speed:slow' \ + --format progress \ + --format RspecJunitFormatter \ + --out "tmp/test-results/rspec-${CI_NODE_INDEX}.xml" + + - name: Upload RSpec results + if: always() + uses: actions/upload-artifact@v4 + with: + name: rspec-results-${{ matrix.ci_node_index }} + path: tmp/test-results/ + if-no-files-found: ignore + + - name: Upload Capybara failures + if: failure() + uses: actions/upload-artifact@v4 + with: + name: capybara-${{ matrix.ci_node_index }} + path: tmp/capybara/ + if-no-files-found: ignore + + - name: Tear down stack + if: always() + run: docker compose -f docker-compose.ci.yml down -v --remove-orphans diff --git a/Gemfile b/Gemfile index 415da7bd2..827752e22 100644 --- a/Gemfile +++ b/Gemfile @@ -26,7 +26,9 @@ gem 'blacklight_advanced_search' gem 'blacklight_oai_provider', '~> 7.0' gem 'blacklight_range_limit', '~> 8.5' gem 'bolognese', '>= 1.9.10' +gem 'brakeman', group: %i[development test] gem 'bootstrap', '~> 4.6' +gem 'bundler-audit', group: %i[development test] gem 'bootstrap-datepicker-rails' gem 'bulkrax', '~> 9.5' gem 'byebug', group: %i[development test] diff --git a/Gemfile.lock b/Gemfile.lock index 66f5bb047..509067dd6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -271,7 +271,7 @@ GEM babel-source (>= 4.0, < 6) execjs (~> 2.0) backport (1.2.0) - bagit (0.6.0) + bagit (0.6.1) docopt (~> 0.5.0) validatable (~> 1.6) base64 (0.3.0) @@ -360,6 +360,8 @@ GEM popper_js (>= 1.16.1, < 2) bootstrap-datepicker-rails (1.10.0.1) railties (>= 3.0) + brakeman (8.0.5) + racc breadcrumbs_on_rails (3.0.1) browse-everything (1.5.0) addressable (~> 2.5) @@ -388,6 +390,9 @@ GEM rdf (>= 2.0.2, < 4.0) rubyzip simple_form + bundler-audit (0.9.3) + bundler (>= 1.2.0) + thor (~> 1.0) byebug (12.0.0) cancancan (3.6.1) capybara (3.40.0) @@ -1665,7 +1670,9 @@ DEPENDENCIES bolognese (>= 1.9.10) bootstrap (~> 4.6) bootstrap-datepicker-rails + brakeman bulkrax (~> 9.5) + bundler-audit byebug capybara capybara-screenshot (~> 1.0) diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml new file mode 100644 index 000000000..06a14cd89 --- /dev/null +++ b/docker-compose.ci.yml @@ -0,0 +1,62 @@ +# CI dependency stack for GitHub Actions (Scholar@UC / hyku-oob). +# Started by .github/workflows/ci.yml — one stack per parallel test shard. +services: + db: + image: postgres:16 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: hyku_test + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 20 + + redis: + image: redis:7 + ports: + - "6379:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 5s + retries: 20 + + fcrepo: + image: ghcr.io/samvera/fcrepo4:4.7.5 + environment: + JAVA_OPTS: >- + -Djava.awt.headless=true + -Dfcrepo.modeshape.configuration=classpath:/config/file-simple/repository.json + -Dfcrepo.object.directory=/data/objects + -Dfcrepo.binary.directory=/data/binaries + ports: + - "8080:8080" + + # SolrCloud with embedded ZooKeeper (same approach as develop CircleCI: solr -cloud) + solr: + image: solr:8.11.2 + command: ["solr-foreground", "-c"] + ports: + - "8983:8983" + healthcheck: + test: ["CMD-SHELL", "curl -sf http://localhost:8983/solr/admin/info/system || exit 1"] + interval: 10s + timeout: 5s + retries: 30 + start_period: 30s + + chrome: + image: selenium/standalone-chromium:latest + ports: + - "4444:4444" + shm_size: "2gb" + # Rails runs on the GHA host; Chrome must reach it via the Docker host gateway. + extra_hosts: + - "host.docker.internal:host-gateway" + environment: + SE_NODE_MAX_SESSIONS: 2 + SE_NODE_OVERRIDE_MAX_SESSIONS: "true" diff --git a/docs/local/README.md b/docs/local/README.md index a7ef40f55..96c1819aa 100644 --- a/docs/local/README.md +++ b/docs/local/README.md @@ -13,7 +13,7 @@ | [switching-to-develop.md](./switching-to-develop.md) | Switching back to Scholar@UC `develop` | | [troubleshooting.md](./troubleshooting.md) | Common failures | | [updating-from-hyku.md](./updating-from-hyku.md) | Pulling Hyku updates with a normal `git merge` | -| [github-actions.md](./github-actions.md) | PR label checker and other Actions notes | +| [github-actions.md](./github-actions.md) | CI on `hyku-oob` (RuboCop, security, parallel RSpec); label checker notes | ## Quick start diff --git a/docs/local/github-actions.md b/docs/local/github-actions.md index 63f7b0d8f..bdf3c98a1 100644 --- a/docs/local/github-actions.md +++ b/docs/local/github-actions.md @@ -2,6 +2,22 @@ Team index: [docs/local/README.md](./README.md). +## Scholar@UC CI (`CI` workflow) + +PRs and pushes to **`hyku-oob`** run [`.github/workflows/ci.yml`](../../.github/workflows/ci.yml). This is the CircleCI-style path for feature work — not Hyku’s Docker image-publish pipeline. + +| Job | What it does | +|-----|----------------| +| **RuboCop** | `bundle exec rubocop` (no Solr/Fedora) | +| **Brakeman and bundler-audit** | Security scans (no Solr/Fedora) | +| **RSpec (shards 0–5)** | Six parallel jobs; each starts Postgres, Redis, Solr, Fedora (and Chrome) via [`docker-compose.ci.yml`](../../docker-compose.ci.yml), then runs ~1/6 of the spec files | + +Required checks for PRs into `hyku-oob` should be those jobs (lint, security, and all six RSpec shards). No release labels are required for these checks. + +CI uses Actions + Docker **only on the runner**. Local macOS setup under `docs/local/` stays no-Docker. + +Making the full suite green is a follow-up; the workflow is wired first so failures are visible. + ## PR label checker (`Verify` / “PR has required labels”) Upstream Hyku requires release labels (`patch-ver`, `minor-ver`, `major-ver`, `ignore-for-release`, or `dependencies`) on PRs. @@ -12,6 +28,8 @@ PRs into `hyku-oob` (or other non-`main` bases) should **not** need those labels If a Hyku merge resets that workflow to `branches: ['**']`, restore the `main`-only filter (see the comments in the workflow file). -## Other CI +## Hyku `build-test-lint` (do not use on this fork) + +[`.github/workflows/build-test-lint.yaml`](../../.github/workflows/build-test-lint.yaml) builds and pushes multi-arch images to `ghcr.io/samvera/hyku`. On this fork every job is gated with `if: github.repository == 'samvera/hyku'`, so ordinary `hyku-oob` feature PRs do **not** run it. -Hyku also ships workflows such as `build-test-lint.yaml`. Those are separate from the label check and may still run on your PRs depending on their `on:` triggers. +After merging from Hyku, re-check that guard (and `verify_labels` triggers) if upstream restores broader behavior. From 923283aca137c1b03406ee5ebbdda31af8aa9e1a Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 10:37:43 -0400 Subject: [PATCH 04/43] Baseline Hyku OOB Brakeman findings so CI can pass. Ignore the eight Medium/High warnings present in upstream Hyku code so new regressions still fail while the out-of-box setup stays green. --- config/brakeman.ignore | 186 +++++++++++++++++++++++++++++++++++ docs/local/github-actions.md | 2 +- 2 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 config/brakeman.ignore diff --git a/config/brakeman.ignore b/config/brakeman.ignore new file mode 100644 index 000000000..ef3c1ce78 --- /dev/null +++ b/config/brakeman.ignore @@ -0,0 +1,186 @@ +{ + "ignored_warnings": [ + { + "warning_type": "Remote Code Execution", + "warning_code": 24, + "fingerprint": "0823ab4c52ee68ca7e4031f4ff42b364fc4245103654ccc0874bcc8b45e6e93d", + "check_name": "UnsafeReflection", + "message": "Unsafe reflection method `constantize` called on parameter value", + "file": "app/presenters/hyku/deposit_wizard/presenter.rb", + "line": 502, + "link": "https://brakemanscanner.org/docs/warning_types/remote_code_execution/", + "code": "params[:work_type].to_s.constantize", + "render_path": null, + "location": { + "type": "method", + "class": "Hyku::DepositWizard::Presenter", + "method": "select_work_type" + }, + "user_input": "params[:work_type].to_s", + "confidence": "High", + "cwe_id": [ + 470 + ], + "note": "Hyku upstream / OOB baseline — accepted until fixed upstream or intentionally hardened." + }, + { + "warning_type": "Mass Assignment", + "warning_code": 105, + "fingerprint": "187071d999f8d3f46a7dd7ba10f4d8427b1a4e2684af6320e0e2f6984893d287", + "check_name": "PermitAttributes", + "message": "Potentially dangerous key allowed for mass assignment", + "file": "app/controllers/hyku/invitations_controller.rb", + "line": 36, + "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", + "code": "params.require(:user).permit(:email, :role)", + "render_path": null, + "location": { + "type": "method", + "class": "Hyku::InvitationsController", + "method": "user_params" + }, + "user_input": ":role", + "confidence": "Medium", + "cwe_id": [ + 915 + ], + "note": "Hyku upstream / OOB baseline — accepted until fixed upstream or intentionally hardened." + }, + { + "warning_type": "Dynamic Render Path", + "warning_code": 15, + "fingerprint": "1f9487ee406544a43f34d8b720ad73180c81bdbf796bee263d292441a4f815b6", + "check_name": "Render", + "message": "Render path contains parameter value", + "file": "app/controllers/hyrax/deposit_wizard_controller.rb", + "line": 47, + "link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/", + "code": "render(action => params[:step].to_s, {})", + "render_path": null, + "location": { + "type": "method", + "class": "Hyrax::DepositWizardController", + "method": "show" + }, + "user_input": "params[:step].to_s", + "confidence": "High", + "cwe_id": [ + 22 + ], + "note": "Hyku upstream / OOB baseline — accepted until fixed upstream or intentionally hardened." + }, + { + "warning_type": "Mass Assignment", + "warning_code": 70, + "fingerprint": "4f5cdbd540d7d0d970c40c606f82b8e181a0c0b451f44676863bbc33dbbe5423", + "check_name": "MassAssignment", + "message": "Specify exact keys allowed for mass assignment instead of using `permit!` which allows any keys", + "file": "app/presenters/hyku/deposit_wizard/presenter.rb", + "line": 288, + "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", + "code": "params.fetch(work_form.model_name.param_key, {}).permit!", + "render_path": null, + "location": { + "type": "method", + "class": "Hyku::DepositWizard::Presenter", + "method": "capture_review_extras" + }, + "user_input": null, + "confidence": "Medium", + "cwe_id": [ + 915 + ], + "note": "Hyku upstream / OOB baseline — accepted until fixed upstream or intentionally hardened." + }, + { + "warning_type": "Mass Assignment", + "warning_code": 70, + "fingerprint": "979c41d94c82a3de2f9b56b37946a6b32a6c157455fb73210d38ff86b95963a5", + "check_name": "MassAssignment", + "message": "Specify exact keys allowed for mass assignment instead of using `permit!` which allows any keys", + "file": "app/presenters/hyku/deposit_wizard/presenter.rb", + "line": 532, + "link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/", + "code": "params.fetch(:file_metadata, {}).permit!", + "render_path": null, + "location": { + "type": "method", + "class": "Hyku::DepositWizard::Presenter", + "method": "advance_from_file_meta" + }, + "user_input": null, + "confidence": "Medium", + "cwe_id": [ + 915 + ], + "note": "Hyku upstream / OOB baseline — accepted until fixed upstream or intentionally hardened." + }, + { + "warning_type": "Unmaintained Dependency", + "warning_code": 122, + "fingerprint": "98b26f60d776fd41ee6f088c833725145be9aac2d7c5b33780241c273622db42", + "check_name": "EOLRails", + "message": "Support for Rails 7.2.3.1 ends on 2026-08-09", + "file": "Gemfile.lock", + "line": 1154, + "link": "https://brakemanscanner.org/docs/warning_types/unmaintained_dependency/", + "code": null, + "render_path": null, + "location": null, + "user_input": null, + "confidence": "Medium", + "cwe_id": [ + 1104 + ], + "note": "Hyku upstream / OOB baseline — accepted until fixed upstream or intentionally hardened." + }, + { + "warning_type": "Remote Code Execution", + "warning_code": 24, + "fingerprint": "bfbe7e80f1a8bb7389d6f306238bc3638f269bf11dcd9396131eaef6965cc969", + "check_name": "UnsafeReflection", + "message": "Unsafe reflection method `constantize` called on model attribute", + "file": "app/helpers/hyku_helper.rb", + "line": 35, + "link": "https://brakemanscanner.org/docs/warning_types/remote_code_execution/", + "code": "SolrDocument[\"has_model_ssim\"].first.constantize", + "render_path": null, + "location": { + "type": "method", + "class": "HykuHelper", + "method": "parent_path" + }, + "user_input": "SolrDocument[\"has_model_ssim\"].first", + "confidence": "Medium", + "cwe_id": [ + 470 + ], + "note": "Hyku upstream / OOB baseline — accepted until fixed upstream or intentionally hardened." + }, + { + "warning_type": "Command Injection", + "warning_code": 14, + "fingerprint": "cbbf10f6d262b3afca23f5e73f6c42920434709d3b9c2bea06458765b27b8ed7", + "check_name": "Execute", + "message": "Possible command injection", + "file": "lib/reprocessor.rb", + "line": 196, + "link": "https://brakemanscanner.org/docs/warning_types/command_injection/", + "code": "`wc -l #{id_path}`", + "render_path": null, + "location": { + "type": "method", + "class": "Reprocessor", + "method": "id_line_size" + }, + "user_input": "id_path", + "confidence": "Medium", + "cwe_id": [ + 77 + ], + "note": "Hyku upstream / OOB baseline — accepted until fixed upstream or intentionally hardened." + } + ], + "updated": "2026-07-20 14:37:36 +0000", + "brakeman_version": "8.0.5" +} diff --git a/docs/local/github-actions.md b/docs/local/github-actions.md index bdf3c98a1..dead74a97 100644 --- a/docs/local/github-actions.md +++ b/docs/local/github-actions.md @@ -9,7 +9,7 @@ PRs and pushes to **`hyku-oob`** run [`.github/workflows/ci.yml`](../../.github/ | Job | What it does | |-----|----------------| | **RuboCop** | `bundle exec rubocop` (no Solr/Fedora) | -| **Brakeman and bundler-audit** | Security scans (no Solr/Fedora) | +| **Brakeman and bundler-audit** | Security scans (no Solr/Fedora). Known Hyku OOB findings are baselined in [`config/brakeman.ignore`](../../config/brakeman.ignore); **new** Medium/High warnings still fail CI. | | **RSpec (shards 0–5)** | Six parallel jobs; each starts Postgres, Redis, Solr, Fedora (and Chrome) via [`docker-compose.ci.yml`](../../docker-compose.ci.yml), then runs ~1/6 of the spec files | Required checks for PRs into `hyku-oob` should be those jobs (lint, security, and all six RSpec shards). No release labels are required for these checks. From 2cf35e02de88c060957217de4fac61ff30d73819 Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 10:40:11 -0400 Subject: [PATCH 05/43] Speed up CI with Docker, apt, RuboCop, and advisory-db caches. Reuse Bundler/Yarn caches already in place, overlap image pulls with setup on cache miss, and avoid parallel shards racing an empty Docker save. --- .github/workflows/ci.yml | 77 ++++++++++++++++++++++++++++++++++-- docs/local/github-actions.md | 2 + 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 877338183..68551fc00 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,6 +50,7 @@ env: HYKU_ADMIN_HOST: test.host SECRET_KEY_BASE: ci-secret-key-base-not-for-production DISABLE_REDIS_CLUSTER: "true" + RUBOCOP_CACHE_ROOT: ${{ github.workspace }}/tmp/rubocop_cache jobs: lint: @@ -57,10 +58,20 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 with: ruby-version: "3.3.6" bundler-cache: true + + - name: Cache RuboCop + uses: actions/cache@v4 + with: + path: tmp/rubocop_cache + key: rubocop-${{ runner.os }}-${{ hashFiles('Gemfile.lock', '.rubocop.yml', '.rubocop_todo.yml', '.rubocop_todo.hyrax.yml') }} + restore-keys: | + rubocop-${{ runner.os }}- + - name: RuboCop run: bundle exec rubocop --format progress @@ -69,12 +80,23 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 with: ruby-version: "3.3.6" bundler-cache: true + + - name: Cache ruby-advisory-db + uses: actions/cache@v4 + with: + path: ~/.local/share/ruby-advisory-db + key: ruby-advisory-db-${{ runner.os }}-${{ github.run_id }} + restore-keys: | + ruby-advisory-db-${{ runner.os }}- + - name: Brakeman run: bundle exec brakeman -q -w 2 + - name: bundler-audit run: bundle exec bundler-audit check --update @@ -91,8 +113,31 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Start CI dependency stack - run: docker compose -f docker-compose.ci.yml up -d + - name: Cache CI Docker images + id: cache-docker + uses: actions/cache@v4 + with: + path: /tmp/ci-docker-images + key: ci-docker-${{ runner.os }}-${{ hashFiles('docker-compose.ci.yml') }} + # Only shard 0 writes the cache so parallel shards cannot race an empty save. + lookup-only: ${{ matrix.ci_node_index != 0 }} + + - name: Load cached Docker images + if: steps.cache-docker.outputs.cache-hit == 'true' + run: | + set -euo pipefail + for f in /tmp/ci-docker-images/*.tar; do + echo "Loading $f" + docker load -i "$f" + done + + # Overlap image pull with Ruby/Node setup when cache missed. + - name: Pull CI images (background) + if: steps.cache-docker.outputs.cache-hit != 'true' + run: | + set -euo pipefail + docker compose -f docker-compose.ci.yml pull --quiet & + echo $! > /tmp/docker-pull.pid - uses: ruby/setup-ruby@v1 with: @@ -107,8 +152,32 @@ jobs: - name: Install JS dependencies run: yarn install --frozen-lockfile || yarn install - - name: Install wait tools - run: sudo apt-get update && sudo apt-get install -y netcat-openbsd zip curl postgresql-client redis-tools + - name: Cache apt packages + uses: awalsh128/cache-apt-pkgs-action@v1.5.0 + with: + packages: netcat-openbsd zip curl postgresql-client redis-tools + version: "1" + + - name: Wait for Docker image pull + if: steps.cache-docker.outputs.cache-hit != 'true' + run: | + set -euo pipefail + wait "$(cat /tmp/docker-pull.pid)" + + - name: Save Docker images for cache + if: steps.cache-docker.outputs.cache-hit != 'true' && matrix.ci_node_index == 0 + run: | + set -euo pipefail + mkdir -p /tmp/ci-docker-images + i=0 + for img in $(docker compose -f docker-compose.ci.yml config --images); do + echo "Saving $img" + docker save "$img" -o "/tmp/ci-docker-images/${i}.tar" + i=$((i + 1)) + done + + - name: Start CI dependency stack + run: docker compose -f docker-compose.ci.yml up -d --no-build - name: Wait for Postgres run: | diff --git a/docs/local/github-actions.md b/docs/local/github-actions.md index dead74a97..592047b85 100644 --- a/docs/local/github-actions.md +++ b/docs/local/github-actions.md @@ -16,6 +16,8 @@ Required checks for PRs into `hyku-oob` should be those jobs (lint, security, an CI uses Actions + Docker **only on the runner**. Local macOS setup under `docs/local/` stays no-Docker. +Caching (to keep runs shorter): Bundler (`ruby/setup-ruby` bundler-cache), Yarn, RuboCop result cache, ruby-advisory-db for bundler-audit, apt wait-tools, and Docker service images from `docker-compose.ci.yml` (saved by shard 0, restored by all shards). + Making the full suite green is a follow-up; the workflow is wired first so failures are visible. ## PR label checker (`Verify` / “PR has required labels”) From def0a2212fc66c4fd6c0c1eeb7fa458a1ace5bc7 Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 10:42:38 -0400 Subject: [PATCH 06/43] Install libvips (and ImageMagick) on CI test runners. RSpec was failing to load rails_helper because ruby-vips/riiif needs libvips.so.42, which is not present on the stock ubuntu-latest image. --- .github/workflows/ci.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68551fc00..f6a197e9a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -155,8 +155,12 @@ jobs: - name: Cache apt packages uses: awalsh128/cache-apt-pkgs-action@v1.5.0 with: - packages: netcat-openbsd zip curl postgresql-client redis-tools - version: "1" + # libvips: required by ruby-vips / riiif at boot (LoadError without it) + # imagemagick: Hyrax derivatives / feature specs + packages: >- + netcat-openbsd zip curl postgresql-client redis-tools + libvips42 imagemagick + version: "2" - name: Wait for Docker image pull if: steps.cache-docker.outputs.cache-hit != 'true' From 1996f9242d4907742738a09d3505c6641bde5ea6 Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 10:44:30 -0400 Subject: [PATCH 07/43] Fix Gemfile gem order for Bundler/OrderedGems. Keep brakeman and bundler-audit alphabetically placed so RuboCop passes. --- Gemfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 827752e22..019fc9a29 100644 --- a/Gemfile +++ b/Gemfile @@ -26,11 +26,11 @@ gem 'blacklight_advanced_search' gem 'blacklight_oai_provider', '~> 7.0' gem 'blacklight_range_limit', '~> 8.5' gem 'bolognese', '>= 1.9.10' -gem 'brakeman', group: %i[development test] gem 'bootstrap', '~> 4.6' -gem 'bundler-audit', group: %i[development test] gem 'bootstrap-datepicker-rails' +gem 'brakeman', group: %i[development test] gem 'bulkrax', '~> 9.5' +gem 'bundler-audit', group: %i[development test] gem 'byebug', group: %i[development test] gem 'capybara', group: %i[test] gem 'capybara-screenshot', '~> 1.0', group: %i[test] From dd79d474016890f8183cb43a04d6085d883073e5 Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 10:47:33 -0400 Subject: [PATCH 08/43] Baseline Hyku lockfile advisories in .bundler-audit.yml. Ignore current Gemfile.lock CVEs/GHSAs so CI stays green without diverging from upstream Hyku versions; new advisories still fail. --- .bundler-audit.yml | 112 +++++++++++++++++++++++++++++++++++ docs/local/github-actions.md | 2 +- 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 .bundler-audit.yml diff --git a/.bundler-audit.yml b/.bundler-audit.yml new file mode 100644 index 000000000..fa2cc991c --- /dev/null +++ b/.bundler-audit.yml @@ -0,0 +1,112 @@ +# Hyku OOB baseline: advisories present in the current Gemfile.lock. +# Do not bump gems solely to clear these — stay aligned with hyku/main. +# New advisories (not listed here) should still fail CI. +ignore: + + # addressable (2.8.1) — Hyku lockfile; ignore until upstream upgrades + - 2026-35611 + - h27x-rffw-24p4 + - CVE-2026-35611 + + # carrierwave (1.3.4) — Hyku lockfile; ignore until upstream upgrades + - 2023-49090 + - gxhx-g4fq-49hj + - CVE-2023-49090 + - 2024-29034 + - vfmv-jfc5-pjjw + - CVE-2024-29034 + - 2026-44587 + - 7g26-2qgj-chfg + - CVE-2026-44587 + + # concurrent-ruby (1.3.4) — Hyku lockfile; ignore until upstream upgrades + - 2026-54904 + - h8w8-99g7-qmvj + - CVE-2026-54904 + - 2026-54905 + - wv3x-4vxv-whpp + - CVE-2026-54905 + - 2026-54906 + - 6wx8-w4f5-wwcr + - CVE-2026-54906 + + # crass (1.0.6) — Hyku lockfile; ignore until upstream upgrades + - 6jxj-px6v-747w + - GHSA-6jxj-px6v-747w + - 6wmf-3r64-vcwv + - GHSA-6wmf-3r64-vcwv + - 8vfg-2r28-hvhj + - GHSA-8vfg-2r28-hvhj + - wwpr-jff3-395c + - GHSA-wwpr-jff3-395c + + # excon (0.71.1) — Hyku lockfile; ignore until upstream upgrades + - 2026-54171 + - 48rx-c7pg-q66r + - CVE-2026-54171 + + # json (2.19.2) — Hyku lockfile; ignore until upstream upgrades + - 2026-54696 + - x2f5-4prf-w687 + - CVE-2026-54696 + + # loofah (2.25.1) — Hyku lockfile; ignore until upstream upgrades + - 5qhf-9phg-95m2 + - GHSA-5qhf-9phg-95m2 + - 8whx-365g-h9vv + - GHSA-8whx-365g-h9vv + - 9wjq-cp2p-hrgf + - GHSA-9wjq-cp2p-hrgf + + # oauth (1.1.2) — Hyku lockfile; ignore until upstream upgrades + - prq8-7wvh-44qh + - GHSA-prq8-7wvh-44qh + + # oauth2 (1.4.11) — Hyku lockfile; ignore until upstream upgrades + - pp92-crg2-gfv9 + - GHSA-pp92-crg2-gfv9 + + # rails-html-sanitizer (1.7.0) — Hyku lockfile; ignore until upstream upgrades + - cj75-f6xr-r4g7 + - GHSA-cj75-f6xr-r4g7 + + # secure_headers (7.1.0) — Hyku lockfile; ignore until upstream upgrades + - 2026-54163 + - rqq5-2gf9-4w4q + - CVE-2026-54163 + + # sinatra (2.2.4) — Hyku lockfile; ignore until upstream upgrades + - 2024-21510 + - hxx2-7vcw-mqr3 + - CVE-2024-21510 + - 2025-61921 + - mr3q-g2mv-mr4q + - CVE-2025-61921 + + # view_component (2.74.1) — Hyku lockfile; ignore until upstream upgrades + - 2024-21636 + - wf2x-8w6j-qw37 + - CVE-2024-21636 + + # websocket-driver (0.8.0) — Hyku lockfile; ignore until upstream upgrades + - 2026-54463 + - ghhp-3qvg-889p + - CVE-2026-54463 + - 2026-54464 + - 33ph-fccm-39pj + - CVE-2026-54464 + - 2026-54465 + - 8j3g-f24p-4mpw + - CVE-2026-54465 + - 2x63-gw47-w4mm + - GHSA-2x63-gw47-w4mm + + # yard (0.9.42) — Hyku lockfile; ignore until upstream upgrades + - 2026-49342 + - pxcc-8665-phx8 + - CVE-2026-49342 + + # zlib (2.1.1) — Hyku lockfile; ignore until upstream upgrades + - 2026-27820 + - g857-hhfv-j68w + - CVE-2026-27820 diff --git a/docs/local/github-actions.md b/docs/local/github-actions.md index 592047b85..1442c145e 100644 --- a/docs/local/github-actions.md +++ b/docs/local/github-actions.md @@ -9,7 +9,7 @@ PRs and pushes to **`hyku-oob`** run [`.github/workflows/ci.yml`](../../.github/ | Job | What it does | |-----|----------------| | **RuboCop** | `bundle exec rubocop` (no Solr/Fedora) | -| **Brakeman and bundler-audit** | Security scans (no Solr/Fedora). Known Hyku OOB findings are baselined in [`config/brakeman.ignore`](../../config/brakeman.ignore); **new** Medium/High warnings still fail CI. | +| **Brakeman and bundler-audit** | Security scans (no Solr/Fedora). Known Hyku OOB findings are baselined in [`config/brakeman.ignore`](../../config/brakeman.ignore) and [`.bundler-audit.yml`](../../.bundler-audit.yml); **new** Medium/High Brakeman warnings or unignored advisories still fail CI. | | **RSpec (shards 0–5)** | Six parallel jobs; each starts Postgres, Redis, Solr, Fedora (and Chrome) via [`docker-compose.ci.yml`](../../docker-compose.ci.yml), then runs ~1/6 of the spec files | Required checks for PRs into `hyku-oob` should be those jobs (lint, security, and all six RSpec shards). No release labels are required for these checks. From 12fc10bc5ada2d324e09ac2a7ac6e42f3cd9d0c1 Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 10:48:20 -0400 Subject: [PATCH 09/43] Tidy .bundler-audit.yml to CVE/GHSA IDs only. Drop malformed bare id fragments from the generated ignore list. --- .bundler-audit.yml | 65 +++++++++++++--------------------------------- 1 file changed, 18 insertions(+), 47 deletions(-) diff --git a/.bundler-audit.yml b/.bundler-audit.yml index fa2cc991c..3853516af 100644 --- a/.bundler-audit.yml +++ b/.bundler-audit.yml @@ -4,109 +4,80 @@ ignore: # addressable (2.8.1) — Hyku lockfile; ignore until upstream upgrades - - 2026-35611 - - h27x-rffw-24p4 - CVE-2026-35611 + - GHSA-h27x-rffw-24p4 # carrierwave (1.3.4) — Hyku lockfile; ignore until upstream upgrades - - 2023-49090 - - gxhx-g4fq-49hj - CVE-2023-49090 - - 2024-29034 - - vfmv-jfc5-pjjw - CVE-2024-29034 - - 2026-44587 - - 7g26-2qgj-chfg - CVE-2026-44587 + - GHSA-7g26-2qgj-chfg + - GHSA-gxhx-g4fq-49hj + - GHSA-vfmv-jfc5-pjjw # concurrent-ruby (1.3.4) — Hyku lockfile; ignore until upstream upgrades - - 2026-54904 - - h8w8-99g7-qmvj - CVE-2026-54904 - - 2026-54905 - - wv3x-4vxv-whpp - CVE-2026-54905 - - 2026-54906 - - 6wx8-w4f5-wwcr - CVE-2026-54906 + - GHSA-6wx8-w4f5-wwcr + - GHSA-h8w8-99g7-qmvj + - GHSA-wv3x-4vxv-whpp # crass (1.0.6) — Hyku lockfile; ignore until upstream upgrades - - 6jxj-px6v-747w - GHSA-6jxj-px6v-747w - - 6wmf-3r64-vcwv - GHSA-6wmf-3r64-vcwv - - 8vfg-2r28-hvhj - GHSA-8vfg-2r28-hvhj - - wwpr-jff3-395c - GHSA-wwpr-jff3-395c # excon (0.71.1) — Hyku lockfile; ignore until upstream upgrades - - 2026-54171 - - 48rx-c7pg-q66r - CVE-2026-54171 + - GHSA-48rx-c7pg-q66r # json (2.19.2) — Hyku lockfile; ignore until upstream upgrades - - 2026-54696 - - x2f5-4prf-w687 - CVE-2026-54696 + - GHSA-x2f5-4prf-w687 # loofah (2.25.1) — Hyku lockfile; ignore until upstream upgrades - - 5qhf-9phg-95m2 - GHSA-5qhf-9phg-95m2 - - 8whx-365g-h9vv - GHSA-8whx-365g-h9vv - - 9wjq-cp2p-hrgf - GHSA-9wjq-cp2p-hrgf # oauth (1.1.2) — Hyku lockfile; ignore until upstream upgrades - - prq8-7wvh-44qh - GHSA-prq8-7wvh-44qh # oauth2 (1.4.11) — Hyku lockfile; ignore until upstream upgrades - - pp92-crg2-gfv9 - GHSA-pp92-crg2-gfv9 # rails-html-sanitizer (1.7.0) — Hyku lockfile; ignore until upstream upgrades - - cj75-f6xr-r4g7 - GHSA-cj75-f6xr-r4g7 # secure_headers (7.1.0) — Hyku lockfile; ignore until upstream upgrades - - 2026-54163 - - rqq5-2gf9-4w4q - CVE-2026-54163 + - GHSA-rqq5-2gf9-4w4q # sinatra (2.2.4) — Hyku lockfile; ignore until upstream upgrades - - 2024-21510 - - hxx2-7vcw-mqr3 - CVE-2024-21510 - - 2025-61921 - - mr3q-g2mv-mr4q - CVE-2025-61921 + - GHSA-hxx2-7vcw-mqr3 + - GHSA-mr3q-g2mv-mr4q # view_component (2.74.1) — Hyku lockfile; ignore until upstream upgrades - - 2024-21636 - - wf2x-8w6j-qw37 - CVE-2024-21636 + - GHSA-wf2x-8w6j-qw37 # websocket-driver (0.8.0) — Hyku lockfile; ignore until upstream upgrades - - 2026-54463 - - ghhp-3qvg-889p - CVE-2026-54463 - - 2026-54464 - - 33ph-fccm-39pj - CVE-2026-54464 - - 2026-54465 - - 8j3g-f24p-4mpw - CVE-2026-54465 - - 2x63-gw47-w4mm - GHSA-2x63-gw47-w4mm + - GHSA-33ph-fccm-39pj + - GHSA-8j3g-f24p-4mpw + - GHSA-ghhp-3qvg-889p # yard (0.9.42) — Hyku lockfile; ignore until upstream upgrades - - 2026-49342 - - pxcc-8665-phx8 - CVE-2026-49342 + - GHSA-pxcc-8665-phx8 # zlib (2.1.1) — Hyku lockfile; ignore until upstream upgrades - - 2026-27820 - - g857-hhfv-j68w - CVE-2026-27820 + - GHSA-g857-hhfv-j68w From 03106abe22726b05920243d37589a51f38f56fd2 Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 10:49:06 -0400 Subject: [PATCH 10/43] Pull Docker images in one CI step instead of background wait. GitHub Actions starts a new shell per step, so wait cannot join a PID started in an earlier step. --- .github/workflows/ci.yml | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6a197e9a..3b0872846 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,14 +131,6 @@ jobs: docker load -i "$f" done - # Overlap image pull with Ruby/Node setup when cache missed. - - name: Pull CI images (background) - if: steps.cache-docker.outputs.cache-hit != 'true' - run: | - set -euo pipefail - docker compose -f docker-compose.ci.yml pull --quiet & - echo $! > /tmp/docker-pull.pid - - uses: ruby/setup-ruby@v1 with: ruby-version: "3.3.6" @@ -162,11 +154,11 @@ jobs: libvips42 imagemagick version: "2" - - name: Wait for Docker image pull + # Must run in this step (not backgrounded earlier): each GHA step is a new shell, + # so wait cannot see a PID from a previous step. + - name: Pull CI Docker images if: steps.cache-docker.outputs.cache-hit != 'true' - run: | - set -euo pipefail - wait "$(cat /tmp/docker-pull.pid)" + run: docker compose -f docker-compose.ci.yml pull --quiet - name: Save Docker images for cache if: steps.cache-docker.outputs.cache-hit != 'true' && matrix.ci_node_index == 0 From 5a37795605ba516d964d7ea5d370a213c7bfd01b Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 10:55:25 -0400 Subject: [PATCH 11/43] Enable SolrCloud Basic Auth for CI ConfigSet upload. Unauthenticated Solr rejects trusted ConfigSet uploads, so the Hyku upload script retried until failure. Match docker-compose.yml: ZooKeeper plus solr/security.json credentials. --- .github/workflows/ci.yml | 20 +++++++++++++------- docker-compose.ci.yml | 38 ++++++++++++++++++++++++++++++++++---- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b0872846..6d9f4bc88 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,11 +37,15 @@ env: FCREPO_TEST_BASE_PATH: /test SOLR_HOST: localhost SOLR_PORT: "8983" - SOLR_URL: http://localhost:8983/solr/ + SOLR_ADMIN_USER: solr + SOLR_ADMIN_PASSWORD: SolrRocks + # Credentials match solr/security.json (user solr / SolrRocks) + SOLR_URL: http://solr:SolrRocks@localhost:8983/solr/ SOLR_COLLECTION_NAME: hydra-test SOLR_COLLECTION_TEST: hydra-test SOLR_CONFIGSET_NAME: hyku SOLR_ENABLE_CLOUD_MODE: "yes" + SOLR_ENABLE_AUTHENTICATION: "yes" CHROME_HOSTNAME: localhost # Capybara app_host seen from the Chrome container (see docker-compose.ci.yml extra_hosts) WEB_HOST: host.docker.internal @@ -202,13 +206,14 @@ jobs: - name: Wait for Solr run: | for i in $(seq 1 60); do - if curl -sf http://localhost:8983/solr/admin/info/system >/dev/null; then + if curl -sf -u "${SOLR_ADMIN_USER}:${SOLR_ADMIN_PASSWORD}" \ + http://localhost:8983/solr/admin/info/system >/dev/null; then exit 0 fi sleep 3 done echo "Solr did not become ready" - docker compose -f docker-compose.ci.yml logs solr + docker compose -f docker-compose.ci.yml logs solr zoo exit 1 - name: Wait for Fedora @@ -227,11 +232,12 @@ jobs: - name: Prepare database run: bundle exec rails db:prepare - - name: Upload Solr configset (best effort) + - name: Upload Solr configset run: | - chmod +x bin/solrcloud-upload-configset.sh bin/solrcloud-assign-configset.sh || true - ./bin/solrcloud-upload-configset.sh solr/conf || true - SOLR_COLLECTION_NAME=hydra-test ./bin/solrcloud-assign-configset.sh || true + set -euo pipefail + chmod +x bin/solrcloud-upload-configset.sh bin/solrcloud-assign-configset.sh + ./bin/solrcloud-upload-configset.sh solr/conf + SOLR_COLLECTION_NAME=hydra-test ./bin/solrcloud-assign-configset.sh - name: Run RSpec shard run: | diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml index 06a14cd89..65acdbdc1 100644 --- a/docker-compose.ci.yml +++ b/docker-compose.ci.yml @@ -36,18 +36,48 @@ services: ports: - "8080:8080" - # SolrCloud with embedded ZooKeeper (same approach as develop CircleCI: solr -cloud) + # SolrCloud needs ZooKeeper + Basic Auth so bin/solrcloud-upload-configset.sh + # can upload a trusted ConfigSet (same pattern as docker-compose.yml). + zoo: + image: zookeeper:3.8 + ports: + - "2181:2181" + healthcheck: + test: ["CMD-SHELL", "echo ruok | nc -w 2 localhost 2181 | grep imok"] + interval: 10s + timeout: 5s + retries: 15 + solr: image: solr:8.11.2 - command: ["solr-foreground", "-c"] + depends_on: + zoo: + condition: service_healthy + environment: + ZK_HOST: zoo:2181 + SOLR_ADMIN_USER: solr + SOLR_ADMIN_PASSWORD: SolrRocks ports: - "8983:8983" + volumes: + - ./solr/security.json:/opt/security.json:ro + user: root + command: + - bash + - -c + - | + set -e + mkdir -p /var/solr/data + cp /opt/security.json /var/solr/data/security.json + chown -R 8983:8983 /var/solr + ./bin/solr zk cp file:/var/solr/data/security.json zk:/security.json -z zoo:2181 + exec runuser -u solr -- solr-foreground -c -z zoo:2181 healthcheck: - test: ["CMD-SHELL", "curl -sf http://localhost:8983/solr/admin/info/system || exit 1"] + test: ["CMD-SHELL", "curl -sf -u solr:SolrRocks http://localhost:8983/solr/admin/info/system || exit 1"] interval: 10s timeout: 5s retries: 30 - start_period: 30s + start_period: 40s chrome: image: selenium/standalone-chromium:latest From 743eff2f3edc13be38d04a44df09702bcc2d7b5c Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 11:02:06 -0400 Subject: [PATCH 12/43] Fix ZooKeeper healthcheck for CI SolrCloud stack. Use Hyku's zookeeper:3.6.2 settings and zkServer.sh status instead of an ruok/nc probe that left the zoo container unhealthy. --- docker-compose.ci.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml index 65acdbdc1..d76beb5ad 100644 --- a/docker-compose.ci.yml +++ b/docker-compose.ci.yml @@ -39,14 +39,20 @@ services: # SolrCloud needs ZooKeeper + Basic Auth so bin/solrcloud-upload-configset.sh # can upload a trusted ConfigSet (same pattern as docker-compose.yml). zoo: - image: zookeeper:3.8 + image: zookeeper:3.6.2 + environment: + ZOO_MY_ID: "1" + ZOO_SERVERS: server.1=zoo:2888:3888;2181 + # 3.5+ disables most 4lw commands unless whitelisted (needed for ruok healthcheck) + ZOO_4LW_COMMANDS_WHITELIST: mntr,srvr,ruok,conf ports: - "2181:2181" healthcheck: - test: ["CMD-SHELL", "echo ruok | nc -w 2 localhost 2181 | grep imok"] + test: ["CMD", "zkServer.sh", "status"] interval: 10s - timeout: 5s + timeout: 10s retries: 15 + start_period: 20s solr: image: solr:8.11.2 From 5dad85d236fb525eac5f2b6a5c01d7eee158951d Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 11:05:35 -0400 Subject: [PATCH 13/43] Remove trivial smoke-test marker from local docs index. --- docs/local/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/local/README.md b/docs/local/README.md index 96c1819aa..8c38e733b 100644 --- a/docs/local/README.md +++ b/docs/local/README.md @@ -20,5 +20,3 @@ 1. [install.md](./install.md) → [environment.md](./environment.md) 2. [run-the-app.md](./run-the-app.md) → http://localhost:3000 3. Tests only when you need them: [run-tests.md](./run-tests.md) - - From b7582cd0c84ad73e68a2596bc4f457cfdc3db67a Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 11:11:52 -0400 Subject: [PATCH 14/43] Point CI file cache at the workspace, not /app. Host-based RSpec was failing with Permission denied mkdir /app because HYKU_CACHE_ROOT defaults to /app/samvera/file_cache. Drop IN_DOCKER and verify ImageMagick is on PATH for MiniMagick. --- .github/workflows/ci.yml | 14 +++++++++++--- docs/local/github-actions.md | 2 ++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d9f4bc88..0e4cf32de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,9 @@ env: BUNDLE_RETRY: 3 RAILS_ENV: test RACK_ENV: test - IN_DOCKER: "true" + # Ruby runs on the GHA host (not inside the Hyku web image). Do not set + # IN_DOCKER — that path layout assumes /app/samvera/... + HYKU_CACHE_ROOT: ${{ github.workspace }}/tmp/hyku_file_cache DB_ADAPTER: postgresql DB_HOST: localhost DB_PORT: "5432" @@ -152,11 +154,17 @@ jobs: uses: awalsh128/cache-apt-pkgs-action@v1.5.0 with: # libvips: required by ruby-vips / riiif at boot (LoadError without it) - # imagemagick: Hyrax derivatives / feature specs + # imagemagick: MiniMagick / CarrierWave derivatives packages: >- netcat-openbsd zip curl postgresql-client redis-tools libvips42 imagemagick - version: "2" + version: "3" + + - name: Ensure ImageMagick and cache dir + run: | + set -euo pipefail + mkdir -p "${HYKU_CACHE_ROOT}" + convert -version # Must run in this step (not backgrounded earlier): each GHA step is a new shell, # so wait cannot see a PID from a previous step. diff --git a/docs/local/github-actions.md b/docs/local/github-actions.md index 1442c145e..565674001 100644 --- a/docs/local/github-actions.md +++ b/docs/local/github-actions.md @@ -18,6 +18,8 @@ CI uses Actions + Docker **only on the runner**. Local macOS setup under `docs/l Caching (to keep runs shorter): Bundler (`ruby/setup-ruby` bundler-cache), Yarn, RuboCop result cache, ruby-advisory-db for bundler-audit, apt wait-tools, and Docker service images from `docker-compose.ci.yml` (saved by shard 0, restored by all shards). +Specs run Ruby on the Actions host (not inside the Hyku web container). CI sets `HYKU_CACHE_ROOT` under the workspace so the app does not try to write `/app/samvera/file_cache`. + Making the full suite green is a follow-up; the workflow is wired first so failures are visible. ## PR label checker (`Verify` / “PR has required labels”) From 36cae65d375be25cbdd4a738ff62f59aae250822 Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 11:14:32 -0400 Subject: [PATCH 15/43] Install CI apt packages without a shared cache race. Six RSpec shards contended on the same cache-apt-pkgs key; use a plain apt-get install instead so system deps stay reliable. --- .github/workflows/ci.yml | 18 +++++++----------- docs/local/github-actions.md | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e4cf32de..69cad547b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -150,21 +150,17 @@ jobs: - name: Install JS dependencies run: yarn install --frozen-lockfile || yarn install - - name: Cache apt packages - uses: awalsh128/cache-apt-pkgs-action@v1.5.0 - with: - # libvips: required by ruby-vips / riiif at boot (LoadError without it) - # imagemagick: MiniMagick / CarrierWave derivatives - packages: >- - netcat-openbsd zip curl postgresql-client redis-tools - libvips42 imagemagick - version: "3" - - - name: Ensure ImageMagick and cache dir + - name: Install system packages run: | set -euo pipefail + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + netcat-openbsd zip curl postgresql-client redis-tools \ + libvips42 imagemagick mkdir -p "${HYKU_CACHE_ROOT}" convert -version + # libvips is required by ruby-vips / riiif at boot + ldconfig -p | grep -q libvips # Must run in this step (not backgrounded earlier): each GHA step is a new shell, # so wait cannot see a PID from a previous step. diff --git a/docs/local/github-actions.md b/docs/local/github-actions.md index 565674001..62e9eb379 100644 --- a/docs/local/github-actions.md +++ b/docs/local/github-actions.md @@ -16,7 +16,7 @@ Required checks for PRs into `hyku-oob` should be those jobs (lint, security, an CI uses Actions + Docker **only on the runner**. Local macOS setup under `docs/local/` stays no-Docker. -Caching (to keep runs shorter): Bundler (`ruby/setup-ruby` bundler-cache), Yarn, RuboCop result cache, ruby-advisory-db for bundler-audit, apt wait-tools, and Docker service images from `docker-compose.ci.yml` (saved by shard 0, restored by all shards). +Caching (to keep runs shorter): Bundler (`ruby/setup-ruby` bundler-cache), Yarn, RuboCop result cache, ruby-advisory-db for bundler-audit, and Docker service images from `docker-compose.ci.yml` (saved by shard 0, restored by all shards). Apt packages are installed directly (not cached) so six parallel shards do not race the same Actions cache key. Specs run Ruby on the Actions host (not inside the Hyku web container). CI sets `HYKU_CACHE_ROOT` under the workspace so the app does not try to write `/app/samvera/file_cache`. From 30577524f79106d0ff029dd396c4f797278a7fc3 Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 11:22:45 -0400 Subject: [PATCH 16/43] Run db:extensions after db:prepare in CI. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Compose pre-creates hyku_test, so db:create never runs and Hyku's shared_extensions / uuid-ossp setup was skipped — Apartment tenants then failed with uuid_generate_v4() missing. --- .github/workflows/ci.yml | 9 ++++++++- docs/local/github-actions.md | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69cad547b..ef3cb300e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -234,7 +234,14 @@ jobs: exit 1 - name: Prepare database - run: bundle exec rails db:prepare + run: | + set -euo pipefail + # Compose already creates hyku_test via POSTGRES_DB, so db:prepare skips + # db:create — and Hyku's db:extensions hook only runs on db:create / + # db:test:purge. Without shared_extensions (uuid-ossp), Apartment tenant + # creation fails with uuid_generate_v4() does not exist. + bundle exec rails db:prepare + bundle exec rails db:extensions - name: Upload Solr configset run: | diff --git a/docs/local/github-actions.md b/docs/local/github-actions.md index 62e9eb379..ee2e77f6d 100644 --- a/docs/local/github-actions.md +++ b/docs/local/github-actions.md @@ -18,7 +18,7 @@ CI uses Actions + Docker **only on the runner**. Local macOS setup under `docs/l Caching (to keep runs shorter): Bundler (`ruby/setup-ruby` bundler-cache), Yarn, RuboCop result cache, ruby-advisory-db for bundler-audit, and Docker service images from `docker-compose.ci.yml` (saved by shard 0, restored by all shards). Apt packages are installed directly (not cached) so six parallel shards do not race the same Actions cache key. -Specs run Ruby on the Actions host (not inside the Hyku web container). CI sets `HYKU_CACHE_ROOT` under the workspace so the app does not try to write `/app/samvera/file_cache`. +Specs run Ruby on the Actions host (not inside the Hyku web container). CI sets `HYKU_CACHE_ROOT` under the workspace so the app does not try to write `/app/samvera/file_cache`. After `db:prepare`, CI runs `db:extensions` so `shared_extensions` / `uuid-ossp` exist for Apartment tenants (Compose pre-creates the DB, which skips Hyku’s `db:create` hook). Making the full suite green is a follow-up; the workflow is wired first so failures are visible. From 5f79b77c1e6597d8fb431f913466b2773c185248 Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 11:38:24 -0400 Subject: [PATCH 17/43] Align CI DB and env with Hyku Docker test setup. Install Postgres extensions via db:create before schema:load so uuid-ossp lives in shared_extensions for Apartment tenants, and set HYKU_RESTRICT_CREATE_AND_DESTROY_PERMISSIONS (from .env) so collection reader abilities match Groups with Roles expectations. --- .github/workflows/ci.yml | 22 ++++++++++++++++------ docs/local/github-actions.md | 4 +--- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef3cb300e..079b5d549 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,6 +57,14 @@ env: SECRET_KEY_BASE: ci-secret-key-base-not-for-production DISABLE_REDIS_CLUSTER: "true" RUBOCOP_CACHE_ROOT: ${{ github.workspace }}/tmp/rubocop_cache + # Parity with Hyku Docker CI (env_file: .env). Without RESTRICT, registered + # users get everyone_can_create_curation_concerns and collection_reader can + # :create — those ability/feature specs then fail. + HYKU_RESTRICT_CREATE_AND_DESTROY_PERMISSIONS: "true" + VALKYRIE_ID_TYPE: string + HYRAX_VALKYRIE: "true" + HYRAX_FLEXIBLE: "false" + DATABASE_CLEANER_ALLOW_REMOTE_DATABASE_URL: "true" jobs: lint: @@ -236,12 +244,14 @@ jobs: - name: Prepare database run: | set -euo pipefail - # Compose already creates hyku_test via POSTGRES_DB, so db:prepare skips - # db:create — and Hyku's db:extensions hook only runs on db:create / - # db:test:purge. Without shared_extensions (uuid-ossp), Apartment tenant - # creation fails with uuid_generate_v4() does not exist. - bundle exec rails db:prepare - bundle exec rails db:extensions + # Match Hyku Docker CI (notch8/actions setup_db_cmd): + # db:create db:schema:load db:migrate + # db:create's enhance hook installs uuid-ossp into shared_extensions + # *before* schema.rb's enable_extension can put it in public. If + # schema loads first, CREATE EXTENSION IF NOT EXISTS is a no-op and + # Apartment tenant DDL (search_path: tenant,shared_extensions) cannot + # find uuid_generate_v4(). + bundle exec rails db:create db:schema:load db:migrate - name: Upload Solr configset run: | diff --git a/docs/local/github-actions.md b/docs/local/github-actions.md index ee2e77f6d..f946c2698 100644 --- a/docs/local/github-actions.md +++ b/docs/local/github-actions.md @@ -18,9 +18,7 @@ CI uses Actions + Docker **only on the runner**. Local macOS setup under `docs/l Caching (to keep runs shorter): Bundler (`ruby/setup-ruby` bundler-cache), Yarn, RuboCop result cache, ruby-advisory-db for bundler-audit, and Docker service images from `docker-compose.ci.yml` (saved by shard 0, restored by all shards). Apt packages are installed directly (not cached) so six parallel shards do not race the same Actions cache key. -Specs run Ruby on the Actions host (not inside the Hyku web container). CI sets `HYKU_CACHE_ROOT` under the workspace so the app does not try to write `/app/samvera/file_cache`. After `db:prepare`, CI runs `db:extensions` so `shared_extensions` / `uuid-ossp` exist for Apartment tenants (Compose pre-creates the DB, which skips Hyku’s `db:create` hook). - -Making the full suite green is a follow-up; the workflow is wired first so failures are visible. +Specs run Ruby on the Actions host (not inside the Hyku web container). CI sets env to match Hyku Docker’s `.env` where it matters for specs — especially `HYKU_RESTRICT_CREATE_AND_DESTROY_PERMISSIONS` (Groups with Roles) and `HYKU_CACHE_ROOT` under the workspace (not `/app/...`). Database setup matches Hyku CI: `db:create db:schema:load db:migrate` so `shared_extensions` / `uuid-ossp` exist before schema load (required for Apartment tenants). ## PR label checker (`Verify` / “PR has required labels”) From 39c8efabb3fcf998a8984d1c0022ba00bb69c0be Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 11:50:09 -0400 Subject: [PATCH 18/43] Keep SOLR_HOST=solr for RSpec; use localhost only in setup scripts. CreateSolrCollectionJob#solr_url specs stub SOLR_URL away and expect the Hyku default host. Global SOLR_HOST=localhost made those assertions fail. --- .github/workflows/ci.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 079b5d549..f31d3b2a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,10 @@ env: FCREPO_TEST_PORT: "8080" FCREPO_BASE_PATH: /test FCREPO_TEST_BASE_PATH: /test - SOLR_HOST: localhost + # Keep Hyku default "solr" for Ruby/RSpec (CreateSolrCollectionJob#solr_url + # specs expect that host when SOLR_URL is unset). Shell scripts that talk to + # Solr from the GHA host override SOLR_HOST=localhost in their step. + SOLR_HOST: solr SOLR_PORT: "8983" SOLR_ADMIN_USER: solr SOLR_ADMIN_PASSWORD: SolrRocks @@ -254,6 +257,9 @@ jobs: bundle exec rails db:create db:schema:load db:migrate - name: Upload Solr configset + env: + # Scripts run on the GHA host; compose service hostname "solr" is not resolvable here. + SOLR_HOST: localhost run: | set -euo pipefail chmod +x bin/solrcloud-upload-configset.sh bin/solrcloud-assign-configset.sh From 6b1e50c32f907b341f96e9af87278452bb8e13d6 Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:02:57 -0400 Subject: [PATCH 19/43] Use good_job in CI and save site themes before redirect. Account#find_job was hitting Sidekiq Redis with thread_safe: true and raising ArgumentError; Hyku Docker uses HYRAX_ACTIVE_JOB_QUEUE=good_job. Theme form updates now run before the success redirect so Site theme attributes persist. --- .github/workflows/ci.yml | 4 ++++ app/controllers/sites_controller.rb | 10 +++++++--- docs/local/github-actions.md | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f31d3b2a0..13433928b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,6 +67,10 @@ env: VALKYRIE_ID_TYPE: string HYRAX_VALKYRIE: "true" HYRAX_FLEXIBLE: "false" + # Hyku Docker loads this from .env. Default "sidekiq" makes Account#find_job + # scan Redis with Sidekiq client options that include thread_safe: true, which + # the redis gem rejects (ArgumentError: unknown keyword: :thread_safe). + HYRAX_ACTIVE_JOB_QUEUE: good_job DATABASE_CLEANER_ALLOW_REMOTE_DATABASE_URL: "true" jobs: diff --git a/app/controllers/sites_controller.rb b/app/controllers/sites_controller.rb index 3379ecda9..0c5c95937 100644 --- a/app/controllers/sites_controller.rb +++ b/app/controllers/sites_controller.rb @@ -7,7 +7,13 @@ class SitesController < ApplicationController def update # FIXME: Pull these strings out to i18n locale - if @site.update(update_params) + # Apply theme params before redirect. Updating after redirect_to still runs + # in MRI Rails, but success was announced even when themes were never saved + # (e.g. empty update_params path). Theme form posts to this action via + # site_path; image-removal buttons also post here. + themes_ok = params[:site] ? @site.update(site_theme_params) : true + + if themes_ok && @site.update(update_params) # If updating work or collection default image, works and/or collections should be reindexed # as they are when the default image is added. See AppearancesControllerDecorator#update @@ -26,8 +32,6 @@ def update else redirect_to hyrax.admin_appearance_path, flash: { error: 'Updating the appearance was unsuccessful.' } end - - @site.update(site_theme_params) if params[:site] end private diff --git a/docs/local/github-actions.md b/docs/local/github-actions.md index f946c2698..37cc388fb 100644 --- a/docs/local/github-actions.md +++ b/docs/local/github-actions.md @@ -18,7 +18,7 @@ CI uses Actions + Docker **only on the runner**. Local macOS setup under `docs/l Caching (to keep runs shorter): Bundler (`ruby/setup-ruby` bundler-cache), Yarn, RuboCop result cache, ruby-advisory-db for bundler-audit, and Docker service images from `docker-compose.ci.yml` (saved by shard 0, restored by all shards). Apt packages are installed directly (not cached) so six parallel shards do not race the same Actions cache key. -Specs run Ruby on the Actions host (not inside the Hyku web container). CI sets env to match Hyku Docker’s `.env` where it matters for specs — especially `HYKU_RESTRICT_CREATE_AND_DESTROY_PERMISSIONS` (Groups with Roles) and `HYKU_CACHE_ROOT` under the workspace (not `/app/...`). Database setup matches Hyku CI: `db:create db:schema:load db:migrate` so `shared_extensions` / `uuid-ossp` exist before schema load (required for Apartment tenants). +Specs run Ruby on the Actions host (not inside the Hyku web container). CI sets env to match Hyku Docker’s `.env` where it matters for specs — especially `HYKU_RESTRICT_CREATE_AND_DESTROY_PERMISSIONS` (Groups with Roles), `HYRAX_ACTIVE_JOB_QUEUE=good_job` (avoids Sidekiq Redis `thread_safe` errors in `Account#find_job`), and `HYKU_CACHE_ROOT` under the workspace (not `/app/...`). Database setup matches Hyku CI: `db:create db:schema:load db:migrate` so `shared_extensions` / `uuid-ossp` exist before schema load (required for Apartment tenants). ## PR label checker (`Verify` / “PR has required labels”) From c0a579a8afd1e116330b75eb985a4dd65c8d2f80 Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:04:56 -0400 Subject: [PATCH 20/43] Tolerate empty Docker image cache hits in CI. A cache hit with no *.tar files left the glob literal and failed docker load. Fall back to pull, bump the cache key, and refuse to save an empty image dir. --- .github/workflows/ci.yml | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13433928b..9ebe75527 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -139,18 +139,29 @@ jobs: uses: actions/cache@v4 with: path: /tmp/ci-docker-images - key: ci-docker-${{ runner.os }}-${{ hashFiles('docker-compose.ci.yml') }} + # v2: invalidate empty caches from earlier runs that saved the dir + # before any *.tar files existed. + key: ci-docker-v2-${{ runner.os }}-${{ hashFiles('docker-compose.ci.yml') }} # Only shard 0 writes the cache so parallel shards cannot race an empty save. lookup-only: ${{ matrix.ci_node_index != 0 }} - name: Load cached Docker images + id: load-docker if: steps.cache-docker.outputs.cache-hit == 'true' run: | set -euo pipefail - for f in /tmp/ci-docker-images/*.tar; do + shopt -s nullglob + files=(/tmp/ci-docker-images/*.tar) + if (( ${#files[@]} == 0 )); then + echo "Cache hit but no image tarballs present; will pull instead" + echo "loaded=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + for f in "${files[@]}"; do echo "Loading $f" docker load -i "$f" done + echo "loaded=true" >> "$GITHUB_OUTPUT" - uses: ruby/setup-ruby@v1 with: @@ -180,20 +191,26 @@ jobs: # Must run in this step (not backgrounded earlier): each GHA step is a new shell, # so wait cannot see a PID from a previous step. - name: Pull CI Docker images - if: steps.cache-docker.outputs.cache-hit != 'true' + if: steps.cache-docker.outputs.cache-hit != 'true' || steps.load-docker.outputs.loaded != 'true' run: docker compose -f docker-compose.ci.yml pull --quiet - name: Save Docker images for cache - if: steps.cache-docker.outputs.cache-hit != 'true' && matrix.ci_node_index == 0 + if: (steps.cache-docker.outputs.cache-hit != 'true' || steps.load-docker.outputs.loaded != 'true') && matrix.ci_node_index == 0 run: | set -euo pipefail mkdir -p /tmp/ci-docker-images + rm -f /tmp/ci-docker-images/*.tar i=0 for img in $(docker compose -f docker-compose.ci.yml config --images); do echo "Saving $img" docker save "$img" -o "/tmp/ci-docker-images/${i}.tar" i=$((i + 1)) done + if (( i == 0 )); then + echo "No images to cache" >&2 + exit 1 + fi + ls -lh /tmp/ci-docker-images/ - name: Start CI dependency stack run: docker compose -f docker-compose.ci.yml up -d --no-build From a5bd9663e4ddae578aa40f7aed680494a347c22e Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:06:25 -0400 Subject: [PATCH 21/43] Fix Docker cache: restore on all shards, save only on shard 0. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit lookup-only made shards 1–5 report cache-hit without downloading tarballs, so docker load failed on an empty directory. Split actions/cache restore/save so every shard gets the files. --- .github/workflows/ci.yml | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ebe75527..daf05b633 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -134,16 +134,15 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Cache CI Docker images + # Restore on every shard (must download tarballs). Do not use lookup-only: + # that reports cache-hit without restoring files, so shards 1–5 tried to + # docker load an empty directory while shard 0 (writer) actually restored. + - name: Restore CI Docker image cache id: cache-docker - uses: actions/cache@v4 + uses: actions/cache/restore@v4 with: path: /tmp/ci-docker-images - # v2: invalidate empty caches from earlier runs that saved the dir - # before any *.tar files existed. - key: ci-docker-v2-${{ runner.os }}-${{ hashFiles('docker-compose.ci.yml') }} - # Only shard 0 writes the cache so parallel shards cannot race an empty save. - lookup-only: ${{ matrix.ci_node_index != 0 }} + key: ci-docker-v3-${{ runner.os }}-${{ hashFiles('docker-compose.ci.yml') }} - name: Load cached Docker images id: load-docker @@ -191,11 +190,13 @@ jobs: # Must run in this step (not backgrounded earlier): each GHA step is a new shell, # so wait cannot see a PID from a previous step. - name: Pull CI Docker images + id: pull-docker if: steps.cache-docker.outputs.cache-hit != 'true' || steps.load-docker.outputs.loaded != 'true' run: docker compose -f docker-compose.ci.yml pull --quiet - - name: Save Docker images for cache - if: (steps.cache-docker.outputs.cache-hit != 'true' || steps.load-docker.outputs.loaded != 'true') && matrix.ci_node_index == 0 + - name: Pack Docker images for cache + id: pack-docker + if: steps.pull-docker.outcome == 'success' && matrix.ci_node_index == 0 run: | set -euo pipefail mkdir -p /tmp/ci-docker-images @@ -212,6 +213,13 @@ jobs: fi ls -lh /tmp/ci-docker-images/ + - name: Save CI Docker image cache + if: steps.pack-docker.outcome == 'success' && matrix.ci_node_index == 0 + uses: actions/cache/save@v4 + with: + path: /tmp/ci-docker-images + key: ci-docker-v3-${{ runner.os }}-${{ hashFiles('docker-compose.ci.yml') }} + - name: Start CI dependency stack run: docker compose -f docker-compose.ci.yml up -d --no-build From b7ea4c56c87db80a87db30f76b6bb78774a07403 Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:20:51 -0400 Subject: [PATCH 22/43] Fix CI PDF text extraction and featured collection order spec. Install poppler-utils so pdftotext matches the Hyrax base image. Give manually ordered featured collections explicit order values so the list is not left in unstable same-order / creation order. --- .github/workflows/ci.yml | 4 +++- spec/models/featured_collection_list_spec.rb | 9 +++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index daf05b633..d197a5fe2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -181,11 +181,13 @@ jobs: sudo apt-get update sudo apt-get install -y --no-install-recommends \ netcat-openbsd zip curl postgresql-client redis-tools \ - libvips42 imagemagick + libvips42 imagemagick poppler-utils mkdir -p "${HYKU_CACHE_ROOT}" convert -version # libvips is required by ruby-vips / riiif at boot ldconfig -p | grep -q libvips + # poppler-utils provides pdftotext (Hyrax base image; FileSet PDF indexing) + command -v pdftotext # Must run in this step (not backgrounded earlier): each GHA step is a new shell, # so wait cannot see a PID from a previous step. diff --git a/spec/models/featured_collection_list_spec.rb b/spec/models/featured_collection_list_spec.rb index 702cd02f5..0ff187b04 100644 --- a/spec/models/featured_collection_list_spec.rb +++ b/spec/models/featured_collection_list_spec.rb @@ -50,9 +50,14 @@ end context 'when the featured collections have been manually ordered' do - it 'is not sorted by title' do - allow(instance).to receive(:manually_ordered?).and_return(true) + before do + # Both factories default order to feature_limit (6), so without an + # explicit order the list is creation/id order — not reverse title. + FeaturedCollection.find_by!(collection_id: collection1.id).update!(order: 2) + FeaturedCollection.find_by!(collection_id: collection2.id).update!(order: 1) + end + it 'is not sorted by title' do expect(instance.featured_collections.map(&:presenter).map(&:title).flatten).to eq [collection2.title.first, collection1.title.first] end end From edfce2cde08fda0a7b8dcb511836ff2ddd744811 Mon Sep 17 00:00:00 2001 From: Janell Huyck <57018862+Janell-Huyck@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:45:16 -0400 Subject: [PATCH 23/43] Fix appearance theme form posting to singular site route. site_path(@form.site) generated /site.1 under resource :site, so theme saves were unreliable. Post to site_path, put select prompts in the options hash, harden themes.js against blank selections, and assert on Site.instance from the themes tab Save button. --- .../hyku/admin/appearance/themes.js | 13 +++++---- .../appearances/_banner_image_form.html.erb | 2 +- .../appearances/_default_images_form.html.erb | 4 +-- .../_directory_image_form.html.erb | 2 +- .../admin/appearances/_favicon_form.html.erb | 2 +- .../appearances/_logo_image_form.html.erb | 2 +- .../admin/appearances/_theme_form.html.erb | 8 +++--- spec/features/appearance_theme_spec.rb | 26 ++++++++--------- spec/features/community_theme_spec.rb | 28 +++++++++---------- .../cultural_repository_theme_spec.rb | 8 +++--- .../institutional_repository_theme_spec.rb | 12 ++++---- spec/features/show_page_theme_spec.rb | 18 ++++++------ 12 files changed, 64 insertions(+), 61 deletions(-) diff --git a/app/assets/javascripts/hyku/admin/appearance/themes.js b/app/assets/javascripts/hyku/admin/appearance/themes.js index 69051ba08..a5685d02a 100644 --- a/app/assets/javascripts/hyku/admin/appearance/themes.js +++ b/app/assets/javascripts/hyku/admin/appearance/themes.js @@ -7,7 +7,7 @@ Blacklight.onLoad(function() { var themeInfo = el.data('theme-info'); var assetPath = el.find(':selected').data('image'); - if (typeof theme !== 'undefined' && typeof themeInfo !== 'undefined') { + if (theme && themeInfo && themeInfo[theme]) { var themeData = themeInfo[theme]; $('#home-theme-notes').html(themeData.notes); themeData.banner_image === true ? $('#banner-image-notes').show() : $('#banner-image-notes').hide(); @@ -19,8 +19,9 @@ Blacklight.onLoad(function() { el.on('change', function() { theme = el.val(); themeInfo = el.data('theme-info'); - themeData = themeInfo[theme]; + themeData = themeInfo && themeInfo[theme]; assetPath = el.find(':selected').data('image'); + if (!themeData) { return; } $('#home-theme-notes').html(themeData.notes); themeData.banner_image === true ? $('#banner-image-notes').show() : $('#banner-image-notes').hide(); @@ -35,7 +36,7 @@ Blacklight.onLoad(function() { var showThemeInfo = showSelect.data('theme-info'); var showAssetPath = showSelect.find(':selected').data('image'); - if (typeof showTheme !== 'undefined' && typeof showThemeInfo !== 'undefined') { + if (showTheme && showThemeInfo && showThemeInfo[showTheme]) { var showThemeData = showThemeInfo[showTheme]; $('#show-theme-notes').html(showThemeData.notes); $('#show-wireframe').find("img").attr("src", showAssetPath).attr("alt", $('#show-wireframe').data('alt-template').replace('%{theme}', showSelect.find(':selected').text())); @@ -44,8 +45,9 @@ Blacklight.onLoad(function() { showSelect.on('change', function() { showTheme = showSelect.val(); showThemeInfo = showSelect.data('theme-info'); - showThemeData = showThemeInfo[showTheme]; + showThemeData = showThemeInfo && showThemeInfo[showTheme]; showAssetPath = showSelect.find(':selected').data('image'); + if (!showThemeData) { return; } $('#show-theme-notes').html(showThemeData.notes); $('#show-wireframe').find("img").attr("src", showAssetPath).attr("alt", $('#show-wireframe').data('alt-template').replace('%{theme}', showSelect.find(':selected').text())); @@ -56,13 +58,14 @@ Blacklight.onLoad(function() { var searchTheme = searchSelect.val(); var searchAssetPath = searchSelect.find(':selected').data('image'); - if (typeof searchTheme !== 'undefined') { + if (searchTheme) { $('#search-wireframe').find("img").attr("src", searchAssetPath).attr("alt", $('#search-wireframe').data('alt-template').replace('%{theme}', searchSelect.find(':selected').text())); } searchSelect.on('change', function() { searchTheme = searchSelect.val(); searchAssetPath = searchSelect.find(':selected').data('image'); + if (!searchTheme) { return; } $('#search-wireframe').find("img").attr("src", searchAssetPath).attr("alt", $('#search-wireframe').data('alt-template').replace('%{theme}', searchSelect.find(':selected').text())); }); diff --git a/app/views/hyrax/admin/appearances/_banner_image_form.html.erb b/app/views/hyrax/admin/appearances/_banner_image_form.html.erb index 10533191d..02b9b5676 100644 --- a/app/views/hyrax/admin/appearances/_banner_image_form.html.erb +++ b/app/views/hyrax/admin/appearances/_banner_image_form.html.erb @@ -21,7 +21,7 @@ <% if @form.banner_image? %>
diff --git a/app/views/hyrax/admin/appearances/_default_images_form.html.erb b/app/views/hyrax/admin/appearances/_default_images_form.html.erb index 97abd96a9..11663fa3d 100644 --- a/app/views/hyrax/admin/appearances/_default_images_form.html.erb +++ b/app/views/hyrax/admin/appearances/_default_images_form.html.erb @@ -16,12 +16,12 @@ <% if @form.default_collection_image? || @form.default_work_image? %>