Repository navigation
Archive website (LFS + history cleanup) #408
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- | |
| name: Archive website (LFS + history cleanup) | |
| 'on': | |
| schedule: | |
| - cron: '0 0 * * *' # Daily at midnight UTC | |
| workflow_dispatch: {} | |
| permissions: | |
| contents: write | |
| jobs: | |
| archive: | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: archive-signing | |
| steps: | |
| # ---------------------------- | |
| # Checkout repository (full history + LFS) | |
| # ---------------------------- | |
| - name: Checkout repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 | |
| with: | |
| lfs: true | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| # ---------------------------- | |
| # Install dependencies | |
| # ---------------------------- | |
| - name: Install dependencies | |
| run: | | |
| set -euo pipefail | |
| sudo apt-get update | |
| sudo apt-get install -y clang libbsd-dev | |
| python3 -m pip install --upgrade git-filter-repo | |
| - name: Install signify (build from source or fall back to package) | |
| id: signify | |
| run: | | |
| set -euo pipefail | |
| SIGNIFY_REPO="https://codeberg.org/aperezdc/signify.git" | |
| SIGNIFY_DIR="$(mktemp -d)" | |
| trap 'rm -rf "$SIGNIFY_DIR"' EXIT | |
| build_signify() { | |
| make -s -C "$SIGNIFY_DIR" CC=clang BUNDLED_LIBBSD=1 | |
| sudo install -m 0755 "$SIGNIFY_DIR/signify" /usr/local/bin/signify | |
| } | |
| clone_signify() { | |
| local proxy_cfg="$1" | |
| LATEST_TAG="$( | |
| git ${proxy_cfg} ls-remote \ | |
| --tags --sort='version:refname' \ | |
| "$SIGNIFY_REPO" 'refs/tags/v*' | | |
| awk '{print $2}' | | |
| sed 's#refs/tags/##; s#\^{}##' | | |
| uniq | | |
| tail -n1 | |
| )" | |
| test -n "$LATEST_TAG" | |
| git ${proxy_cfg} clone --quiet --depth 1 \ | |
| "$SIGNIFY_REPO" "$SIGNIFY_DIR" | |
| git -C "$SIGNIFY_DIR" ${proxy_cfg} fetch --quiet --depth 1 origin \ | |
| "refs/tags/$LATEST_TAG:refs/tags/$LATEST_TAG" | |
| git -C "$SIGNIFY_DIR" checkout --quiet "tags/$LATEST_TAG" | |
| } | |
| # Attempt 1: clearnet clone from Codeberg | |
| if clone_signify ""; then | |
| build_signify | |
| echo "binary=/usr/local/bin/signify" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # Attempt 2: via Tor SOCKS proxy | |
| echo "Clearnet clone failed. Attempting via Tor..." | |
| sudo apt-get install -y tor | |
| sudo nohup tor --RunAsDaemon 1 & | |
| sleep 3 | |
| PROXY="-c http.proxy=socks5h://127.0.0.1:9050" | |
| PROXY="$PROXY -c https.proxy=socks5h://127.0.0.1:9050" | |
| if clone_signify "$PROXY"; then | |
| build_signify | |
| echo "binary=/usr/local/bin/signify" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # Attempt 3: Debian / Ubuntu package fallback | |
| echo "Source build failed. Installing signify-openbsd package..." | |
| sudo apt-get install -y signify-openbsd | |
| echo "binary=/usr/bin/signify-openbsd" >> "$GITHUB_OUTPUT" | |
| # ---------------------------- | |
| # Check if repository changed since last archive | |
| # ---------------------------- | |
| - name: Check for repository changes | |
| id: changes | |
| run: | | |
| set -euo pipefail | |
| LAST_AUTHOR="$(git log -1 --pretty=%an)" | |
| LAST_MESSAGE="$(git log -1 --pretty=%s)" | |
| if [[ "$LAST_AUTHOR" == "github-actions[bot]" && \ | |
| "$LAST_MESSAGE" == Archive\ website* ]]; then | |
| echo "No real changes since last archive. Skipping." | |
| echo "changed=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "Changes detected. Proceeding with archive." | |
| echo "changed=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| # ---------------------------- | |
| # Create archive, checksum, and signature | |
| # ---------------------------- | |
| - name: Create archive, checksum, and signature | |
| if: steps.changes.outputs.changed == 'true' | |
| env: | |
| SIGNIFY_KEY: ${{ secrets.SIGNIFY_KEY }} | |
| SIGNIFY_BIN: ${{ steps.signify.outputs.binary }} | |
| run: | | |
| set -euo pipefail | |
| REPO_NAME="$(basename "$GITHUB_REPOSITORY")" | |
| WORKDIR="$GITHUB_WORKSPACE" | |
| TMP_DIR="$WORKDIR/tmp_archive" | |
| ARCHIVE_DIR="$WORKDIR/archive" | |
| mkdir -p "$TMP_DIR" "$ARCHIVE_DIR" | |
| ARCHIVE="$TMP_DIR/$REPO_NAME.tar.gz" | |
| CHECKSUM="$TMP_DIR/$REPO_NAME.tar.gz.sha256" | |
| SIGNATURE="$TMP_DIR/$REPO_NAME.tar.gz.sig" | |
| KEYFILE="$TMP_DIR/$REPO_NAME.sec" | |
| rm -f "$ARCHIVE" "$CHECKSUM" "$SIGNATURE" "$KEYFILE" | |
| # Create tar.gz archive | |
| # Excluded: | |
| # - .git/ (VCS metadata) | |
| # - .github/ (CI configuration) | |
| # - .gitattributes (LFS rules / derived config) | |
| # - archive/ (derived artifacts) | |
| # - tmp_archive/ (temporary files) | |
| tar \ | |
| --format=pax \ | |
| --exclude="./.git" \ | |
| --exclude="./.github" \ | |
| --exclude="./.gitattributes" \ | |
| --exclude="./archive" \ | |
| --exclude="./tmp_archive" \ | |
| --use-compress-program="gzip -9" \ | |
| -cf "$ARCHIVE" . | |
| # Generate SHA256 checksum (filename only) | |
| ( | |
| cd "$TMP_DIR" | |
| sha256sum "$(basename "$ARCHIVE")" > "$(basename "$CHECKSUM")" | |
| ) | |
| # Write signify private key to a temporary file | |
| printf '%s\n' "$SIGNIFY_KEY" > "$KEYFILE" | |
| chmod 600 "$KEYFILE" | |
| # Sign the checksum (NOT the archive) | |
| "$SIGNIFY_BIN" -S \ | |
| -s "$KEYFILE" \ | |
| -m "$CHECKSUM" \ | |
| -x "$SIGNATURE" | |
| # Securely delete secret key material | |
| shred -u "$KEYFILE" | |
| # ---------------------------- | |
| # Track archive via Git LFS | |
| # ---------------------------- | |
| - name: Track archive with Git LFS | |
| if: steps.changes.outputs.changed == 'true' | |
| run: | | |
| set -euo pipefail | |
| git lfs install --local | |
| git lfs track "archive/*.tar.gz" | |
| git add .gitattributes | |
| # ---------------------------- | |
| # Remove old archive from history | |
| # ---------------------------- | |
| - name: Clean old archive from history | |
| if: steps.changes.outputs.changed == 'true' | |
| run: | | |
| set -euo pipefail | |
| REPO_NAME="$(basename "$GITHUB_REPOSITORY")" | |
| # Remove previous archive versions from history | |
| git filter-repo \ | |
| --path "archive/$REPO_NAME.tar.gz" \ | |
| --invert-paths \ | |
| --force | |
| # Copy new artifacts into the repository | |
| cp "$GITHUB_WORKSPACE/tmp_archive/$REPO_NAME.tar.gz" archive/ | |
| cp "$GITHUB_WORKSPACE/tmp_archive/$REPO_NAME.tar.gz.sha256" archive/ | |
| cp "$GITHUB_WORKSPACE/tmp_archive/$REPO_NAME.tar.gz.sig" archive/ | |
| git add \ | |
| "archive/$REPO_NAME.tar.gz" \ | |
| "archive/$REPO_NAME.tar.gz.sha256" \ | |
| "archive/$REPO_NAME.tar.gz.sig" | |
| # ---------------------------- | |
| # Commit and push changes | |
| # ---------------------------- | |
| - name: Commit and push changes | |
| if: steps.changes.outputs.changed == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| # Re-add origin remote (removed by git-filter-repo) | |
| PROTO="https://x-access-token:" | |
| REPO_PART="@github.com/${GITHUB_REPOSITORY}.git" | |
| REPO_URL="${PROTO}${GH_TOKEN}${REPO_PART}" | |
| git remote add origin "$REPO_URL" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| if [ -n "$(git status --porcelain)" ]; then | |
| git commit -m "Archive website (updated $(date -u +'%Y-%m-%d'))" | |
| git push --force origin main | |
| else | |
| echo "No changes to commit." | |
| fi |