From 26e3395b881d728137b8de27366b5af230d8cf0c Mon Sep 17 00:00:00 2001 From: yqtian-se Date: Tue, 25 Aug 2026 09:25:38 +1000 Subject: [PATCH 1/3] Add Sandeep Kuttal webinar recording --- content/activities/webinarsmain.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/content/activities/webinarsmain.md b/content/activities/webinarsmain.md index 9955cd36..da874d0a 100644 --- a/content/activities/webinarsmain.md +++ b/content/activities/webinarsmain.md @@ -44,6 +44,8 @@ Sandeep Kaur Kuttal is an Associate Professor at North Carolina State University #### Date and Time: June 19, 2026, 10am-11am ET +Recording is available on [YouTube](https://youtu.be/ihPuLPvC2TI). + ### What do professional software developers need to know to succeed in an age of Artificial Intelligence? From 17e1f5359b5c792bff011d9c1f80e0d2d60e4645 Mon Sep 17 00:00:00 2001 From: yqtian-se Date: Tue, 25 Aug 2026 10:01:07 +1000 Subject: [PATCH 2/3] Secure pull request preview deployment --- .github/workflows/pr-preview-deploy.yml | 177 ++++++++++++++++++++++++ .github/workflows/pr-preview.yml | 112 +++++---------- .github/workflows/production-deploy.yml | 34 +++-- README.md | 3 +- 4 files changed, 240 insertions(+), 86 deletions(-) create mode 100644 .github/workflows/pr-preview-deploy.yml diff --git a/.github/workflows/pr-preview-deploy.yml b/.github/workflows/pr-preview-deploy.yml new file mode 100644 index 00000000..0958d138 --- /dev/null +++ b/.github/workflows/pr-preview-deploy.yml @@ -0,0 +1,177 @@ +# Deploy the static artifact produced by the unprivileged Build Preview workflow. +# Never check out or execute pull request code in this privileged workflow. +name: Deploy Preview + +on: + workflow_run: + workflows: + - Build Preview + types: + - completed + +permissions: + actions: read + contents: read + pages: write + id-token: write + pull-requests: write + +# Share the Pages deployment lock with the production workflow. +concurrency: + group: pages + cancel-in-progress: false + +defaults: + run: + shell: bash + +jobs: + deploy: + if: >- + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + env: + HUGO_VERSION: 0.148.2 + SITE_BASE_URL: https://www2.sigsoft.org + environment: + name: github-pages + url: ${{ steps.metadata.outputs.preview_url }} + steps: + - name: Download preview artifact + uses: actions/download-artifact@v8 + with: + name: pr-preview + path: ${{ runner.temp }}/preview + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Validate deployment metadata + id: metadata + env: + PREVIEW_ROOT: ${{ runner.temp }}/preview + run: | + test -d "$PREVIEW_ROOT/preview-public" + test -f "$PREVIEW_ROOT/metadata/pr-number" + grep -Eq '^[1-9][0-9]*$' "$PREVIEW_ROOT/metadata/pr-number" + PR_NUMBER="$(tr -d '\r\n' < "$PREVIEW_ROOT/metadata/pr-number")" + printf 'pr_number=%s\n' "$PR_NUMBER" >> "$GITHUB_OUTPUT" + printf 'preview_url=%s/pr-%s\n' "$SITE_BASE_URL" "$PR_NUMBER" >> "$GITHUB_OUTPUT" + + - name: Install Hugo CLI + run: | + wget -O "${{ runner.temp }}/hugo.deb" \ + "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb" + sudo dpkg -i "${{ runner.temp }}/hugo.deb" + + - name: Install Dart Sass + env: + DART_SASS_VERSION: 1.83.4 + run: | + wget -O /tmp/dart-sass.tar.gz \ + "https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz" + tar -xf /tmp/dart-sass.tar.gz -C /tmp + echo "/tmp/dart-sass" >> "$GITHUB_PATH" + + # The default checkout is the trusted default branch for a workflow_run event. + - name: Checkout trusted base branch + uses: actions/checkout@v6 + with: + path: base + submodules: recursive + fetch-depth: 0 + persist-credentials: false + + - name: Install Node.js dependencies for base + working-directory: base + run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true" + + - name: Build production site + env: + HUGO_ENVIRONMENT: production + HUGO_ENV: production + run: | + hugo \ + --source base \ + --minify \ + --baseURL "${SITE_BASE_URL}/" \ + --destination "${{ github.workspace }}/public" + + - name: Copy legacy SEN assets for production + working-directory: base + env: + PUBLIC_DIR: ${{ github.workspace }}/public + run: node scripts/copy-legacy-sen-assets.mjs + + - name: Add PR preview to production site + env: + PREVIEW_ROOT: ${{ runner.temp }}/preview + PR_NUMBER: ${{ steps.metadata.outputs.pr_number }} + run: | + mkdir -p "${{ github.workspace }}/public/pr-${PR_NUMBER}" + cp -a "$PREVIEW_ROOT/preview-public/." "${{ github.workspace }}/public/pr-${PR_NUMBER}/" + + # Create the Pages artifact without executing any file from the pull request. + - name: Archive Pages artifact + env: + PUBLIC_DIR: ${{ github.workspace }}/public + run: | + tar \ + --dereference --hard-dereference \ + --directory "$PUBLIC_DIR" \ + -cvf "${{ runner.temp }}/artifact.tar" \ + --exclude=.git \ + --exclude=.github \ + --exclude='.[^/]*' \ + . + + - name: Upload Pages artifact + uses: actions/upload-artifact@v7 + with: + name: github-pages + path: ${{ runner.temp }}/artifact.tar + retention-days: 1 + if-no-files-found: error + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v5 + + - name: Add preview URL to PR + uses: actions/github-script@v9 + env: + PR_NUMBER: ${{ steps.metadata.outputs.pr_number }} + PREVIEW_URL: ${{ steps.metadata.outputs.preview_url }} + with: + script: | + const issueNumber = Number(process.env.PR_NUMBER); + if (!Number.isSafeInteger(issueNumber) || issueNumber <= 0) { + throw new Error(`Invalid pull request number: ${process.env.PR_NUMBER}`); + } + + const marker = ''; + const body = `${marker}\nPreview deployed at: ${process.env.PREVIEW_URL}`; + const comments = await github.paginate(github.rest.issues.listComments, { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + }); + const existing = comments.find(comment => + comment.user?.type === 'Bot' && comment.body?.includes(marker) + ); + + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + body, + }); + } diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml index 5bf8e1aa..26b0a141 100644 --- a/.github/workflows/pr-preview.yml +++ b/.github/workflows/pr-preview.yml @@ -1,41 +1,37 @@ -# Workflow for building and deploying a preview of the Hugo site for a pull request -name: Deploy Preview +# Build a Hugo preview for a pull request in an unprivileged workflow. +# Deployment is handled separately by pr-preview-deploy.yml. +name: Build Preview on: - # Runs on pull requests targeting the main branch - pull_request_target: + pull_request: branches: - main -# Sets permissions of the GitHub TOKEN to allow deployment to GitHub Pages +# Fork pull requests run with a read-only token and without repository secrets. permissions: contents: read - pages: write - id-token: write -# Allow one concurrent deployment per pull request, and cancel in-progress runs. concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} cancel-in-progress: true -# Default to bash defaults: run: shell: bash jobs: - # Build job build: runs-on: ubuntu-latest - outputs: - preview_url: ${{ steps.base_url.outputs.base_url }} env: HUGO_VERSION: 0.148.2 + PREVIEW_BASE_URL: https://www2.sigsoft.org/pr-${{ github.event.pull_request.number }} steps: - name: Install Hugo CLI run: | - wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \ - && sudo dpkg -i ${{ runner.temp }}/hugo.deb + wget -O "${{ runner.temp }}/hugo.deb" \ + "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb" + sudo dpkg -i "${{ runner.temp }}/hugo.deb" + - name: Install Dart Sass env: DART_SASS_VERSION: 1.83.4 @@ -43,46 +39,22 @@ jobs: wget -O /tmp/dart-sass.tar.gz \ "https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz" tar -xf /tmp/dart-sass.tar.gz -C /tmp - echo "/tmp/dart-sass" >> $GITHUB_PATH - - name: Setup Pages - id: pages - uses: actions/configure-pages@v5 - - # Build production site from base branch - - name: Checkout base branch - uses: actions/checkout@v4 - with: - ref: ${{ github.base_ref }} - path: base - submodules: recursive - - name: Install Node.js dependencies for base - working-directory: base - run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true" - - name: Build production site - env: - HUGO_ENVIRONMENT: production - HUGO_ENV: production - run: | - hugo \ - --source base \ - --minify \ - --baseURL "${{ steps.pages.outputs.base_url }}/" \ - --destination "${{ github.workspace }}/public" + echo "/tmp/dart-sass" >> "$GITHUB_PATH" - # Build PR preview + # This is untrusted fork code, but this workflow has no secrets or write permissions. - name: Checkout PR branch - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: ${{ github.event.pull_request.head.sha }} path: pr submodules: recursive fetch-depth: 0 + persist-credentials: false + - name: Install Node.js dependencies for PR working-directory: pr run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true" - - name: Set preview baseURL - id: base_url - run: echo "base_url=${{ steps.pages.outputs.base_url }}/pr-${{ github.event.number }}" >> $GITHUB_OUTPUT + - name: Build PR preview env: HUGO_ENVIRONMENT: production @@ -91,48 +63,36 @@ jobs: hugo \ --source pr \ --minify \ - --baseURL "${{ steps.base_url.outputs.base_url }}/" \ - --destination "${{ github.workspace }}/public/pr-${{ github.event.number }}" + --baseURL "${PREVIEW_BASE_URL}/" \ + --destination "${{ github.workspace }}/preview-public" - name: Copy legacy SEN assets for PR preview working-directory: pr env: - PUBLIC_DIR: ${{ github.workspace }}/public/pr-${{ github.event.number }} + PUBLIC_DIR: ${{ github.workspace }}/preview-public run: node scripts/copy-legacy-sen-assets.mjs - name: Check internal links working-directory: pr env: - SITE_BASE_URL: ${{ steps.base_url.outputs.base_url }}/ - PUBLIC_DIR: ${{ github.workspace }}/public/pr-${{ github.event.number }} + SITE_BASE_URL: ${{ env.PREVIEW_BASE_URL }}/ + PUBLIC_DIR: ${{ github.workspace }}/preview-public LINK_CHECK_BASE_REF: origin/${{ github.base_ref }} run: node scripts/check-changed-links.mjs - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: ./public + - name: Save pull request metadata + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + mkdir -p metadata + printf '%s\n' "$PR_NUMBER" > metadata/pr-number - # Deployment job - deploy: - environment: - name: github-pages - url: ${{ needs.build.outputs.preview_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 - add-comment: - runs-on: ubuntu-latest - needs: [build, deploy] - permissions: - pull-requests: write - steps: - - name: Add preview URL to PR - uses: peter-evans/create-or-update-comment@v4 + - name: Upload preview artifact + uses: actions/upload-artifact@v7 with: - issue-number: ${{ github.event.pull_request.number }} - body: | # This is a multiline string, so newlines are preserved correctly. - Preview deployed at: ${{ needs.build.outputs.preview_url }} + name: pr-preview + path: | + preview-public/ + metadata/ + retention-days: 1 + if-no-files-found: error diff --git a/.github/workflows/production-deploy.yml b/.github/workflows/production-deploy.yml index b5059e1e..badaa379 100644 --- a/.github/workflows/production-deploy.yml +++ b/.github/workflows/production-deploy.yml @@ -34,8 +34,9 @@ jobs: steps: - name: Install Hugo CLI run: | - wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \ - && sudo dpkg -i ${{ runner.temp }}/hugo.deb + wget -O "${{ runner.temp }}/hugo.deb" \ + "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb" + sudo dpkg -i "${{ runner.temp }}/hugo.deb" - name: Install Dart Sass env: DART_SASS_VERSION: 1.83.4 @@ -43,15 +44,16 @@ jobs: wget -O /tmp/dart-sass.tar.gz \ "https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz" tar -xf /tmp/dart-sass.tar.gz -C /tmp - echo "/tmp/dart-sass" >> $GITHUB_PATH + echo "/tmp/dart-sass" >> "$GITHUB_PATH" - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: recursive fetch-depth: 0 + persist-credentials: false - name: Setup Pages id: pages - uses: actions/configure-pages@v5 + uses: actions/configure-pages@v6 - name: Install Node.js dependencies run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true" - name: Build with Hugo @@ -72,10 +74,24 @@ jobs: SITE_BASE_URL: ${{ steps.pages.outputs.base_url }}/ run: node scripts/check-changed-links.mjs - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + - name: Archive Pages artifact + run: | + tar \ + --dereference --hard-dereference \ + --directory public \ + -cvf "${{ runner.temp }}/artifact.tar" \ + --exclude=.git \ + --exclude=.github \ + --exclude='.[^/]*' \ + . + + - name: Upload Pages artifact + uses: actions/upload-artifact@v7 with: - path: ./public + name: github-pages + path: ${{ runner.temp }}/artifact.tar + retention-days: 1 + if-no-files-found: error # Deployment job deploy: @@ -87,4 +103,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 diff --git a/README.md b/README.md index fd1df505..de01013b 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,8 @@ Alternatively, to test the website on GitHub, please create a pull request and t You can then review the changes on the staging environment before asking for reviewing the pull request. There are two GitHub Actions workflows defined in the `.github/workflows` directory: -- `pr-preview.yml`: This workflow is triggered on pull requests and deploys the website to a temporary URL. +- `pr-preview.yml`: This read-only workflow builds preview artifacts for pull requests. +- `pr-preview-deploy.yml`: This workflow deploys successful preview artifacts and adds the preview URL to the pull request. - `production-deploy.yml`: This workflow is triggered on pushes to the main branch and deploys the website to the production URL. You can enable these workflows in your forked repository by going to the "Actions" tab and clicking on the "Enable workflow" button. From 281631ae870eeaaf145ba7f8e69d924c16b68126 Mon Sep 17 00:00:00 2001 From: yqtian-se Date: Tue, 25 Aug 2026 10:14:43 +1000 Subject: [PATCH 3/3] Use repository Pages URL for previews --- .github/workflows/pr-preview-deploy.yml | 7 ++++++- .github/workflows/pr-preview.yml | 8 ++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-preview-deploy.yml b/.github/workflows/pr-preview-deploy.yml index 0958d138..42120447 100644 --- a/.github/workflows/pr-preview-deploy.yml +++ b/.github/workflows/pr-preview-deploy.yml @@ -33,7 +33,6 @@ jobs: runs-on: ubuntu-latest env: HUGO_VERSION: 0.148.2 - SITE_BASE_URL: https://www2.sigsoft.org environment: name: github-pages url: ${{ steps.metadata.outputs.preview_url }} @@ -46,10 +45,15 @@ jobs: run-id: ${{ github.event.workflow_run.id }} github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Setup Pages + id: pages + uses: actions/configure-pages@v6 + - name: Validate deployment metadata id: metadata env: PREVIEW_ROOT: ${{ runner.temp }}/preview + SITE_BASE_URL: ${{ steps.pages.outputs.base_url }} run: | test -d "$PREVIEW_ROOT/preview-public" test -f "$PREVIEW_ROOT/metadata/pr-number" @@ -90,6 +94,7 @@ jobs: env: HUGO_ENVIRONMENT: production HUGO_ENV: production + SITE_BASE_URL: ${{ steps.pages.outputs.base_url }} run: | hugo \ --source base \ diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml index 26b0a141..0817740d 100644 --- a/.github/workflows/pr-preview.yml +++ b/.github/workflows/pr-preview.yml @@ -24,7 +24,6 @@ jobs: runs-on: ubuntu-latest env: HUGO_VERSION: 0.148.2 - PREVIEW_BASE_URL: https://www2.sigsoft.org/pr-${{ github.event.pull_request.number }} steps: - name: Install Hugo CLI run: | @@ -41,6 +40,10 @@ jobs: tar -xf /tmp/dart-sass.tar.gz -C /tmp echo "/tmp/dart-sass" >> "$GITHUB_PATH" + - name: Setup Pages + id: pages + uses: actions/configure-pages@v6 + # This is untrusted fork code, but this workflow has no secrets or write permissions. - name: Checkout PR branch uses: actions/checkout@v6 @@ -59,6 +62,7 @@ jobs: env: HUGO_ENVIRONMENT: production HUGO_ENV: production + PREVIEW_BASE_URL: ${{ steps.pages.outputs.base_url }}/pr-${{ github.event.pull_request.number }} run: | hugo \ --source pr \ @@ -75,7 +79,7 @@ jobs: - name: Check internal links working-directory: pr env: - SITE_BASE_URL: ${{ env.PREVIEW_BASE_URL }}/ + SITE_BASE_URL: ${{ steps.pages.outputs.base_url }}/pr-${{ github.event.pull_request.number }}/ PUBLIC_DIR: ${{ github.workspace }}/preview-public LINK_CHECK_BASE_REF: origin/${{ github.base_ref }} run: node scripts/check-changed-links.mjs