|
| 1 | +name: Deploy verified LibreFS HFS wrapper |
| 2 | + |
| 3 | +on: |
| 4 | + workflow_dispatch: |
| 5 | + inputs: |
| 6 | + target: |
| 7 | + description: "Manifest-owned deployment target" |
| 8 | + required: true |
| 9 | + default: candidate |
| 10 | + type: choice |
| 11 | + options: [candidate, production] |
| 12 | + confirm_release: |
| 13 | + description: "Type RELEASE to confirm the Space write" |
| 14 | + required: true |
| 15 | + type: string |
| 16 | + librefs_commit: |
| 17 | + description: "Immutable 40-character lowercase libreFS commit already configured in Space Variables" |
| 18 | + required: true |
| 19 | + type: string |
| 20 | + |
| 21 | +permissions: |
| 22 | + contents: read |
| 23 | + |
| 24 | +concurrency: |
| 25 | + group: librefs-hfs-space-release |
| 26 | + cancel-in-progress: false |
| 27 | + |
| 28 | +env: |
| 29 | + HF_CLI_VERSION: "1.5.0" |
| 30 | + |
| 31 | +jobs: |
| 32 | + deploy: |
| 33 | + if: ${{ inputs.confirm_release == 'RELEASE' }} |
| 34 | + runs-on: ubuntu-24.04 |
| 35 | + environment: ${{ inputs.target == 'candidate' && 'hfs-candidate' || 'hfs-production' }} |
| 36 | + steps: |
| 37 | + - name: Check out the immutable wrapper commit |
| 38 | + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 |
| 39 | + with: |
| 40 | + fetch-depth: 1 |
| 41 | + |
| 42 | + - name: Validate release inputs and configured source pin |
| 43 | + env: |
| 44 | + HF_TOKEN: ${{ secrets.HF_TOKEN }} |
| 45 | + LIBREFS_COMMIT: ${{ inputs.librefs_commit }} |
| 46 | + HFS_MANIFEST: ${{ inputs.target == 'candidate' && 'hfs-dev.candidate.toml' || 'hfs-dev.toml' }} |
| 47 | + run: | |
| 48 | + set -Eeuo pipefail |
| 49 | + [[ "$LIBREFS_COMMIT" =~ ^[0-9a-f]{40}$ ]] |
| 50 | + SPACE_ID="$(python3 -c 'import os,tomllib; print(tomllib.load(open(os.environ["HFS_MANIFEST"], "rb"))["space"])')" |
| 51 | + echo "SPACE_ID=$SPACE_ID" >> "$GITHUB_ENV" |
| 52 | + python3 -m pip install --disable-pip-version-check "huggingface_hub==${HF_CLI_VERSION}" |
| 53 | + if [[ "${{ inputs.target }}" == candidate ]]; then |
| 54 | + test "$(hf spaces info "$SPACE_ID" --expand private --json | python3 -c 'import json,sys; print(str(json.load(sys.stdin)["private"]).lower())')" = true |
| 55 | + fi |
| 56 | + configured_variables="$(hf spaces variables list "$SPACE_ID")" |
| 57 | + grep -F "LIBREFS_COMMIT" <<<"$configured_variables" >/dev/null |
| 58 | + grep -F "$LIBREFS_COMMIT" <<<"$configured_variables" >/dev/null |
| 59 | + grep -E 'HFS_RELEASE_BUILD.*true|true.*HFS_RELEASE_BUILD' <<<"$configured_variables" >/dev/null |
| 60 | +
|
| 61 | + - name: Export and verify the allowlisted Space wrapper |
| 62 | + env: |
| 63 | + HFS_MANIFEST: ${{ inputs.target == 'candidate' && 'hfs-dev.candidate.toml' || 'hfs-dev.toml' }} |
| 64 | + run: | |
| 65 | + set -Eeuo pipefail |
| 66 | + scripts/export-space-bundle.sh \ |
| 67 | + --source-commit "$GITHUB_SHA" \ |
| 68 | + --librefs-source-commit "$LIBREFS_COMMIT" \ |
| 69 | + --manifest "$HFS_MANIFEST" \ |
| 70 | + --output "$RUNNER_TEMP/librefs-hfs-space-bundle" |
| 71 | + scripts/verify-space-bundle.sh --bundle "$RUNNER_TEMP/librefs-hfs-space-bundle" |
| 72 | +
|
| 73 | + - name: Refuse a Space repository outside the wrapper boundary |
| 74 | + env: |
| 75 | + HF_TOKEN: ${{ secrets.HF_TOKEN }} |
| 76 | + run: | |
| 77 | + set -Eeuo pipefail |
| 78 | + bundle_dir="$RUNNER_TEMP/librefs-hfs-space-bundle" |
| 79 | + preflight_dir="$RUNNER_TEMP/librefs-hfs-space-preflight" |
| 80 | + expected_paths="$(cd "$bundle_dir" && find . -type f -print | LC_ALL=C sort | cut -c3-)" |
| 81 | + if ! hf download "$SPACE_ID" --repo-type space --local-dir "$preflight_dir" >/dev/null 2>&1; then |
| 82 | + echo "Refusing to upload: unable to read the current Space repository." >&2 |
| 83 | + exit 1 |
| 84 | + fi |
| 85 | + actual_paths="$(cd "$preflight_dir" && find . -type f ! -path './.cache/huggingface/*' -print | LC_ALL=C sort | cut -c3-)" |
| 86 | + if [[ "$actual_paths" != "$expected_paths" ]]; then |
| 87 | + printf '%s\n%s\n' \ |
| 88 | + 'Refusing to upload: the current Space repository is not exactly the verified wrapper boundary.' \ |
| 89 | + 'This workflow never deletes legacy files. Migrate to a clean Space repository or perform a separately approved, reviewed cleanup before retrying.' >&2 |
| 90 | + exit 1 |
| 91 | + fi |
| 92 | +
|
| 93 | + - name: Upload only the verified wrapper bundle |
| 94 | + env: |
| 95 | + HF_TOKEN: ${{ secrets.HF_TOKEN }} |
| 96 | + run: | |
| 97 | + set -Eeuo pipefail |
| 98 | + hf upload "$SPACE_ID" "$RUNNER_TEMP/librefs-hfs-space-bundle" . \ |
| 99 | + --repo-type space \ |
| 100 | + --commit-message "Deploy verified wrapper ${GITHUB_SHA}" |
| 101 | +
|
| 102 | + - name: Read back the complete uploaded wrapper and Space metadata |
| 103 | + env: |
| 104 | + HF_TOKEN: ${{ secrets.HF_TOKEN }} |
| 105 | + run: | |
| 106 | + set -Eeuo pipefail |
| 107 | + bundle_dir="$RUNNER_TEMP/librefs-hfs-space-bundle" |
| 108 | + readback_dir="$RUNNER_TEMP/librefs-hfs-space-readback" |
| 109 | + expected_paths="$(cd "$bundle_dir" && find . -type f -print | LC_ALL=C sort | cut -c3-)" |
| 110 | + if ! hf download "$SPACE_ID" --repo-type space --local-dir "$readback_dir" >/dev/null 2>&1; then |
| 111 | + echo "Unable to read back the uploaded Space repository." >&2 |
| 112 | + exit 1 |
| 113 | + fi |
| 114 | + actual_paths="$(cd "$readback_dir" && find . -type f ! -path './.cache/huggingface/*' -print | LC_ALL=C sort | cut -c3-)" |
| 115 | + [[ "$actual_paths" == "$expected_paths" ]] |
| 116 | + cmp "$bundle_dir/BUILD_SOURCE.json" "$readback_dir/BUILD_SOURCE.json" |
| 117 | + cmp "$bundle_dir/SHA256SUMS" "$readback_dir/SHA256SUMS" |
| 118 | + ( |
| 119 | + cd "$readback_dir" |
| 120 | + sha256sum -c SHA256SUMS |
| 121 | + ) |
| 122 | + hf spaces info "$SPACE_ID" |
| 123 | +
|
| 124 | + - name: Record remaining runtime owner gates |
| 125 | + run: | |
| 126 | + cat <<'EOF' |
| 127 | + Source bundle upload and complete repository readback succeeded. Before |
| 128 | + treating the release as complete, the release and data owners must |
| 129 | + separately read back Space build/runtime status and perform the approved |
| 130 | + health, authenticated S3 smoke, restart/rebuild persistence, backup, and |
| 131 | + isolated-restore checks. This workflow performs none of those actions. |
| 132 | + EOF |
0 commit comments