Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1 @@
@pywire/maintainers
* @pywire/maintainers
14 changes: 14 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: 2
updates:
# Workflow actions are pinned to commit SHAs; this keeps the pins (and their
# `# vX.Y.Z` comments) current.
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
groups:
actions:
patterns: ["*"]
commit-message:
prefix: chore
include: scope
21 changes: 14 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,31 @@ on:
branches:
- main

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
name: Build Check
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@v7
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'

- name: Install pnpm
uses: pnpm/action-setup@v6
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 10

- name: Install dependencies
working-directory: site
run: pnpm install
run: pnpm install --frozen-lockfile --ignore-scripts

- name: Build
working-directory: site
Expand All @@ -34,12 +39,14 @@ jobs:
runs-on: ubuntu-latest
name: Installer Tests
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@v7
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'

- name: Test install.sh
- name: Test installer and router Worker
run: node --test 'tests/*.test.mjs'
64 changes: 64 additions & 0 deletions .github/workflows/deploy-nightly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Deploy Nightly

# Puts a PR's landing-site build on nightly.pywire.dev. workflow_run always
# runs this file as it is on main, so a PR can change what gets built but not
# what this job does with the token: it only downloads the artifact that
# deploy.yml built (without secrets) and uploads it to the nightly branch.
on:
workflow_run:
workflows: [Deploy Landing Site]
types: [completed]

permissions: {}

jobs:
deploy-nightly:
if: >-
github.event.workflow_run.conclusion == 'success'
&& github.event.workflow_run.head_repository.full_name == github.repository
&& (github.event.workflow_run.event == 'pull_request'
|| (github.event.workflow_run.event == 'workflow_dispatch'
&& github.event.workflow_run.head_branch != 'main'))
# Strictly one deploy at a time: a running deploy is never cancelled, and
# a newer one waits for it (GitHub keeps only the newest waiting job).
concurrency:
group: deploy-landing-nightly
cancel-in-progress: false
runs-on: ubuntu-latest
# Restricted to main in the repo settings (workflow_run runs on main) and
# holds the Pages-only CLOUDFLARE_PAGES_TOKEN.
environment:
name: nightly
url: https://nightly.pywire.dev
permissions:
actions: read # the build artifact lives on the triggering run
name: Deploy Nightly
steps:
- name: Find build
id: build
# Drafts skip the build, so there is nothing to deploy.
env:
GH_TOKEN: ${{ github.token }}
RUN_ID: ${{ github.event.workflow_run.id }}
run: |
count=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$RUN_ID/artifacts" \
--jq '[.artifacts[] | select(.name == "landing-dist" and (.expired | not))] | length')
echo "found=$([ "$count" -gt 0 ] && echo true || echo false)" >>"$GITHUB_OUTPUT"

- name: Download build
if: steps.build.outputs.found == 'true'
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: landing-dist
path: dist
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ github.token }}

