name: Public candidate server acceptance # Builds only an exact, curated public-source commit in a disposable Compose namespace. This job # never calls the Unraid deploy controller and cannot select the production deployment action. on: workflow_dispatch: inputs: source_commit: description: Exact canonical 40-character commit SHA to export and validate required: true type: string public_api_origin: description: Bare API origin compiled into the candidate Console image required: true default: https://modelforge.example.test type: string workflow_call: inputs: source_commit: description: Exact canonical 40-character commit SHA to export and validate required: true type: string public_api_origin: description: Bare API origin compiled into the candidate Console image required: true type: string permissions: contents: read concurrency: group: public-candidate-acceptance-${{ inputs.source_commit }} cancel-in-progress: false jobs: acceptance: name: Four images and isolated clean install runs-on: ubuntu-latest timeout-minutes: 240 env: GITLEAKS_VERSION: 8.30.1 GITLEAKS_SHA256: 551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb TRIVY_VERSION: 0.74.0 TRIVY_SHA256: 2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a steps: - name: Validate immutable acceptance inputs shell: bash env: SOURCE_COMMIT: ${{ inputs.source_commit }} PUBLIC_API_ORIGIN: ${{ inputs.public_api_origin }} run: | set -euo pipefail [[ "${SOURCE_COMMIT}" =~ ^[0-9a-f]{40}$ ]] || { echo "source_commit must be an exact lowercase commit SHA" >&2; exit 2; } python3 - "${PUBLIC_API_ORIGIN}" <<'PY' import sys from urllib.parse import urlparse parsed = urlparse(sys.argv[1]) if parsed.scheme not in {"http", "https"} or not parsed.netloc: raise SystemExit("public_api_origin must be an absolute HTTP(S) origin") if parsed.path or parsed.params or parsed.query or parsed.fragment: raise SystemExit("public_api_origin must be a bare origin without a path") PY - name: Check out the exact canonical source uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ inputs.source_commit }} fetch-depth: 0 persist-credentials: false - name: Verify checkout and runner isolation toolchain shell: bash env: SOURCE_COMMIT: ${{ inputs.source_commit }} run: | set -euo pipefail [[ "$(git rev-parse HEAD)" == "${SOURCE_COMMIT}" ]] [[ -z "$(git status --porcelain)" ]] command -v python3 >/dev/null command -v node >/dev/null command -v docker >/dev/null docker version docker compose version python3 -m unittest discover -s .gitea/tests -p 'test_*.py' -v - name: Install checksum-pinned acceptance tools shell: bash run: | set -euo pipefail tool_root="$(mktemp -d /tmp/modelforge-acceptance-tools.XXXXXXXX)" trivy_archive="${tool_root}/trivy.tar.gz" curl --fail --location --show-error --retry 3 --retry-all-errors \ --output "${trivy_archive}" \ "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" printf '%s %s\n' "${TRIVY_SHA256}" "${trivy_archive}" \ | sha256sum --check --strict tar --extract --gzip --file "${trivy_archive}" \ --directory "${tool_root}" trivy chmod 0755 "${tool_root}/trivy" gitleaks_archive="${tool_root}/gitleaks.tar.gz" curl --fail --location --show-error --retry 3 --retry-all-errors \ --output "${gitleaks_archive}" \ "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" printf '%s %s\n' "${GITLEAKS_SHA256}" "${gitleaks_archive}" \ | sha256sum --check --strict tar --extract --gzip --file "${gitleaks_archive}" \ --directory "${tool_root}" gitleaks chmod 0755 "${tool_root}/gitleaks" printf 'TRIVY=%s\n' "${tool_root}/trivy" >> "${GITHUB_ENV}" printf 'GITLEAKS=%s\n' "${tool_root}/gitleaks" >> "${GITHUB_ENV}" - name: Export and validate the curated public source shell: bash env: SOURCE_COMMIT: ${{ inputs.source_commit }} run: | set -euo pipefail public_source="$(mktemp -d /tmp/modelforge-public-parent.XXXXXXXX)/candidate" reports="$(mktemp -d /tmp/modelforge-public-reports.XXXXXXXX)" node scripts/export-public-source.mjs \ --output "${public_source}" --report "${reports}/export-report.json" (cd "${public_source}" && node scripts/validate-public-source.mjs) "${GITLEAKS}" dir "${public_source}" --no-banner --redact \ --report-format json --report-path "${reports}/gitleaks.json" source_date="$(git show -s --format=%cI "${SOURCE_COMMIT}")" git -C "${public_source}" init --initial-branch=main git -C "${public_source}" config user.name "ModelForge acceptance" git -C "${public_source}" config user.email "acceptance@modelforge.invalid" git -C "${public_source}" add --all GIT_AUTHOR_DATE="${source_date}" GIT_COMMITTER_DATE="${source_date}" \ git -C "${public_source}" commit -m "Public candidate from ${SOURCE_COMMIT}" printf 'PUBLIC_SOURCE=%s\n' "${public_source}" >> "${GITHUB_ENV}" printf 'ACCEPTANCE_REPORTS=%s\n' "${reports}" >> "${GITHUB_ENV}" - name: Build, scan and clean-install the public candidate shell: bash env: PUBLIC_API_ORIGIN: ${{ inputs.public_api_origin }} run: | set -euo pipefail cd "${PUBLIC_SOURCE}" python3 scripts/rc_server_acceptance.py \ --public-api-origin "${PUBLIC_API_ORIGIN}" \ --trivy "${TRIVY}" \ --output acceptance-evidence \ --project-suffix "${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" - name: Always remove acceptance Docker resources if: always() shell: bash run: | set +e cleanup_failed=0 suffix="$(printf '%s' "${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" \ | tr '[:upper:]' '[:lower:]' | tr -cd '[:alnum:]' | tail -c 24)" project="modelforge-rc-${suffix}" for kind in container volume network; do while IFS= read -r resource; do [[ -n "${resource}" ]] || continue case "${kind}" in container) docker container rm --force "${resource}" || cleanup_failed=1 ;; volume) docker volume rm --force "${resource}" || cleanup_failed=1 ;; network) docker network rm "${resource}" || cleanup_failed=1 ;; esac done < <(docker "${kind}" ls --quiet \ --filter "label=com.docker.compose.project=${project}" 2>/dev/null) done if [[ -n "${PUBLIC_SOURCE:-}" && -d "${PUBLIC_SOURCE}/.git" ]]; then candidate_commit="$(git -C "${PUBLIC_SOURCE}" rev-parse HEAD 2>/dev/null)" version="$(tr -d '\r\n' < "${PUBLIC_SOURCE}/VERSION" 2>/dev/null)" for image in modelforge-api modelforge-web modelforge-node-agent \ modelforge-runtime-worker; do tag="${image}:${version}" revision="$(docker inspect --format \ '{{index .Config.Labels "org.opencontainers.image.revision"}}' \ "${tag}" 2>/dev/null)" if [[ -n "${candidate_commit}" && "${revision}" == "${candidate_commit}" ]]; then docker image rm --force "${tag}" || cleanup_failed=1 fi done fi leftovers="$(docker container ls --all --quiet \ --filter "label=com.docker.compose.project=${project}" 2>/dev/null)" [[ -z "${leftovers}" ]] || { echo "Acceptance containers remain after cleanup: ${leftovers}" >&2 exit 1 } [[ "${cleanup_failed}" == 0 ]] || { echo "One or more exact acceptance resources could not be removed" >&2 exit 1 } - name: Upload acceptance evidence if: always() uses: https://gitea.com/actions/gitea-upload-artifact@81f940d004763f986ba3582c007fd842dd5cb0d7 with: name: public-candidate-${{ gitea.run_id }}-${{ gitea.run_attempt }}-${{ inputs.source_commit }} path: | ${{ env.ACCEPTANCE_REPORTS }}/export-report.json ${{ env.ACCEPTANCE_REPORTS }}/gitleaks.json ${{ env.PUBLIC_SOURCE }}/PUBLIC_SOURCE_EXPORT.md ${{ env.PUBLIC_SOURCE }}/PUBLIC_SOURCE_MANIFEST.json ${{ env.PUBLIC_SOURCE }}/acceptance-evidence/*.json ${{ env.PUBLIC_SOURCE }}/acceptance-evidence/*.txt ${{ env.PUBLIC_SOURCE }}/acceptance-evidence/release/*.json ${{ env.PUBLIC_SOURCE }}/acceptance-evidence/release/*SHA256SUMS if-no-files-found: warn retention-days: 90