website

#astro#js#html#css

git clone https://git.pyrossh.dev/website

木 Personal website of pyrossh. Built with astrojs, shiki, vite.


infra/main.tf
778e0a4 1
terraform {
778e0a4 2
  required_providers {
778e0a4 3
    cloudflare = {
778e0a4 4
      source  = "cloudflare/cloudflare"
778e0a4 5
      version = "~> 5.0"
778e0a4 6
    }
778e0a4 7
  }
778e0a4 8
}
778e0a4 9
778e0a4 10
provider "cloudflare" {}
778e0a4 11
778e0a4 12
resource "cloudflare_r2_bucket" "website" {
778e0a4 13
  account_id = var.cloudflare_account_id
778e0a4 14
  name       = "website"
778e0a4 15
}
778e0a4 16
778e0a4 17
resource "cloudflare_r2_custom_domain" "website" {
778e0a4 18
  account_id  = var.cloudflare_account_id
778e0a4 19
  bucket_name = cloudflare_r2_bucket.website.name
778e0a4 20
  domain      = "pyrossh.dev"
778e0a4 21
  zone_id     = var.cloudflare_zone_id
778e0a4 22
  enabled     = true
778e0a4 23
}
778e0a4 24
9bd972a 25
9bd972a 26
resource "cloudflare_r2_bucket" "git" {
9bd972a 27
  account_id = var.cloudflare_account_id
9bd972a 28
  name       = "git"
9bd972a 29
}
9bd972a 30
9bd972a 31
resource "cloudflare_r2_custom_domain" "git" {
9bd972a 32
  account_id  = var.cloudflare_account_id
9bd972a 33
  bucket_name = cloudflare_r2_bucket.git.name
9bd972a 34
  domain      = "git.pyrossh.dev"
9bd972a 35
  zone_id     = var.cloudflare_zone_id
9bd972a 36
  enabled     = true
9bd972a 37
}
9bd972a 38
de88ec5 39
resource "cloudflare_r2_bucket" "apps" {
de88ec5 40
  account_id = var.cloudflare_account_id
de88ec5 41
  name       = "apps"
de88ec5 42
}
de88ec5 43
5a9aa2d 44
# Runs the Eleventy build on every `terraform apply`, before the sync below,
5a9aa2d 45
# so `terraform apply` alone is enough — no separate `npm run build` step
5a9aa2d 46
# needed first. Same "always re-run" trigger pattern as sync_website.
5a9aa2d 47
resource "terraform_data" "build_website" {
5a9aa2d 48
  triggers_replace = [timestamp()]
5a9aa2d 49
5a9aa2d 50
  provisioner "local-exec" {
5a9aa2d 51
    working_dir = "${path.module}/.."
5a9aa2d 52
    command     = "npm run build"
5a9aa2d 53
  }
5a9aa2d 54
}
5a9aa2d 55
778e0a4 56
# Syncs ../dist into the bucket on every `terraform apply` — `terraform_data`
778e0a4 57
# has no real infra state of its own, so `triggers_replace` on a timestamp is
778e0a4 58
# the standard way to force its provisioner to re-run every time rather than
778e0a4 59
# only on the first apply. `rclone sync` (not `copy`) both uploads changed
778e0a4 60
# files and deletes remote objects no longer present locally, so the bucket
778e0a4 61
# always matches the latest build exactly.
778e0a4 62
#
778e0a4 63
# Requires `rclone` on the machine running `terraform apply`, with a remote
778e0a4 64
# named "website" configured for R2 (`rclone config`) — an S3-compatible
778e0a4 65
# access key/secret scoped to this bucket, not the Cloudflare API token used
778e0a4 66
# above.
5a9aa2d 67
#
fa93bad 68
resource "terraform_data" "sync_website" {
fa93bad 69
  triggers_replace = [timestamp()]
fa93bad 70
fa93bad 71
  depends_on = [cloudflare_r2_bucket.website, terraform_data.build_website]
fa93bad 72
fa93bad 73
  provisioner "local-exec" {
60e980e 74
    command = "rclone sync ${path.module}/../dist website:${cloudflare_r2_bucket.website.name} --checksum --fast-list --multi-thread-streams=32 -P "
fa93bad 75
  }
fa93bad 76
}
fa93bad 77
5a9aa2d 78
# The custom domain sits behind Cloudflare's edge cache, and by default only
5a9aa2d 79
# static-asset extensions (css/js/images/fonts) are cached there — not HTML.
5a9aa2d 80
# Since the sync reuses the same URLs on every deploy (no content-hashed
5a9aa2d 81
# filenames), an updated asset at an unchanged path would keep serving the
5a9aa2d 82
# stale cached copy at the edge until it expires on its own. Purging right
5a9aa2d 83
# after the sync avoids that gap. Requires the CLOUDFLARE_API_TOKEN already
5a9aa2d 84
# used above to also carry the "Zone > Cache Purge > Purge" permission for
5a9aa2d 85
# this zone — it isn't included in the R2-only scope from the setup steps
5a9aa2d 86
# earlier, so add it to the token if the purge call starts failing with 403.
fa93bad 87
#
fa93bad 88
# Kept as its own resource (not tacked onto sync_website's command) so a
fa93bad 89
# flaky purge call fails independently of the sync — you can tell from the
fa93bad 90
# apply output which of the two actually broke, rather than one opaque
fa93bad 91
# multi-line script's exit code.
fa93bad 92
resource "terraform_data" "purge_cache" {
778e0a4 93
  triggers_replace = [timestamp()]
778e0a4 94
fa93bad 95
  depends_on = [terraform_data.sync_website]
778e0a4 96
778e0a4 97
  provisioner "local-exec" {
5a9aa2d 98
    command = <<-EOT
fa93bad 99
      curl -sf --retry 3 -X POST "https://api.cloudflare.com/client/v4/zones/${var.cloudflare_zone_id}/purge_cache" \
5a9aa2d 100
        -H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
5a9aa2d 101
        -H "Content-Type: application/json" \
5a9aa2d 102
        --data '{"purge_everything": true}'
5a9aa2d 103
    EOT
778e0a4 104
  }
778e0a4 105
}