- name: Deploy to Cloudflare Pages
if: steps.build.outputs.found == 'true'
uses: cloudflare/wrangler-action@953926a2e2182532811c01a25e53647d93bf07c0 # v4.1.3
with:
wranglerVersion: '4.145.0'
apiToken: ${{ secrets.CLOUDFLARE_PAGES_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy dist --project-name=pywire-landing --branch=nightly
72 changes: 51 additions & 21 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ name: Deploy Landing Site
# main deploys production. Nightly (nightly.pywire.dev) only takes PRs that are
# ready for review: drafts and bare branch pushes never deploy. Mark a PR
# ready, or push to one that already is, to put it on nightly.
#
# Building runs PR code and third-party install scripts, so it holds no
# secrets: it uploads site/dist as an artifact. Deploying only downloads that
# artifact and uploads it to Pages, in a job whose environment only main can
# use. PR builds deploy from deploy-nightly.yml (workflow_run), which runs
# main's copy of that workflow, never the PR's.
on:
workflow_dispatch: {}
push:
Expand All @@ -14,55 +20,79 @@ on:
paths:
- 'site/**'

permissions: {}

jobs:
deploy-landing:
# PRs: ready for review only, and only from this repo (forks get no secrets).
build:
# PRs: ready for review only, and only from this repo.
if: >-
github.event_name != 'pull_request'
|| (!github.event.pull_request.draft
&& github.event.pull_request.head.repo.full_name == github.repository)
# Strictly one deploy per target at a time: a running deploy is never
# cancelled, and a newer one waits for it (GitHub keeps only the newest
# waiting job, so an older queued deploy is dropped in its favour). Job
# level, so skipped draft runs never join the queue.
concurrency:
group: deploy-landing-${{ github.ref == 'refs/heads/main' && 'main' || 'nightly' }}
cancel-in-progress: false
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
name: Deploy Landing Site
env:
TARGET_BRANCH: ${{ github.ref == 'refs/heads/main' && 'main' || 'nightly' }}
name: Build Landing Site
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Deploy the PR's own head, not GitHub's merge preview.
ref: ${{ github.event.pull_request.head.sha || github.sha }}
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@v7
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'

- name: Install pnpm
uses: pnpm/action-setup@v6
uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
with:
version: 10

- name: Install dependencies
working-directory: site
run: pnpm install
run: pnpm install --frozen-lockfile --ignore-scripts

- name: Build
working-directory: site
run: pnpm run build

- name: Upload build
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: landing-dist
path: site/dist
if-no-files-found: error
retention-days: 3

deploy-production:
needs: build
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
# Strictly one deploy at a time: a running deploy is never cancelled, and
# a newer one waits for it (GitHub keeps only the newest waiting job).
concurrency:
group: deploy-landing-main
cancel-in-progress: false
runs-on: ubuntu-latest
# The environment is restricted to main in the repo settings and holds
# CLOUDFLARE_PAGES_TOKEN, so no other branch's workflow can read it.
environment:
name: production
url: https://pywire.dev
permissions: {}
name: Deploy Landing Site
steps:
- name: Download build
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: landing-dist
path: dist

- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v4
uses: cloudflare/wrangler-action@953926a2e2182532811c01a25e53647d93bf07c0 # v4.1.3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
wranglerVersion: '4.145.0'
apiToken: ${{ secrets.CLOUDFLARE_PAGES_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
command: pages deploy site/dist --project-name=pywire-landing --branch=${{ env.TARGET_BRANCH }}
command: pages deploy dist --project-name=pywire-landing --branch=main
23 changes: 13 additions & 10 deletions .github/workflows/infra-apply.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,24 @@ name: Infra apply
on:
workflow_dispatch: {}

permissions:
contents: read
permissions: {}

concurrency:
group: terraform
cancel-in-progress: false # never cancel mid-apply

jobs:
apply:
# The real guard is the production environment: it is restricted to main
# in the repo settings and holds the write credentials, so a dispatch from
# any other branch (which runs that branch's copy of this file) gets
# nothing. The job-level `if` just skips such runs cleanly.
if: github.ref == 'refs/heads/main'
environment: production
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
defaults:
run:
working-directory: infra
Expand All @@ -25,15 +32,11 @@ jobs:
TF_VAR_maintainer_emails: ${{ secrets.MAINTAINER_EMAILS }}
TF_IN_AUTOMATION: "true"
steps:
- uses: actions/checkout@v7

- name: Main branch only
if: github.ref != 'refs/heads/main'
run: |
echo "::error::Infra apply only runs on main"
exit 1
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- uses: hashicorp/setup-terraform@v4.0.1
- uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1
with:
terraform_version: 1.16.4
terraform_wrapper: false
Expand Down
32 changes: 20 additions & 12 deletions .github/workflows/infra-plan.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
name: Infra plan

# Plans only ever read. Every run (PRs, main, the weekly drift check) uses a
# read-only Cloudflare token and read-only state-bucket keys, and skips the
# state lock (-lock=false) because taking it is a write. PR runs execute the
# PR's own Terraform, so they must never see a credential that can change
# anything; applying is infra-apply.yml's job, in the production environment.
on:
pull_request:
paths: ["infra/**", "worker/**"]
Expand All @@ -9,10 +14,7 @@ on:
schedule:
- cron: "0 8 * * 1" # weekly drift check

permissions:
contents: read
pull-requests: write # plan comments
issues: write # drift issues
permissions: {}

concurrency:
group: terraform
Expand All @@ -22,20 +24,26 @@ jobs:
plan:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
pull-requests: write # plan comments
issues: write # drift issues
defaults:
run:
working-directory: infra
env:
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
TF_VAR_cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
AWS_ACCESS_KEY_ID: ${{ secrets.R2_READONLY_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_READONLY_SECRET_ACCESS_KEY }}
TF_VAR_cloudflare_api_token: ${{ secrets.CLOUDFLARE_READONLY_TOKEN }}
TF_VAR_forwarding_rules: ${{ secrets.EMAIL_FORWARDING_RULES }}
TF_VAR_maintainer_emails: ${{ secrets.MAINTAINER_EMAILS }}
TF_IN_AUTOMATION: "true"
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- uses: hashicorp/setup-terraform@v4.0.1
- uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1
with:
terraform_version: 1.16.4
terraform_wrapper: false
Expand All @@ -50,7 +58,7 @@ jobs:
echo "exitcode=$code" >>"$GITHUB_OUTPUT"
exit 0
fi
terraform plan -input=false -no-color -detailed-exitcode -lock-timeout=120s >plan.txt 2>&1
terraform plan -input=false -no-color -detailed-exitcode -lock=false >plan.txt 2>&1
code=$?
# The plan is posted publicly (PR comments, drift issues) — scrub emails.
sed -i -E 's/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/[email]/g' plan.txt
Expand All @@ -60,7 +68,7 @@ jobs:
- name: Comment plan on PR
# A non-empty plan on a PR is expected — comment it, never fail the PR.
if: github.event_name == 'pull_request'
uses: actions/github-script@v9.0.0
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const code = Number('${{ steps.plan.outputs.exitcode }}');
Expand Down Expand Up @@ -109,7 +117,7 @@ jobs:

- name: Open drift issue (scheduled check)
if: github.event_name == 'schedule' && steps.plan.outputs.exitcode != '0'
uses: actions/github-script@v9.0.0
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const title = 'Terraform drift on main';
Expand Down
Loading
Loading